This repository has been archived by the owner on Jan 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
- Loading branch information
1 parent
44fa23d
commit 3f84a92
Showing
2 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// Slo types lifted from github.com/grafana/slo/pkg/generated/models/slo/slo_type_gen.go | ||
package gapi | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
) | ||
|
||
var sloPath string = "/api/plugins/grafana-slo-app/resources/v1/slo" | ||
|
||
type Slos struct { | ||
Slos []Slo `json:"slos"` | ||
} | ||
|
||
const ( | ||
QueryTypeFreeform QueryType = "freeform" | ||
QueryTypeHistogram QueryType = "histogram" | ||
QueryTypeRatio QueryType = "ratio" | ||
QueryTypeThreshold QueryType = "threshold" | ||
) | ||
|
||
const ( | ||
ThresholdOperatorEmpty ThresholdOperator = "<" | ||
ThresholdOperatorEqualEqual ThresholdOperator = "==" | ||
ThresholdOperatorN1 ThresholdOperator = "<=" | ||
ThresholdOperatorN2 ThresholdOperator = ">=" | ||
ThresholdOperatorN3 ThresholdOperator = ">" | ||
) | ||
|
||
type Alerting struct { | ||
Annotations []Label `json:"annotations,omitempty"` | ||
FastBurn *AlertingMetadata `json:"fastBurn,omitempty"` | ||
Labels []Label `json:"labels,omitempty"` | ||
SlowBurn *AlertingMetadata `json:"slowBurn,omitempty"` | ||
} | ||
|
||
type AlertingMetadata struct { | ||
Annotations []Label `json:"annotations,omitempty"` | ||
Labels []Label `json:"labels,omitempty"` | ||
} | ||
|
||
type DashboardRef struct { | ||
UID string `json:"UID"` | ||
} | ||
|
||
type FreeformQuery struct { | ||
Query string `json:"query"` | ||
} | ||
|
||
type HistogramQuery struct { | ||
GroupByLabels []string `json:"groupByLabels,omitempty"` | ||
Metric MetricDef `json:"metric"` | ||
Percentile float64 `json:"percentile"` | ||
Threshold Threshold `json:"threshold"` | ||
} | ||
|
||
type Label struct { | ||
Key string `json:"key"` | ||
Value string `json:"value"` | ||
} | ||
|
||
type MetricDef struct { | ||
PrometheusMetric string `json:"prometheusMetric"` | ||
Type *string `json:"type,omitempty"` | ||
} | ||
|
||
type Objective struct { | ||
Value float64 `json:"value"` | ||
Window string `json:"window"` | ||
} | ||
|
||
type Query struct { | ||
Freeform *FreeformQuery `json:"freeform,omitempty"` | ||
Histogram *HistogramQuery `json:"histogram,omitempty"` | ||
Ratio *RatioQuery `json:"ratio,omitempty"` | ||
Threshold *ThresholdQuery `json:"threshold,omitempty"` | ||
Type QueryType `json:"type"` | ||
} | ||
|
||
type QueryType string | ||
|
||
type RatioQuery struct { | ||
GroupByLabels []string `json:"groupByLabels,omitempty"` | ||
SuccessMetric MetricDef `json:"successMetric"` | ||
TotalMetric MetricDef `json:"totalMetric"` | ||
} | ||
|
||
type Slo struct { | ||
Alerting *Alerting `json:"alerting,omitempty"` | ||
Description string `json:"description"` | ||
DrillDownDashboardRef *DashboardRef `json:"drillDownDashboardRef,omitempty"` | ||
Labels []Label `json:"labels,omitempty"` | ||
Name string `json:"name"` | ||
Objectives []Objective `json:"objectives"` | ||
Query Query `json:"query"` | ||
UUID string `json:"uuid"` | ||
} | ||
|
||
type Threshold struct { | ||
Operator ThresholdOperator `json:"operator"` | ||
Value float64 `json:"value"` | ||
} | ||
|
||
type ThresholdOperator string | ||
|
||
type ThresholdQuery struct { | ||
GroupByLabels []string `json:"groupByLabels,omitempty"` | ||
Metric MetricDef `json:"metric"` | ||
Threshold Threshold `json:"threshold"` | ||
} | ||
|
||
type CreateSLOResponse struct { | ||
Message string `json:"message,omitempty"` | ||
UUID string `json:"uuid,omitempty"` | ||
} | ||
|
||
// ListSlos retrieves a list of all Slos | ||
func (c *Client) ListSlos() (Slos, error) { | ||
var slos Slos | ||
|
||
if err := c.request("GET", sloPath, nil, nil, &slos); err != nil { | ||
return Slos{}, err | ||
} | ||
|
||
return slos, nil | ||
} | ||
|
||
// GetSLO returns a single Slo based on its uuid | ||
func (c *Client) GetSlo(uuid string) (Slo, error) { | ||
var slo Slo | ||
path := fmt.Sprintf("%s/%s", sloPath, uuid) | ||
|
||
if err := c.request("GET", path, nil, nil, &slo); err != nil { | ||
return Slo{}, err | ||
} | ||
|
||
return slo, nil | ||
} | ||
|
||
// CreateSLO creates a single Slo | ||
func (c *Client) CreateSlo(slo Slo) (CreateSLOResponse, error) { | ||
response := CreateSLOResponse{} | ||
|
||
data, err := json.Marshal(slo) | ||
if err != nil { | ||
return response, err | ||
} | ||
|
||
if err := c.request("POST", sloPath, nil, bytes.NewBuffer(data), &response); err != nil { | ||
return CreateSLOResponse{}, err | ||
} | ||
|
||
return response, err | ||
} | ||
|
||
// DeleteSLO deletes the Slo with the passed in UUID | ||
func (c *Client) DeleteSlo(uuid string) error { | ||
path := fmt.Sprintf("%s/%s", sloPath, uuid) | ||
return c.request("DELETE", path, nil, nil, nil) | ||
} | ||
|
||
// UpdateSLO updates the Slo with the passed in UUID and Slo | ||
func (c *Client) UpdateSlo(uuid string, slo Slo) error { | ||
path := fmt.Sprintf("%s/%s", sloPath, uuid) | ||
|
||
data, err := json.Marshal(slo) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := c.request("PUT", path, nil, bytes.NewBuffer(data), nil); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package gapi | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/gobs/pretty" | ||
) | ||
|
||
func TestSLOs(t *testing.T) { | ||
t.Run("list all SLOs succeeds", func(t *testing.T) { | ||
client := gapiTestTools(t, 200, getSlosJSON) | ||
|
||
resp, err := client.ListSlos() | ||
|
||
slos := resp.Slos | ||
|
||
if err != nil { | ||
t.Error(err) | ||
} | ||
t.Log(pretty.PrettyFormat(slos)) | ||
if len(slos) != 1 { | ||
t.Errorf("wrong number of contact points returned, got %d", len(slos)) | ||
} | ||
if slos[0].Name != "list-slos" { | ||
t.Errorf("incorrect name - expected Name-Test, got %s", slos[0].Name) | ||
} | ||
}) | ||
|
||
t.Run("get individual SLO succeeds", func(t *testing.T) { | ||
client := gapiTestTools(t, 200, getSloJSON) | ||
|
||
slo, err := client.GetSlo("qkkrknp12w6tmsdcrfkdf") | ||
|
||
t.Log(pretty.PrettyFormat(slo)) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if slo.UUID != "qkkrknp12w6tmsdcrfkdf" { | ||
t.Errorf("incorrect UID - expected qkkrknp12w6tmsdcrfkdf, got %s", slo.UUID) | ||
} | ||
}) | ||
|
||
t.Run("get non-existent SLOs fails", func(t *testing.T) { | ||
client := gapiTestTools(t, 404, getSlosJSON) | ||
|
||
slo, err := client.GetSlo("qkkrknp12w6tmsdcrfkdf") | ||
|
||
if err == nil { | ||
t.Log(pretty.PrettyFormat(slo)) | ||
t.Error("expected error but got nil") | ||
} | ||
}) | ||
|
||
t.Run("create SLO succeeds", func(t *testing.T) { | ||
client := gapiTestTools(t, 201, createSloJSON) | ||
slo := generateSlo() | ||
|
||
resp, err := client.CreateSlo(slo) | ||
|
||
if err != nil { | ||
t.Error(err) | ||
} | ||
if resp.UUID != "sjnp8wobcbs3eit28n8yb" { | ||
t.Errorf("unexpected UID returned, got %s", resp.UUID) | ||
} | ||
}) | ||
|
||
t.Run("update SLO succeeds", func(t *testing.T) { | ||
client := gapiTestTools(t, 200, createSloJSON) | ||
slo := generateSlo() | ||
slo.Description = "Updated Description" | ||
|
||
err := client.UpdateSlo(slo.UUID, slo) | ||
|
||
if err != nil { | ||
t.Error(err) | ||
} | ||
}) | ||
|
||
t.Run("delete SLO succeeds", func(t *testing.T) { | ||
client := gapiTestTools(t, 204, "") | ||
|
||
err := client.DeleteSlo("qkkrknp12w6tmsdcrfkdf") | ||
|
||
if err != nil { | ||
t.Log(err) | ||
t.Error(err) | ||
} | ||
}) | ||
} | ||
|
||
const getSlosJSON = ` | ||
{ | ||
"slos": [ | ||
{ | ||
"uuid": "qkkrknp12w6tmsdcrfkdf", | ||
"name": "list-slos", | ||
"description": "list-slos-description", | ||
"query": { | ||
"freeform": { | ||
"query": "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" | ||
}, | ||
"type": "freeform" | ||
}, | ||
"objectives": [ | ||
{ | ||
"value": 0.995, | ||
"window": "28d" | ||
} | ||
], | ||
"drillDownDashboardRef": { | ||
"uid": "5IkqX6P4k" | ||
} | ||
} | ||
] | ||
}` | ||
|
||
const getSloJSON = ` | ||
{ | ||
"uuid": "qkkrknp12w6tmsdcrfkdf", | ||
"name": "Name-Test", | ||
"description": "Description-Test", | ||
"query": { | ||
"freeform": { | ||
"query": "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" | ||
}, | ||
"type": "freeform" | ||
}, | ||
"objectives": [ | ||
{ | ||
"value": 0.995, | ||
"window": "28d" | ||
} | ||
], | ||
"drillDownDashboardRef": { | ||
"uid": "5IkqX6P4k" | ||
} | ||
}` | ||
|
||
const createSloJSON = ` | ||
{ | ||
"uuid": "sjnp8wobcbs3eit28n8yb", | ||
"name": "test-name", | ||
"description": "test-description", | ||
"query": { | ||
"freeform": { | ||
"query": "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))" | ||
}, | ||
"type": "freeform" | ||
}, | ||
"objectives": [ | ||
{ | ||
"value": 0.995, | ||
"window": "30d" | ||
} | ||
], | ||
"drillDownDashboardRef": { | ||
"uid": "zz5giRyVk" | ||
} | ||
} | ||
` | ||
|
||
func generateSlo() Slo { | ||
objective := []Objective{{Value: 0.995, Window: "30d"}} | ||
query := Query{ | ||
Freeform: &FreeformQuery{ | ||
Query: "sum(rate(apiserver_request_total{code!=\"500\"}[$__rate_interval])) / sum(rate(apiserver_request_total[$__rate_interval]))", | ||
}, | ||
Type: QueryTypeFreeform, | ||
} | ||
|
||
slo := Slo{ | ||
Name: "test-name", | ||
Description: "test-description", | ||
Objectives: objective, | ||
Query: query, | ||
} | ||
|
||
return slo | ||
} |