diff --git a/pkg/kubelet/certificate/kubelet.go b/pkg/kubelet/certificate/kubelet.go index af2d21717dc..cb2ac2a4cd7 100644 --- a/pkg/kubelet/certificate/kubelet.go +++ b/pkg/kubelet/certificate/kubelet.go @@ -60,7 +60,7 @@ func NewKubeletServerCertificateManager(kubeClient clientset.Interface, kubeCfg Subsystem: metrics.KubeletSubsystem, Name: "server_expiration_renew_errors", Help: "Counter of certificate renewal errors.", - StabilityLevel: compbasemetrics.ALPHA, + StabilityLevel: compbasemetrics.STABLE, }, ) legacyregistry.MustRegister(certificateRenewFailure) diff --git a/test/instrumentation/decode_metric.go b/test/instrumentation/decode_metric.go index 721ab9738b1..3f738b1edae 100644 --- a/test/instrumentation/decode_metric.go +++ b/test/instrumentation/decode_metric.go @@ -20,6 +20,7 @@ import ( "fmt" "go/ast" "go/token" + "os" "sort" "strconv" "strings" @@ -182,7 +183,24 @@ func (c *metricDecoder) decodeOpts(expr ast.Expr) (metric, error) { if err != nil { return m, err } + case *ast.SelectorExpr: + packageName := fmt.Sprintf("%v", v.X) + + variableExpr, found := c.variables[packageName + "." + v.Sel.Name] + if !found { + return m, newDecodeErrorf(expr, errBadVariableAttribute) + } + bl, ok := variableExpr.(*ast.BasicLit) + if !ok { + return m, newDecodeErrorf(expr, errNonStringAttribute) + } + value, err = stringValue(bl) + if err != nil { + return m, err + } + default: + fmt.Fprintf(os.Stdout, "key %s is type %T from Default\n", key, v) return m, newDecodeErrorf(expr, errNonStringAttribute) } switch key { diff --git a/test/instrumentation/error.go b/test/instrumentation/error.go index bc3942522d8..8fef4f643c2 100644 --- a/test/instrumentation/error.go +++ b/test/instrumentation/error.go @@ -28,7 +28,7 @@ const ( errStabilityLevel = "StabilityLevel should be passed STABLE, ALPHA or removed" errStableSummary = "Stable summary metric is not supported" errInvalidNewMetricCall = "Invalid new metric call, please ensure code compiles" - errNonStringAttribute = "Non string attribute it not supported" + errNonStringAttribute = "Non string attribute is not supported" errBadVariableAttribute = "Metric attribute was not correctly set. Please use only global consts in same file" errFieldNotSupported = "Field %s is not supported" errBuckets = "Buckets should be set to list of floats, result from function call of prometheus.LinearBuckets or prometheus.ExponentialBuckets" diff --git a/test/instrumentation/main.go b/test/instrumentation/main.go index 961cbac95df..cb350b5d889 100644 --- a/test/instrumentation/main.go +++ b/test/instrumentation/main.go @@ -23,6 +23,7 @@ import ( "go/ast" "go/parser" "go/token" + "io/ioutil" "os" "path/filepath" "sort" @@ -124,6 +125,11 @@ func searchFileForStableMetrics(filename string, src interface{}) ([]metric, []e } variables := globalVariableDeclarations(tree) + variables, err = importedGlobalVariableDeclaration(variables, tree.Imports) + if err != nil { + return []metric{}, addFileInformationToErrors([]error{err}, fileset) + } + stableMetricsFunctionCalls, errors := findStableMetricDeclaration(tree, metricsImportName) metrics, es := decodeMetricCalls(stableMetricsFunctionCalls, metricsImportName, variables) errors = append(errors, es...) @@ -173,3 +179,100 @@ func globalVariableDeclarations(tree *ast.File) map[string]ast.Expr { } return consts } + +func importedGlobalVariableDeclaration(localVariables map[string]ast.Expr, imports []*ast.ImportSpec) (map[string]ast.Expr, error) { + // to test the import will be rooted at GOPATH, so probably import will be something like /test/instrumentation/testpackage + + // env gets + //GOMODCACHE := os.Getenv("GOMODCACHE") + GOROOT := os.Getenv("GOROOT") + GOOS := os.Getenv("GOOS") + KUBE_ROOT := os.Getenv("KUBE_ROOT") + KUBE_URL_ROOT := "k8s.io/kubernetes" + KUBE_ROOT_INTERNAL := strings.Replace(KUBE_ROOT, KUBE_URL_ROOT, "", 1) // k8s/k8s refs need this stripped + + for _, im := range imports { + // get imported label + importAlias := "unknown" + if im.Name == nil { + pathSegments := strings.Split(im.Path.Value, "/") + importAlias = strings.Trim(pathSegments[len(pathSegments)-1], "\"") + } else { + importAlias = im.Name.String() + } + + // parse directory path + pathPrefix := "unknown" + if strings.Contains(im.Path.Value, KUBE_URL_ROOT) { + // search k/k local checkout + pathPrefix = KUBE_ROOT_INTERNAL + } else if strings.Contains(im.Path.Value, "k8s.io/") { + // search k/k/staging local checkout + pathPrefix = KUBE_ROOT + string(os.PathSeparator) + "staging" + string(os.PathSeparator) + "src" //KUBE_ROOT + string(os.PathSeparator) + "vendor" // + } else if strings.Contains(im.Path.Value, ".") { + // not stdlib -> prefix with GOMODCACHE + // pathPrefix = KUBE_ROOT + string(os.PathSeparator) + "vendor" + + // this requires implementing SIV, skip for now + continue + } else { + // stdlib -> prefix with GOROOT + pathPrefix = GOROOT + string(os.PathSeparator) + "src" + } // ToDo: support non go mod + importDirectory := pathPrefix + string(os.PathSeparator) + strings.Trim(im.Path.Value, "\"") + + files, err := ioutil.ReadDir(importDirectory) + if err != nil { + //fmt.Fprintf(os.Stderr, "failed to read import path directory %s with error %w, skipping\n", importDirectory, err) + continue + } + + for _, file := range files { + if file.IsDir() { + // do not grab constants from subpackages + continue + } + + if strings.Contains(file.Name(), "_test") { + // do not parse test files + continue + } + + if !strings.HasSuffix(file.Name(), ".go") { + // not a go code file, do not attempt to parse + continue + } + + fileset := token.NewFileSet() + tree, err := parser.ParseFile(fileset, importDirectory+string(os.PathSeparator)+file.Name(), nil, parser.AllErrors) + if err != nil { + return nil, fmt.Errorf("failed to parse path %s with error %w", im.Path.Value, err) + } + + // pass parsed filepath into globalVariableDeclarations + variables := globalVariableDeclarations(tree) + + // add returned map into supplied map and prepend import label to all keys + for k, v := range variables { + importK := importAlias + "." + k + if _, ok := localVariables[importK]; !ok { + localVariables[importK] = v + } else { + // cross-platform file that gets included in the correct OS build via OS build tags + // use whatever matches GOOS + + if strings.Contains(file.Name(), GOOS) { + // assume at some point we will find the correct OS version of this file + // if we are running on an OS that does not have an OS specific file for something then we will include a constant we shouldn't + // TODO: should we include/exclude based on the build tags? + localVariables[importK] = v + } + + } + } + } + + } + + return localVariables, nil +} diff --git a/test/instrumentation/stability-utils.sh b/test/instrumentation/stability-utils.sh index 4a80f629b11..f1dd406df42 100644 --- a/test/instrumentation/stability-utils.sh +++ b/test/instrumentation/stability-utils.sh @@ -58,7 +58,7 @@ reset=$(tput sgr0) kube::validate::stablemetrics() { stability_check_setup temp_file=$(mktemp) - doValidate=$(find_files_to_check | grep -E ".*.go" | grep -v ".*_test.go" | sort | xargs -L 200 go run "test/instrumentation/main.go" "test/instrumentation/decode_metric.go" "test/instrumentation/find_stable_metric.go" "test/instrumentation/error.go" "test/instrumentation/metric.go" -- 1>"${temp_file}") + doValidate=$(find_files_to_check | grep -E ".*.go" | grep -v ".*_test.go" | sort | KUBE_ROOT=${KUBE_ROOT} xargs -L 200 go run "test/instrumentation/main.go" "test/instrumentation/decode_metric.go" "test/instrumentation/find_stable_metric.go" "test/instrumentation/error.go" "test/instrumentation/metric.go" -- 1>"${temp_file}") if $doValidate; then echo -e "${green}Diffing test/instrumentation/testdata/stable-metrics-list.yaml\n${reset}"