Skip to content

Commit 3f84a92

Browse files
authored
SLO: CRUD API Functionality (grafana#145)
* ListSLOs functionality retrives all SLOs * CreateSLO and GetSLO Functionality implemented * DeleteSLO Functionality implemented * UpdateSLO Functionality implemented * UpdateSLO Functionality corrected * Update Linting Errors * Removes Unnecessary Comments * Update Variable Naming * Lint Checker * Update Tests for SLOs * Updating Slo Types with a comment to original source files * Updated SLO Types and Tests
1 parent 44fa23d commit 3f84a92

File tree

2 files changed

+357
-0
lines changed

2 files changed

+357
-0
lines changed

slo.go

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Slo types lifted from github.com/grafana/slo/pkg/generated/models/slo/slo_type_gen.go
2+
package gapi
3+
4+
import (
5+
"bytes"
6+
"encoding/json"
7+
"fmt"
8+
)
9+
10+
var sloPath string = "/api/plugins/grafana-slo-app/resources/v1/slo"
11+
12+
type Slos struct {
13+
Slos []Slo `json:"slos"`
14+
}
15+
16+
const (
17+
QueryTypeFreeform QueryType = "freeform"
18+
QueryTypeHistogram QueryType = "histogram"
19+
QueryTypeRatio QueryType = "ratio"
20+
QueryTypeThreshold QueryType = "threshold"
21+
)
22+
23+
const (
24+
ThresholdOperatorEmpty ThresholdOperator = "<"
25+
ThresholdOperatorEqualEqual ThresholdOperator = "=="
26+
ThresholdOperatorN1 ThresholdOperator = "<="
27+
ThresholdOperatorN2 ThresholdOperator = ">="
28+
ThresholdOperatorN3 ThresholdOperator = ">"
29+
)
30+
31+
type Alerting struct {
32+
Annotations []Label `json:"annotations,omitempty"`
33+
FastBurn *AlertingMetadata `json:"fastBurn,omitempty"`
34+
Labels []Label `json:"labels,omitempty"`
35+
SlowBurn *AlertingMetadata `json:"slowBurn,omitempty"`
36+
}
37+
38+
type AlertingMetadata struct {
39+
Annotations []Label `json:"annotations,omitempty"`
40+
Labels []Label `json:"labels,omitempty"`
41+
}
42+
43+
type DashboardRef struct {
44+
UID string `json:"UID"`
45+
}
46+
47+
type FreeformQuery struct {
48+
Query string `json:"query"`
49+
}
50+
51+
type HistogramQuery struct {
52+
GroupByLabels []string `json:"groupByLabels,omitempty"`
53+
Metric MetricDef `json:"metric"`
54+
Percentile float64 `json:"percentile"`
55+
Threshold Threshold `json:"threshold"`
56+
}
57+
58+
type Label struct {
59+
Key string `json:"key"`
60+
Value string `json:"value"`
61+
}
62+
63+
type MetricDef struct {
64+
PrometheusMetric string `json:"prometheusMetric"`
65+
Type *string `json:"type,omitempty"`
66+
}
67+
68+
type Objective struct {
69+
Value float64 `json:"value"`
70+
Window string `json:"window"`
71+
}
72+
73+
type Query struct {
74+
Freeform *FreeformQuery `json:"freeform,omitempty"`
75+
Histogram *HistogramQuery `json:"histogram,omitempty"`
76+
Ratio *RatioQuery `json:"ratio,omitempty"`
77+
Threshold *ThresholdQuery `json:"threshold,omitempty"`
78+
Type QueryType `json:"type"`
79+
}
80+
81+
type QueryType string
82+
83+
type RatioQuery struct {
84+
GroupByLabels []string `json:"groupByLabels,omitempty"`
85+
SuccessMetric MetricDef `json:"successMetric"`
86+
TotalMetric MetricDef `json:"totalMetric"`
87+
}
88+
89+
type Slo struct {
90+
Alerting *Alerting `json:"alerting,omitempty"`
91+
Description string `json:"description"`
92+
DrillDownDashboardRef *DashboardRef `json:"drillDownDashboardRef,omitempty"`
93+
Labels []Label `json:"labels,omitempty"`
94+
Name string `json:"name"`
95+
Objectives []Objective `json:"objectives"`
96+
Query Query `json:"query"`
97+
UUID string `json:"uuid"`
98+
}
99+
100+
type Threshold struct {
101+
Operator ThresholdOperator `json:"operator"`
102+
Value float64 `json:"value"`
103+
}
104+
105+
type ThresholdOperator string
106+
107+
type ThresholdQuery struct {
108+
GroupByLabels []string `json:"groupByLabels,omitempty"`
109+
Metric MetricDef `json:"metric"`
110+
Threshold Threshold `json:"threshold"`
111+
}
112+
113+
type CreateSLOResponse struct {
114+
Message string `json:"message,omitempty"`
115+
UUID string `json:"uuid,omitempty"`
116+
}
117+
118+
// ListSlos retrieves a list of all Slos
119+
func (c *Client) ListSlos() (Slos, error) {
120+
var slos Slos
121+
122+
if err := c.request("GET", sloPath, nil, nil, &slos); err != nil {
123+
return Slos{}, err
124+
}
125+
126+
return slos, nil
127+
}
128+
129+
// GetSLO returns a single Slo based on its uuid
130+
func (c *Client) GetSlo(uuid string) (Slo, error) {
131+
var slo Slo
132+
path := fmt.Sprintf("%s/%s", sloPath, uuid)
133+
134+
if err := c.request("GET", path, nil, nil, &slo); err != nil {
135+
return Slo{}, err
136+
}
137+
138+
return slo, nil
139+
}
140+
141+
// CreateSLO creates a single Slo
142+
func (c *Client) CreateSlo(slo Slo) (CreateSLOResponse, error) {
143+
response := CreateSLOResponse{}
144+
145+
data, err := json.Marshal(slo)
146+
if err != nil {
147+
return response, err
148+
}
149+
150+
if err := c.request("POST", sloPath, nil, bytes.NewBuffer(data), &response); err != nil {
151+
return CreateSLOResponse{}, err
152+
}
153+
154+
return response, err
155+
}
156+
157+
// DeleteSLO deletes the Slo with the passed in UUID
158+
func (c *Client) DeleteSlo(uuid string) error {
159+
path := fmt.Sprintf("%s/%s", sloPath, uuid)
160+
return c.request("DELETE", path, nil, nil, nil)
161+
}
162+
163+
// UpdateSLO updates the Slo with the passed in UUID and Slo
164+
func (c *Client) UpdateSlo(uuid string, slo Slo) error {
165+
path := fmt.Sprintf("%s/%s", sloPath, uuid)
166+
167+
data, err := json.Marshal(slo)
168+
if err != nil {
169+
return err
170+
}
171+
172+
if err := c.request("PUT", path, nil, bytes.NewBuffer(data), nil); err != nil {
173+
return err
174+
}
175+
176+
return nil
177+
}

slo_test.go

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package gapi
2+
3+
import (
4+
"testing"
5+
6+
"github.com/gobs/pretty"
7+
)
8+
9+
func TestSLOs(t *testing.T) {
10+
t.Run("list all SLOs succeeds", func(t *testing.T) {
11+
client := gapiTestTools(t, 200, getSlosJSON)
12+
13+
resp, err := client.ListSlos()
14+
15+
slos := resp.Slos
16+
17+
if err != nil {
18+
t.Error(err)
19+
}
20+
t.Log(pretty.PrettyFormat(slos))
21+
if len(slos) != 1 {
22+
t.Errorf("wrong number of contact points returned, got %d", len(slos))
23+
}
24+
if slos[0].Name != "list-slos" {
25+
t.Errorf("incorrect name - expected Name-Test, got %s", slos[0].Name)
26+
}
27+
})
28+
29+
t.Run("get individual SLO succeeds", func(t *testing.T) {
30+
client := gapiTestTools(t, 200, getSloJSON)
31+
32+
slo, err := client.GetSlo("qkkrknp12w6tmsdcrfkdf")
33+
34+
t.Log(pretty.PrettyFormat(slo))
35+
if err != nil {
36+
t.Error(err)
37+
}
38+
if slo.UUID != "qkkrknp12w6tmsdcrfkdf" {
39+
t.Errorf("incorrect UID - expected qkkrknp12w6tmsdcrfkdf, got %s", slo.UUID)
40+
}
41+
})
42+
43+
t.Run("get non-existent SLOs fails", func(t *testing.T) {
44+
client := gapiTestTools(t, 404, getSlosJSON)
45+
46+
slo, err := client.GetSlo("qkkrknp12w6tmsdcrfkdf")
47+
48+
if err == nil {
49+
t.Log(pretty.PrettyFormat(slo))
50+
t.Error("expected error but got nil")
51+
}
52+
})
53+
54+
t.Run("create SLO succeeds", func(t *testing.T) {
55+
client := gapiTestTools(t, 201, createSloJSON)
56+
slo := generateSlo()
57+
58+
resp, err := client.CreateSlo(slo)
59+
60+
if err != nil {
61+
t.Error(err)
62+
}
63+
if resp.UUID != "sjnp8wobcbs3eit28n8yb" {
64+
t.Errorf("unexpected UID returned, got %s", resp.UUID)
65+
}
66+
})
67+
68+
t.Run("update SLO succeeds", func(t *testing.T) {
69+
client := gapiTestTools(t, 200, createSloJSON)
70+
slo := generateSlo()
71+
slo.Description = "Updated Description"
72+
73+
err := client.UpdateSlo(slo.UUID, slo)
74+
75+
if err != nil {
76+
t.Error(err)
77+
}
78+
})
79+
80+
t.Run("delete SLO succeeds", func(t *testing.T) {
81+
client := gapiTestTools(t, 204, "")
82+
83+
err := client.DeleteSlo("qkkrknp12w6tmsdcrfkdf")
84+
85+
if err != nil {
86+
t.Log(err)
87+
t.Error(err)
88+
}
89+
})
90+
}
91+
92+
const getSlosJSON = `
93+
{
94+
"slos": [
95+
{
96+
"uuid": "qkkrknp12w6tmsdcrfkdf",
97+
"name": "list-slos",
98+
"description": "list-slos-description",
99+
"query": {
100+
"freeform": {
101+
"query": "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"
102+
},
103+
"type": "freeform"
104+
},
105+
"objectives": [
106+
{
107+
"value": 0.995,
108+
"window": "28d"
109+
}
110+
],
111+
"drillDownDashboardRef": {
112+
"uid": "5IkqX6P4k"
113+
}
114+
}
115+
]
116+
}`
117+
118+
const getSloJSON = `
119+
{
120+
"uuid": "qkkrknp12w6tmsdcrfkdf",
121+
"name": "Name-Test",
122+
"description": "Description-Test",
123+
"query": {
124+
"freeform": {
125+
"query": "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"
126+
},
127+
"type": "freeform"
128+
},
129+
"objectives": [
130+
{
131+
"value": 0.995,
132+
"window": "28d"
133+
}
134+
],
135+
"drillDownDashboardRef": {
136+
"uid": "5IkqX6P4k"
137+
}
138+
}`
139+
140+
const createSloJSON = `
141+
{
142+
"uuid": "sjnp8wobcbs3eit28n8yb",
143+
"name": "test-name",
144+
"description": "test-description",
145+
"query": {
146+
"freeform": {
147+
"query": "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))"
148+
},
149+
"type": "freeform"
150+
},
151+
"objectives": [
152+
{
153+
"value": 0.995,
154+
"window": "30d"
155+
}
156+
],
157+
"drillDownDashboardRef": {
158+
"uid": "zz5giRyVk"
159+
}
160+
}
161+
`
162+
163+
func generateSlo() Slo {
164+
objective := []Objective{{Value: 0.995, Window: "30d"}}
165+
query := Query{
166+
Freeform: &FreeformQuery{
167+
Query: "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))",
168+
},
169+
Type: QueryTypeFreeform,
170+
}
171+
172+
slo := Slo{
173+
Name: "test-name",
174+
Description: "test-description",
175+
Objectives: objective,
176+
Query: query,
177+
}
178+
179+
return slo
180+
}

0 commit comments

Comments
 (0)