Skip to content

Commit

Permalink
ci: template validation for rest, restperf (#2586)
Browse files Browse the repository at this point in the history
* ci: template validation for rest, restperf
  • Loading branch information
Hardikl authored Jan 22, 2024
1 parent 324db43 commit 6c24b99
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 17 deletions.
7 changes: 7 additions & 0 deletions cmd/tools/rest/href_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
type HrefBuilder struct {
apiPath string
fields string
counterSchema string
filter []string
queryFields string
queryValue string
Expand All @@ -29,6 +30,11 @@ func (b *HrefBuilder) Fields(fields []string) *HrefBuilder {
return b
}

func (b *HrefBuilder) CounterSchema(counterSchema []string) *HrefBuilder {
b.counterSchema = strings.Join(counterSchema, ",")
return b
}

func (b *HrefBuilder) Filter(filter []string) *HrefBuilder {
b.filter = filter
return b
Expand Down Expand Up @@ -63,6 +69,7 @@ func (b *HrefBuilder) Build() string {

href.WriteString("?return_records=true")
addArg(&href, "&fields=", b.fields)
addArg(&href, "&counter_schemas=", b.counterSchema)
for _, f := range b.filter {
addArg(&href, "&", f)
}
Expand Down
26 changes: 13 additions & 13 deletions conf/rest/9.12.0/namespace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ query: api/storage/namespaces
object: namespace

counters:
- ^^uuid => uuid
- ^location.node.name => node
- ^location.volume.name => volume
- ^name => path
- ^os_type => os_type
- ^status.read_only => is_read_only
- ^status.state => state
- ^subsystem => subsystem
- ^subsystem_map.nsid => nsid
- ^svm.name => svm
- space.block_size => block_size
- space.size => size
- space.used => size_used
- ^^uuid => uuid
- ^location.node.name => node
- ^location.volume.name => volume
- ^name => path
- ^os_type => os_type
- ^status.read_only => is_read_only
- ^status.state => state
- ^subsystem_map.nsid => nsid
- ^subsystem_map.subsystem.name => subsystem
- ^svm.name => svm
- space.block_size => block_size
- space.size => size
- space.used => size_used

plugins:
- MetricAgent:
Expand Down
3 changes: 1 addition & 2 deletions conf/rest/9.12.0/netport.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ counters:
- ^lag.active_ports.name => lag_port
- ^lag.distribution_policy => lag_distribution_policy
- ^lag.mode => lag_mode
- ^operational_speed => op_speed
- ^speed => speed
- ^speed => op_speed
- ^state => status
- ^type => port_type
- ^vlan.base_port.name => vlan_port
Expand Down
2 changes: 1 addition & 1 deletion conf/zapi/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ objects:
FlexCache: flexcache.yaml
LIF: lif.yaml
Lun: lun.yaml
# NetPort: netPort.yaml
# NetPort: netport.yaml
Namespace: namespace.yaml
Node: node.yaml
NtpServer: ntpserver.yaml
Expand Down
184 changes: 184 additions & 0 deletions integration/test/counter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package main

import (
"fmt"
"github.com/Netapp/harvest-automation/test/installer"
"github.com/Netapp/harvest-automation/test/utils"
"github.com/hashicorp/go-version"
"github.com/netapp/harvest/v2/cmd/collectors"
rest2 "github.com/netapp/harvest/v2/cmd/tools/rest"
"github.com/netapp/harvest/v2/pkg/auth"
"github.com/netapp/harvest/v2/pkg/conf"
"github.com/netapp/harvest/v2/pkg/logging"
"github.com/netapp/harvest/v2/pkg/tree"
"github.com/netapp/harvest/v2/pkg/tree/node"
"github.com/netapp/harvest/v2/pkg/util"
"github.com/rs/zerolog/log"
"os"
"path/filepath"
"strings"
"testing"
"time"
)

type counterData struct {
api string
restCounters []string
perfCounters []string
}

var replacer = strings.NewReplacer("{", "", "}", "", "^^", "", "^", "")

// TestCounters extracts non-hidden counters from all of the rest and restperf templates and then invokes an HTTP GET for each api path + counters.
// Valid responses are status code = 200. Objects do not need to exist on the cluster, only the api path and counter names are checked.
func TestCounters(t *testing.T) {
var (
poller *conf.Poller
client *rest2.Client
)

utils.SkipIfMissing(t, utils.Regression)
_, err := conf.LoadHarvestConfig(installer.HarvestConfigFile)
if err != nil {
log.Fatal().Err(err).Msg("Unable to load harvest config")
}

pollerName := "dc1"
if poller, err = conf.PollerNamed(pollerName); err != nil {
log.Fatal().Err(err).Str("poller", pollerName).Msgf("")
}
if poller.Addr == "" {
log.Fatal().Str("poller", pollerName).Msg("Address is empty")
}
timeout, _ := time.ParseDuration(rest2.DefaultTimeout)

if client, err = rest2.New(poller, timeout, auth.NewCredentials(poller, logging.Get())); err != nil {
log.Fatal().Err(err).Str("poller", pollerName).Msg("error creating new client")
}

if err = client.Init(5); err != nil {
log.Fatal().Err(err).Msg("client init failed")
}

restCounters := processRestCounters(client)
if err = invokeRestCall(client, restCounters); err != nil {
log.Error().Err(err).Msg("rest call failed")
}

}

func invokeRestCall(client *rest2.Client, counters map[string][]counterData) error {
for _, countersDetail := range counters {
for _, counterDetail := range countersDetail {
href := rest2.NewHrefBuilder().
APIPath(counterDetail.api).
Fields(counterDetail.restCounters).
CounterSchema(counterDetail.perfCounters).
Build()

if _, err := collectors.InvokeRestCall(client, href, logging.Get()); err != nil {
return fmt.Errorf("failed to invoke rest href=%s call: %w", href, err)
}
}
}
return nil
}

func processRestCounters(client *rest2.Client) map[string][]counterData {
restPerfCounters := visitRestTemplates("../../conf/restperf", client, func(path string, currentVersion string, client *rest2.Client) map[string][]counterData {
return processRestConfigCounters(path, currentVersion, "perf")
})

restCounters := visitRestTemplates("../../conf/rest", client, func(path string, currentVersion string, client *rest2.Client) map[string][]counterData {
return processRestConfigCounters(path, currentVersion, "rest")
})

for k, v := range restPerfCounters {
restCounters[k] = v
}
return restCounters
}

func visitRestTemplates(dir string, client *rest2.Client, eachTemp func(path string, currentVersion string, client *rest2.Client) map[string][]counterData) map[string][]counterData {
result := make(map[string][]counterData)
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Fatal().Err(err).Msg("failed to read directory:")
}
ext := filepath.Ext(path)
if ext != ".yaml" {
return nil
}
if strings.HasSuffix(path, "default.yaml") {
return nil
}
r := eachTemp(path, client.Cluster().GetVersion(), client)
for k, v := range r {
result[k] = v
}
return nil
})

if err != nil {
log.Fatal().Err(err).Msgf("failed to walk directory: %s", dir)
}

return result
}

func processRestConfigCounters(path string, currentVersion string, kind string) map[string][]counterData {
countersData := make(map[string][]counterData)
templateVersion := filepath.Base(filepath.Dir(path))
templateV, err := version.NewVersion(templateVersion)
if err != nil {
return nil
}
currentV, err := version.NewVersion(currentVersion)
if err != nil {
return nil
}
if templateV.GreaterThan(currentV) {
return nil
}

t, err := tree.ImportYaml(path)
if t == nil || err != nil {
fmt.Printf("Unable to import template file %s. File is invalid or empty err=%s\n", path, err)
return nil
}

readCounters(t, path, kind, countersData)
if templateEndpoints := t.GetChildS("endpoints"); templateEndpoints != nil {
for _, endpoint := range templateEndpoints.GetChildren() {
readCounters(endpoint, path, kind, countersData)
}
}

return countersData
}

func readCounters(t *node.Node, path, kind string, countersData map[string][]counterData) {
var counters []string
if templateCounters := t.GetChildS("counters"); templateCounters != nil {
templateQuery := t.GetChildS("query")
counters = make([]string, 0)
for _, c := range templateCounters.GetAllChildContentS() {
if c != "" {
if strings.Contains(c, "filter") || strings.Contains(c, "hidden_fields") {
continue
}
if strings.HasPrefix(c, "^") && kind == "perf" {
continue
}
name, _, _, _ := util.ParseMetric(c)
nameArray := strings.Split(name, ".#")
counters = append(counters, replacer.Replace(nameArray[0]))
}
}
if kind == "rest" {
countersData[path] = append(countersData[path], counterData{api: templateQuery.GetContentS(), restCounters: counters})
} else {
countersData[path] = append(countersData[path], counterData{api: templateQuery.GetContentS(), perfCounters: counters})
}
}
}
2 changes: 1 addition & 1 deletion integration/test/dashboard_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func TestJsonExpression(t *testing.T) {
if counterIsMissing(rest, counter, 7*time.Minute) {
t.Fatalf("rest qos counters not found dur=%s", time.Since(now).Round(time.Millisecond).String())
}
if counterIsMissing(zapi, counter, 1*time.Minute) {
if counterIsMissing(zapi, counter, 2*time.Minute) {
t.Fatalf("zapi qos counters not found dur=%s", time.Since(now).Round(time.Millisecond).String())
}
}
Expand Down

0 comments on commit 6c24b99

Please sign in to comment.