Skip to content

Commit

Permalink
Merge pull request #48 from Juniper/feat/47
Browse files Browse the repository at this point in the history
Introduce support for blueprint anomalies
  • Loading branch information
chrismarget-j authored Jun 24, 2023
2 parents 4a2f7e4 + 8ade103 commit 4a68f2d
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 23 deletions.
115 changes: 114 additions & 1 deletion apstra/api_anomalies.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (
)

const (
apiUrlAnomalies = "/api/anomalies"
apiUrlAnomalies = "/api/anomalies"
apiUrlBlueprintAnomalies = apiUrlBlueprintById + apiUrlPathDelim + "anomalies"
apiUrlBlueprintAnomaliesByNode = apiUrlBlueprintById + apiUrlPathDelim + "anomalies_nodes_count"
apiUrlBlueprintAnomaliesByService = apiUrlBlueprintById + apiUrlPathDelim + "anomalies_services_count"
)

type AnomalyProperty struct {
Expand Down Expand Up @@ -224,3 +227,113 @@ func (o *Client) getAnomalies(ctx context.Context) ([]Anomaly, error) {
}
return result, nil
}

// also available in scotch/schemas/alerts.py:
// BGP_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// BLUEPRINT_RENDERING_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// CABLING_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// CONFIG_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// CONFIG_MISMATCH_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// DEPLOYMENT_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// EXTENSIBLE_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // ?
// HOSTNAME_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// INTERFACE_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// LAG_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // ?
// LIVENESS_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// MAC_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // ?
// MLAG_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // ?
// ROUTE_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node
// STREAMING_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // ?
// PROBE_ANOMALY_SCHEMA = t.Object(BASE_ANOMALY_SCHEMA, { // node

type BlueprintAnomaly struct {
Id ObjectId `json:"id"` // part of base schema
LastModifiedAt *time.Time `json:"last_modified_at"` // part of base schema
Severity string `json:"severity"` // part of base schema
AnomalyType string `json:"anomaly_type"` // part of base schema

Actual json.RawMessage `json:"actual"` // universal (near universal?)
Expected json.RawMessage `json:"expected"` // universal (near universal?)
Identity json.RawMessage `json:"identity"` // universal (near universal?)
Role *string `json:"role"` // near universal
Anomalous json.RawMessage `json:"anomalous"` // probe
}

type BlueprintServiceAnomalyCount struct {
AnomalyType string `json:"type"`
Role string `json:"role"`
Count int `json:"count"`
}

// per JP Senior: I think the a reliable list is aos.reference_design.two_stage_l3clos.__init__.py's alert_types list:
// aos/reference_design/two_stage_l3clos/__init__.py:
// alert_types = ['bgp', 'cabling', 'counter', 'interface', 'hostname', 'liveness',
// 'route', 'config', 'deployment', 'blueprint_rendering', 'probe',
// 'streaming', 'mac', 'arp', 'lag', 'mlag', 'series',
// 'all']

type BlueprintNodeAnomalyCounts struct {
Node string `json:"node"`
SystemId ObjectId `json:"system_id"`
All int `json:"all"`

Arp int `json:"arp"`
Bgp int `json:"bgp"`
BlueprintRendering int `json:"blueprint_rendering"`
Cabling int `json:"cabling"`
Config int `json:"config"`
Counter int `json:"counter"`
Deployment int `json:"deployment"`
Hostname int `json:"hostname"`
Interface int `json:"interface"`
Lag int `json:"lag"`
Liveness int `json:"liveness"`
Mac int `json:"mac"`
Mlag int `json:"mlag"`
Probe int `json:"probe"`
Route int `json:"route"`
Series int `json:"series"`
Streaming int `json:"streaming"`
}

func (o *Client) getBlueprintAnomalies(ctx context.Context, blueprintId ObjectId) ([]BlueprintAnomaly, error) {
var apiResonse struct {
Items []BlueprintAnomaly
}

err := o.talkToApstra(ctx, &talkToApstraIn{
method: http.MethodGet,
urlStr: fmt.Sprintf(apiUrlBlueprintAnomalies, blueprintId),
apiResponse: &apiResonse,
unsynchronized: true,
})
return apiResonse.Items, convertTtaeToAceWherePossible(err)
}

func (o *Client) getBlueprintNodeAnomalyCounts(ctx context.Context, blueprintId ObjectId) ([]BlueprintNodeAnomalyCounts, error) {
var apiResonse struct {
Items []BlueprintNodeAnomalyCounts
}

err := o.talkToApstra(ctx, &talkToApstraIn{
method: http.MethodGet,
urlStr: fmt.Sprintf(apiUrlBlueprintAnomaliesByNode, blueprintId),
apiResponse: &apiResonse,
unsynchronized: true,
})
return apiResonse.Items, convertTtaeToAceWherePossible(err)
}

func (o *Client) getBlueprintServiceAnomalyCounts(ctx context.Context, blueprintId ObjectId) ([]BlueprintServiceAnomalyCount, error) {
var apiResonse struct {
Items []BlueprintServiceAnomalyCount
}

err := o.talkToApstra(ctx, &talkToApstraIn{
method: http.MethodGet,
urlStr: fmt.Sprintf(apiUrlBlueprintAnomaliesByService, blueprintId),
apiResponse: &apiResonse,
unsynchronized: true,
})
return apiResonse.Items, convertTtaeToAceWherePossible(err)
}
108 changes: 108 additions & 0 deletions apstra/api_anomalies_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//go:build integration
// +build integration

package apstra

import (
"context"
"log"
"testing"
)

func TestGetAnomalies(t *testing.T) {
clients, err := getTestClients(context.Background(), t)
if err != nil {
t.Fatal(err)
}

for clientName, client := range clients {
log.Printf("testing getAnomalies() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion())

anomalies, err := client.client.GetAnomalies(context.TODO())
if err != nil {
t.Fatal(err)
}
log.Printf("%d anomalies retrieved", len(anomalies))
}
}

func TestGetBlueprintAnomalies(t *testing.T) {
ctx := context.Background()

clients, err := getTestClients(context.Background(), t)
if err != nil {
t.Fatal(err)
}

for clientName, client := range clients {
bpClient, bpDelete := testBlueprintB(ctx, t, client.client)
defer func() {
err := bpDelete(ctx)
if err != nil {
t.Fatal(err)
}
}()

log.Printf("testing GetBlueprintAnomalies() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion())
anomalies, err := client.client.GetBlueprintAnomalies(ctx, bpClient.Id())
if err != nil {
t.Fatal(err)
}

log.Printf("%d blueprint anomalies retrieved", len(anomalies))
}
}

func TestGetBlueprintNodeAnomalyCounts(t *testing.T) {
ctx := context.Background()

clients, err := getTestClients(context.Background(), t)
if err != nil {
t.Fatal(err)
}

for clientName, client := range clients {
bpClient, bpDelete := testBlueprintB(ctx, t, client.client)
defer func() {
err := bpDelete(ctx)
if err != nil {
t.Fatal(err)
}
}()

log.Printf("testing GetBlueprintAnomalies() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion())
anomalies, err := client.client.GetBlueprintNodeAnomalyCounts(ctx, bpClient.Id())
if err != nil {
t.Fatal(err)
}

log.Printf("%d node anomaly counts retrieved", len(anomalies))
}
}

func TestGetBlueprintServiceAnomalyCounts(t *testing.T) {
ctx := context.Background()

clients, err := getTestClients(context.Background(), t)
if err != nil {
t.Fatal(err)
}

for clientName, client := range clients {
bpClient, bpDelete := testBlueprintB(ctx, t, client.client)
defer func() {
err := bpDelete(ctx)
if err != nil {
t.Fatal(err)
}
}()

log.Printf("testing GetBlueprintAnomalies() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion())
anomalies, err := client.client.GetBlueprintServiceAnomalyCounts(ctx, bpClient.Id())
if err != nil {
t.Fatal(err)
}

log.Printf("%d service anomaly counts retrieved", len(anomalies))
}
}
22 changes: 0 additions & 22 deletions apstra/api_anomalies_test.go → apstra/api_anomalies_unit_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
//go:build integration
// +build integration

package apstra

import (
"context"
"log"
"testing"
)

Expand Down Expand Up @@ -70,20 +65,3 @@ func TestUnpackAnomaly(t *testing.T) {
}
_ = a
}

func TestGetAnomalies(t *testing.T) {
clients, err := getTestClients(context.Background(), t)
if err != nil {
t.Fatal(err)
}

for clientName, client := range clients {
log.Printf("testing getAnomalies() against %s %s (%s)", client.clientType, clientName, client.client.ApiVersion())

anomalies, err := client.client.GetAnomalies(context.TODO())
if err != nil {
t.Fatal(err)
}
log.Printf("%d anomalies retrieved", len(anomalies))
}
}
22 changes: 22 additions & 0 deletions apstra/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,28 @@ func (o *Client) GetAnomalies(ctx context.Context) ([]Anomaly, error) {
return result, nil
}

// GetBlueprintAnomalies returns []BlueprintAnomaly representing all anomalies in
// the blueprint.
func (o *Client) GetBlueprintAnomalies(ctx context.Context, blueprintId ObjectId) ([]BlueprintAnomaly, error) {
return o.getBlueprintAnomalies(ctx, blueprintId)
}

// GetBlueprintNodeAnomalyCounts returns []BlueprintNodeAnomalyCounts
// which summarize current anomalies on a per-node basis in the blueprint.
// Nodes which are not currently experiencing an anomaly are not represented in
// the returned slice.
func (o *Client) GetBlueprintNodeAnomalyCounts(ctx context.Context, blueprintId ObjectId) ([]BlueprintNodeAnomalyCounts, error) {
return o.getBlueprintNodeAnomalyCounts(ctx, blueprintId)
}

// GetBlueprintServiceAnomalyCounts returns []BlueprintServiceAnomalyCount
// which summarize current anomalies on a per-service basis in the blueprint.
// Services which are not currently experiencing an anomaly are not represented
// in the returned slice.
func (o *Client) GetBlueprintServiceAnomalyCounts(ctx context.Context, blueprintId ObjectId) ([]BlueprintServiceAnomalyCount, error) {
return o.getBlueprintServiceAnomalyCounts(ctx, blueprintId)
}

// GetAsnPools returns ASN pools configured on Apstra
func (o *Client) GetAsnPools(ctx context.Context) ([]AsnPool, error) {
return o.getAsnPools(ctx)
Expand Down

0 comments on commit 4a68f2d

Please sign in to comment.