...

Source file src/k8s.io/component-base/config/testing/apigroup.go

Documentation: k8s.io/component-base/config/testing

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package testing
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"regexp"
    23  	"strings"
    24  
    25  	apinamingtest "k8s.io/apimachinery/pkg/api/apitesting/naming"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/apimachinery/pkg/util/errors"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  )
    31  
    32  // APIVersionRegexp is the regular expression that matches with valid apiversion
    33  var APIVersionRegexp = regexp.MustCompile(`^v\d+((alpha|beta){1}\d+)?$`)
    34  
    35  // ComponentConfigPackage is used in APIGroup Testing
    36  type ComponentConfigPackage struct {
    37  	ComponentName               string
    38  	GroupName                   string
    39  	SchemeGroupVersion          schema.GroupVersion
    40  	AddToScheme                 func(*runtime.Scheme) error
    41  	SkipTests                   sets.String
    42  	AllowedTags                 map[reflect.Type]bool
    43  	AllowedNonstandardJSONNames map[reflect.Type]string
    44  }
    45  
    46  type testingFunc func(*runtime.Scheme, *ComponentConfigPackage) error
    47  
    48  const (
    49  	verifyTagNaming                 = "verifyTagNaming"
    50  	verifyGroupNameSuffix           = "verifyGroupNameSuffix"
    51  	verifyGroupNameMatch            = "verifyGroupNameMatch"
    52  	verifyCorrectGroupName          = "verifyCorrectGroupName"
    53  	verifyComponentConfigKindExists = "verifyComponentConfigKindExists"
    54  	verifyExternalAPIVersion        = "verifyExternalAPIVersion"
    55  	verifyInternalAPIVersion        = "verifyInternalAPIVersion"
    56  )
    57  
    58  var testingFuncs = map[string]testingFunc{
    59  	verifyTagNaming:        verifyTagNamingFunc,
    60  	verifyGroupNameSuffix:  verifyGroupNameSuffixFunc,
    61  	verifyGroupNameMatch:   verifyGroupNameMatchFunc,
    62  	verifyCorrectGroupName: verifyCorrectGroupNameFunc,
    63  }
    64  
    65  // VerifyExternalTypePackage tests if external component config package is defined correctly
    66  // Test tag naming (json name should match Go name)
    67  // Test that GroupName has the k8s.io suffix
    68  // Test that GroupName == SchemeGroupVersion.GroupName
    69  // Test that the API version follows the right pattern and isn't internal
    70  // Test that addKnownTypes and AddToScheme registers at least one type and doesn't error
    71  // Test that the GroupName is named correctly (based on ComponentName), and there is a {Component}Configuration kind in the scheme
    72  func VerifyExternalTypePackage(pkginfo *ComponentConfigPackage) error {
    73  	scheme, err := setup(pkginfo)
    74  	if err != nil {
    75  		return fmt.Errorf("test setup error: %v", err)
    76  	}
    77  	extraFns := map[string]testingFunc{
    78  		verifyExternalAPIVersion: verifyExternalAPIVersionFunc,
    79  	}
    80  	return runFuncs(scheme, pkginfo, extraFns)
    81  }
    82  
    83  // VerifyInternalTypePackage tests if internal component config package is defined correctly
    84  // Test tag naming (no tags allowed)
    85  // Test that GroupName has the k8s.io suffix
    86  // Test that GroupName == SchemeGroupVersion.GroupName
    87  // API version should be internal
    88  // Test that addKnownTypes and AddToScheme registers at least one type and doesn't error
    89  // Test that the GroupName is named correctly (based on ComponentName), and there is a {Component}Configuration kind in the scheme
    90  func VerifyInternalTypePackage(pkginfo *ComponentConfigPackage) error {
    91  	scheme, err := setup(pkginfo)
    92  	if err != nil {
    93  		return fmt.Errorf("test setup error: %v", err)
    94  	}
    95  	extraFns := map[string]testingFunc{
    96  		verifyInternalAPIVersion:        verifyInternalAPIVersionFunc,
    97  		verifyComponentConfigKindExists: verifyComponentConfigKindExistsFunc,
    98  	}
    99  	return runFuncs(scheme, pkginfo, extraFns)
   100  }
   101  
   102  func setup(pkginfo *ComponentConfigPackage) (*runtime.Scheme, error) {
   103  	if len(pkginfo.ComponentName) == 0 ||
   104  		len(pkginfo.GroupName) == 0 ||
   105  		pkginfo.SchemeGroupVersion.Empty() ||
   106  		pkginfo.AddToScheme == nil {
   107  		return nil, fmt.Errorf("invalid argument: not all parameters were passed correctly to the function")
   108  	}
   109  
   110  	scheme := runtime.NewScheme()
   111  	if err := pkginfo.AddToScheme(scheme); err != nil {
   112  		return nil, fmt.Errorf("AddToScheme must not return an error: %v", err)
   113  	}
   114  	if len(scheme.AllKnownTypes()) == 0 {
   115  		return nil, fmt.Errorf("AddToScheme doesn't register any type")
   116  	}
   117  	return scheme, nil
   118  }
   119  
   120  func runFuncs(scheme *runtime.Scheme, pkginfo *ComponentConfigPackage, extraFns map[string]testingFunc) error {
   121  	verifyFns := []testingFunc{}
   122  	for name, fn := range testingFuncs {
   123  		if pkginfo.SkipTests.Has(name) {
   124  			continue
   125  		}
   126  		verifyFns = append(verifyFns, fn)
   127  	}
   128  	for name, fn := range extraFns {
   129  		if pkginfo.SkipTests.Has(name) {
   130  			continue
   131  		}
   132  		verifyFns = append(verifyFns, fn)
   133  	}
   134  	errs := []error{}
   135  	for _, fn := range verifyFns {
   136  		if err := fn(scheme, pkginfo); err != nil {
   137  			errs = append(errs, err)
   138  		}
   139  	}
   140  	return errors.NewAggregate(errs)
   141  }
   142  
   143  func verifyTagNamingFunc(scheme *runtime.Scheme, pkginfo *ComponentConfigPackage) error {
   144  	return apinamingtest.VerifyTagNaming(scheme, pkginfo.AllowedTags, pkginfo.AllowedNonstandardJSONNames)
   145  }
   146  
   147  func verifyGroupNameSuffixFunc(scheme *runtime.Scheme, _ *ComponentConfigPackage) error {
   148  	return apinamingtest.VerifyGroupNames(scheme, sets.NewString())
   149  }
   150  
   151  func verifyGroupNameMatchFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error {
   152  	if pkginfo.GroupName != pkginfo.SchemeGroupVersion.Group {
   153  		return fmt.Errorf("GroupName must be equal to SchemeGroupVersion.Group, GroupName: %v,SchemeGroupVersion.Group: %v",
   154  			pkginfo.GroupName, pkginfo.SchemeGroupVersion.Group)
   155  	}
   156  	return nil
   157  }
   158  
   159  func verifyCorrectGroupNameFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error {
   160  	desiredGroupName := fmt.Sprintf("%s.config.k8s.io", lowercaseWithoutDashes(pkginfo.ComponentName))
   161  	if pkginfo.SchemeGroupVersion.Group != desiredGroupName {
   162  		return fmt.Errorf("got GroupName %q, want %q", pkginfo.SchemeGroupVersion.Group, desiredGroupName)
   163  
   164  	}
   165  	return nil
   166  }
   167  
   168  func verifyComponentConfigKindExistsFunc(scheme *runtime.Scheme, pkginfo *ComponentConfigPackage) error {
   169  	expectedKind := fmt.Sprintf("%sConfiguration", dashesToCapitalCase(pkginfo.ComponentName))
   170  	expectedGVK := pkginfo.SchemeGroupVersion.WithKind(expectedKind)
   171  	if !scheme.Recognizes(expectedGVK) {
   172  		registeredKinds := sets.NewString()
   173  		for gvk := range scheme.AllKnownTypes() {
   174  			registeredKinds.Insert(gvk.Kind)
   175  		}
   176  		return fmt.Errorf("Kind %s not registered in the scheme, registered kinds are %v", expectedKind, registeredKinds.List())
   177  	}
   178  	return nil
   179  }
   180  
   181  func verifyExternalAPIVersionFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error {
   182  	if !APIVersionRegexp.MatchString(pkginfo.SchemeGroupVersion.Version) {
   183  		return fmt.Errorf("invalid API version %q, must match %q", pkginfo.SchemeGroupVersion.Version, APIVersionRegexp.String())
   184  	}
   185  	return nil
   186  }
   187  
   188  func verifyInternalAPIVersionFunc(_ *runtime.Scheme, pkginfo *ComponentConfigPackage) error {
   189  	if pkginfo.SchemeGroupVersion.Version != runtime.APIVersionInternal {
   190  		return fmt.Errorf("internal API version must be %q, got %q",
   191  			runtime.APIVersionInternal, pkginfo.SchemeGroupVersion.Version)
   192  	}
   193  	return nil
   194  }
   195  
   196  func lowercaseWithoutDashes(str string) string {
   197  	return strings.Replace(strings.ToLower(str), "-", "", -1)
   198  }
   199  
   200  func dashesToCapitalCase(str string) string {
   201  	segments := strings.Split(str, "-")
   202  	result := ""
   203  	for _, segment := range segments {
   204  		result += strings.Title(segment)
   205  	}
   206  	return result
   207  }
   208  

View as plain text