diff --git a/pkg/util/codeinspector/codeinspector.go b/pkg/util/codeinspector/codeinspector.go new file mode 100644 index 00000000000..6ec21be7161 --- /dev/null +++ b/pkg/util/codeinspector/codeinspector.go @@ -0,0 +1,80 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package codeinspector + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "strings" + "unicode" +) + +// GetPublicFunctions lists all public functions (not methods) from a golang source file. +func GetPublicFunctions(filePath string) ([]string, error) { + var functionNames []string + + // Create the AST by parsing src. + fset := token.NewFileSet() // positions are relative to fset + f, err := parser.ParseFile(fset, filePath, nil, 0) + if err != nil { + return nil, fmt.Errorf("failed parse file to list functions: %v", err) + } + + // Inspect the AST and print all identifiers and literals. + ast.Inspect(f, func(n ast.Node) bool { + var s string + switch x := n.(type) { + case *ast.FuncDecl: + s = x.Name.Name + // It's a function (not method), and is public, record it. + if x.Recv == nil && isPublic(s) { + functionNames = append(functionNames, s) + } + } + return true + }) + + return functionNames, nil +} + +// isPublic checks if a given string is a public function name. +func isPublic(myString string) bool { + a := []rune(myString) + a[0] = unicode.ToUpper(a[0]) + return myString == string(a) +} + +// GetSourceCodeFiles lists golang source code files from directory, excluding sub-directory and tests files. +func GetSourceCodeFiles(dir string) ([]string, error) { + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + var filenames []string + + for _, file := range files { + if strings.HasSuffix(file.Name(), ".go") && !strings.HasSuffix(file.Name(), "_test.go") { + filenames = append(filenames, file.Name()) + } + } + + return filenames, nil +} diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates.go b/plugin/pkg/scheduler/algorithm/predicates/predicates.go index cbf99341837..8ded8c303b0 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates.go +++ b/plugin/pkg/scheduler/algorithm/predicates/predicates.go @@ -469,9 +469,9 @@ func NewSelectorMatchPredicate(info NodeInfo) algorithm.FitPredicate { return selector.PodSelectorMatches } -// NodeMatchesNodeSelectorTerms checks if a node's labels satisfy a list of node selector terms, +// nodeMatchesNodeSelectorTerms checks if a node's labels satisfy a list of node selector terms, // terms are ORed, and an emtpy a list of terms will match nothing. -func NodeMatchesNodeSelectorTerms(node *api.Node, nodeSelectorTerms []api.NodeSelectorTerm) bool { +func nodeMatchesNodeSelectorTerms(node *api.Node, nodeSelectorTerms []api.NodeSelectorTerm) bool { for _, req := range nodeSelectorTerms { nodeSelector, err := api.NodeSelectorRequirementsAsSelector(req.MatchExpressions) if err != nil { @@ -524,14 +524,14 @@ func PodMatchesNodeLabels(pod *api.Pod, node *api.Node) bool { // if nodeAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { // nodeSelectorTerms := nodeAffinity.RequiredDuringSchedulingRequiredDuringExecution.NodeSelectorTerms // glog.V(10).Infof("Match for RequiredDuringSchedulingRequiredDuringExecution node selector terms %+v", nodeSelectorTerms) - // nodeAffinityMatches = NodeMatchesNodeSelectorTerms(node, nodeSelectorTerms) + // nodeAffinityMatches = nodeMatchesNodeSelectorTerms(node, nodeSelectorTerms) // } // Match node selector for requiredDuringSchedulingIgnoredDuringExecution. if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { nodeSelectorTerms := nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms glog.V(10).Infof("Match for RequiredDuringSchedulingIgnoredDuringExecution node selector terms %+v", nodeSelectorTerms) - nodeAffinityMatches = nodeAffinityMatches && NodeMatchesNodeSelectorTerms(node, nodeSelectorTerms) + nodeAffinityMatches = nodeAffinityMatches && nodeMatchesNodeSelectorTerms(node, nodeSelectorTerms) } } diff --git a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go b/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go index 4126eafe998..be4a8e87f36 100644 --- a/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -18,11 +18,13 @@ package predicates import ( "fmt" + "os/exec" "reflect" "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/util/codeinspector" "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" ) @@ -1408,3 +1410,48 @@ func TestEBSVolumeCountConflicts(t *testing.T) { } } } + +func TestPredicatesRegistered(t *testing.T) { + var functionNames []string + + // Files and directories which predicates may be referenced + targetFiles := []string{ + "./../../algorithmprovider/defaults/defaults.go", // Default algorithm + "./../../factory/plugins.go", // Registered in init() + "./../../../../../pkg/", // kubernetes/pkg, often used by kubelet or controller + } + + // List all golang source files under ./predicates/, excluding test files and sub-directories. + files, err := codeinspector.GetSourceCodeFiles(".") + + if err != nil { + t.Errorf("unexpected error: %v when listing files in current directory", err) + } + + // Get all public predicates in files. + for _, filePath := range files { + functions, err := codeinspector.GetPublicFunctions(filePath) + if err == nil { + functionNames = append(functionNames, functions...) + } else { + t.Errorf("unexpected error when parsing %s", filePath) + } + } + + // Check if all public predicates are referenced in target files. + for _, functionName := range functionNames { + args := []string{"-rl", functionName} + args = append(args, targetFiles...) + + err := exec.Command("grep", args...).Run() + if err != nil { + switch err.Error() { + case "exit status 2": + t.Errorf("unexpected error when checking %s", functionName) + case "exit status 1": + t.Errorf("predicate %s is implemented as public but seems not registered or used in any other place", + functionName) + } + } + } +} diff --git a/plugin/pkg/scheduler/algorithm/priorities/priorities_test.go b/plugin/pkg/scheduler/algorithm/priorities/priorities_test.go index df623e946ec..b3d8c320521 100644 --- a/plugin/pkg/scheduler/algorithm/priorities/priorities_test.go +++ b/plugin/pkg/scheduler/algorithm/priorities/priorities_test.go @@ -17,6 +17,7 @@ limitations under the License. package priorities import ( + "os/exec" "reflect" "sort" "strconv" @@ -25,6 +26,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util/codeinspector" "k8s.io/kubernetes/plugin/pkg/scheduler" "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" priorityutil "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/priorities/util" @@ -883,3 +885,47 @@ func makeImageNode(node string, status api.NodeStatus) api.Node { Status: status, } } + +func TestPrioritiesRegistered(t *testing.T) { + var functionNames []string + + // Files and directories which priorities may be referenced + targetFiles := []string{ + "./../../algorithmprovider/defaults/defaults.go", // Default algorithm + "./../../factory/plugins.go", // Registered in init() + } + + // List all golang source files under ./priorities/, excluding test files and sub-directories. + files, err := codeinspector.GetSourceCodeFiles(".") + + if err != nil { + t.Errorf("unexpected error: %v when listing files in current directory", err) + } + + // Get all public priorities in files. + for _, filePath := range files { + functions, err := codeinspector.GetPublicFunctions(filePath) + if err == nil { + functionNames = append(functionNames, functions...) + } else { + t.Errorf("unexpected error when parsing %s", filePath) + } + } + + // Check if all public priorities are referenced in target files. + for _, functionName := range functionNames { + args := []string{"-rl", functionName} + args = append(args, targetFiles...) + + err := exec.Command("grep", args...).Run() + if err != nil { + switch err.Error() { + case "exit status 2": + t.Errorf("unexpected error when checking %s", functionName) + case "exit status 1": + t.Errorf("priority %s is implemented as public but seems not registered or used in any other place", + functionName) + } + } + } +}