github.com/cilium/cilium@v1.16.2/pkg/k8s/apis/crdhelpers/register_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package crdhelpers
     5  
     6  import (
     7  	"context"
     8  	"regexp"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/require"
    13  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    14  	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    15  	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/version"
    18  	fakediscovery "k8s.io/client-go/discovery/fake"
    19  
    20  	k8sconst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
    21  	k8sversion "github.com/cilium/cilium/pkg/k8s/version"
    22  	"github.com/cilium/cilium/pkg/policy/api"
    23  	"github.com/cilium/cilium/pkg/versioncheck"
    24  )
    25  
    26  func getV1TestCRD() *apiextensionsv1.CustomResourceDefinition {
    27  	return &apiextensionsv1.CustomResourceDefinition{
    28  		ObjectMeta: metav1.ObjectMeta{
    29  			Name: "foo-v1",
    30  			Labels: map[string]string{
    31  				k8sconst.CustomResourceDefinitionSchemaVersionKey: k8sconst.CustomResourceDefinitionSchemaVersion,
    32  			},
    33  		},
    34  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
    35  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
    36  				{
    37  					Name:    "v2",
    38  					Served:  true,
    39  					Storage: true,
    40  					Schema: &apiextensionsv1.CustomResourceValidation{
    41  						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{},
    42  					},
    43  				},
    44  			},
    45  		},
    46  	}
    47  }
    48  
    49  func getV1beta1TestCRD() *apiextensionsv1beta1.CustomResourceDefinition {
    50  	return &apiextensionsv1beta1.CustomResourceDefinition{
    51  		ObjectMeta: metav1.ObjectMeta{
    52  			Name: "foo-v1beta1",
    53  			Labels: map[string]string{
    54  				k8sconst.CustomResourceDefinitionSchemaVersionKey: k8sconst.CustomResourceDefinitionSchemaVersion,
    55  			},
    56  		},
    57  		Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
    58  			Validation: &apiextensionsv1beta1.CustomResourceValidation{
    59  				OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{},
    60  			},
    61  		},
    62  	}
    63  }
    64  
    65  const labelKey = k8sconst.CustomResourceDefinitionSchemaVersionKey
    66  
    67  var minVersion = versioncheck.MustVersion(k8sconst.CustomResourceDefinitionSchemaVersion)
    68  
    69  func TestCreateUpdateCRD(t *testing.T) {
    70  	v1Support := &version.Info{
    71  		Major: "1",
    72  		Minor: "16",
    73  	}
    74  	v1beta1Support := &version.Info{
    75  		Major: "1",
    76  		Minor: "15",
    77  	}
    78  
    79  	tests := []struct {
    80  		name    string
    81  		test    func() error
    82  		wantErr bool
    83  	}{
    84  		{
    85  			name: "v1 crd installed with v1 apiserver",
    86  			test: func() error {
    87  				crd := getV1TestCRD()
    88  				client := fake.NewSimpleClientset()
    89  				require.Nil(t, k8sversion.Force(v1Support.Major+"."+v1Support.Minor))
    90  				return CreateUpdateCRD(client, crd, newFakePoller(), labelKey, minVersion)
    91  			},
    92  			wantErr: false,
    93  		},
    94  		{
    95  			name: "v1beta1 crd installed with v1beta1 apiserver",
    96  			test: func() error {
    97  				// createUpdateCRD works with v1 CRDs and converts to v1beta1 CRDs if needed.
    98  				crd := getV1TestCRD()
    99  				client := fake.NewSimpleClientset()
   100  				require.Nil(t, k8sversion.Force(v1beta1Support.Major+"."+v1beta1Support.Minor))
   101  				return CreateUpdateCRD(client, crd, newFakePoller(), labelKey, minVersion)
   102  			},
   103  			wantErr: false,
   104  		},
   105  		{
   106  			name: "v1beta1 crd installed with v1 apiserver; upgrade path",
   107  			test: func() error {
   108  				// This test will install a v1beta1 CRD to simulate the
   109  				// scenario where a user already has v1beta1 CRDs installed.
   110  
   111  				require.Nil(t, k8sversion.Force(v1Support.Major+"."+v1Support.Minor))
   112  
   113  				// Ensure same name as to-be installed CRD.
   114  				crd := getV1TestCRD()
   115  				oldCRD := getV1beta1TestCRD()
   116  				oldCRD.ObjectMeta.Name = crd.ObjectMeta.Name
   117  
   118  				var err error
   119  				client := fake.NewSimpleClientset()
   120  				client.Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion = v1Support
   121  				_, err = client.ApiextensionsV1beta1().CustomResourceDefinitions().Create(
   122  					context.TODO(),
   123  					oldCRD,
   124  					metav1.CreateOptions{},
   125  				)
   126  				require.NoError(t, err)
   127  
   128  				return CreateUpdateCRD(client, crd, newFakePoller(), labelKey, minVersion)
   129  			},
   130  			wantErr: false,
   131  		},
   132  		{
   133  			name: "v1 crd installed with v1beta1 apiserver; downgrade path",
   134  			test: func() error {
   135  				// This test will install a v1 CRD to simulate the scenario
   136  				// where a user already has v1 CRDs installed. This test covers
   137  				// that the apiserver will interoperate between the two
   138  				// versions (v1 & v1beta1).
   139  
   140  				require.Nil(t, k8sversion.Force(v1Support.Major+"."+v1Support.Minor))
   141  
   142  				// Ensure same name as to-be installed CRD.
   143  				crdToInstall := getV1beta1TestCRD()
   144  				oldCRD := getV1TestCRD()
   145  				oldCRD.ObjectMeta.Name = crdToInstall.ObjectMeta.Name
   146  
   147  				// Pre-install v1 CRD.
   148  				var err error
   149  				client := fake.NewSimpleClientset()
   150  				_, err = client.ApiextensionsV1().CustomResourceDefinitions().Create(
   151  					context.TODO(),
   152  					oldCRD,
   153  					metav1.CreateOptions{},
   154  				)
   155  				require.NoError(t, err)
   156  
   157  				// Revert back to v1beta1 apiserver.
   158  				client.Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion = v1beta1Support
   159  				require.Nil(t, k8sversion.Force(v1beta1Support.Major+"."+v1beta1Support.Minor))
   160  
   161  				// Retrieve v1 CRD here as that's what CreateUpdateCRD will be
   162  				// expecting, and change the name to match to-be installed CRD.
   163  				// This tests that CreateUpdateCRD will fallback on its v1beta1
   164  				// variant.
   165  				crd := getV1TestCRD()
   166  				crd.ObjectMeta.Name = crdToInstall.ObjectMeta.Name
   167  
   168  				return CreateUpdateCRD(client, crd, newFakePoller(), labelKey, minVersion)
   169  			},
   170  			wantErr: false,
   171  		},
   172  	}
   173  	for _, tt := range tests {
   174  		t.Log(tt.name)
   175  		err := tt.test()
   176  		require.Equal(t, tt.wantErr, err != nil)
   177  	}
   178  }
   179  
   180  func TestNeedsUpdateNoValidation(t *testing.T) {
   181  	v1CRD := getV1TestCRD()
   182  	v1CRD.Spec.Versions[0].Schema = nil
   183  	require.Equal(t, true, needsUpdateV1(v1CRD, labelKey, minVersion))
   184  }
   185  
   186  func TestNeedsUpdateNoLabels(t *testing.T) {
   187  	v1CRD := getV1TestCRD()
   188  	v1CRD.Labels = nil
   189  	require.Equal(t, true, needsUpdateV1(v1CRD, labelKey, minVersion))
   190  }
   191  
   192  func TestNeedsUpdateNoVersionLabel(t *testing.T) {
   193  	v1CRD := getV1TestCRD()
   194  	v1CRD.Labels = map[string]string{"test": "test"}
   195  	require.Equal(t, true, needsUpdateV1(v1CRD, labelKey, minVersion))
   196  }
   197  
   198  func TestNeedsUpdateOlderVersion(t *testing.T) {
   199  	v1CRD := getV1TestCRD()
   200  	v1CRD.Labels[k8sconst.CustomResourceDefinitionSchemaVersionKey] = "0.9"
   201  	require.Equal(t, true, needsUpdateV1(v1CRD, labelKey, minVersion))
   202  }
   203  
   204  func TestNeedsUpdateCorruptedVersion(t *testing.T) {
   205  	v1CRD := getV1TestCRD()
   206  	v1CRD.Labels[k8sconst.CustomResourceDefinitionSchemaVersionKey] = "totally-not-semver"
   207  	require.Equal(t, true, needsUpdateV1(v1CRD, labelKey, minVersion))
   208  }
   209  
   210  func TestFQDNNameRegex(t *testing.T) {
   211  	nameRegex := regexp.MustCompile(api.FQDNMatchNameRegexString)
   212  	patternRegex := regexp.MustCompile(api.FQDNMatchPatternRegexString)
   213  
   214  	badFqdns := []string{
   215  		"%%",
   216  		"",
   217  		"😀.com",
   218  	}
   219  
   220  	goodFqdns := []string{
   221  		"cilium.io",
   222  		"cilium.io.",
   223  		"www.xn--e28h.com",
   224  		"_tcp.cilium.io",
   225  		"foo._tcp.cilium.io",
   226  		"_http._tcp.cilium.io",
   227  	}
   228  
   229  	badFqdnPatterns := []string{
   230  		"%$*.*",
   231  		"",
   232  	}
   233  
   234  	goodFqdnPatterns := []string{
   235  		"*.cilium.io",
   236  		"*.cilium.io.*",
   237  		"*.cilium.io.*.",
   238  		"*.xn--e28h.com",
   239  		"*._tcp.cilium.io",
   240  		"*._tcp.*",
   241  		"_http._tcp.*",
   242  	}
   243  
   244  	for _, f := range badFqdns {
   245  		require.Equal(t, false, nameRegex.MatchString(f), f)
   246  		require.Equal(t, false, patternRegex.MatchString(f), f)
   247  	}
   248  
   249  	for _, f := range goodFqdns {
   250  		require.Equal(t, true, nameRegex.MatchString(f), f)
   251  		require.Equal(t, true, patternRegex.MatchString(f), f)
   252  	}
   253  
   254  	for _, f := range badFqdnPatterns {
   255  		require.Equal(t, false, nameRegex.MatchString(f), f)
   256  		require.Equal(t, false, patternRegex.MatchString(f), f)
   257  	}
   258  
   259  	for _, f := range goodFqdnPatterns {
   260  		require.Equal(t, false, nameRegex.MatchString(f), f)
   261  		require.Equal(t, true, patternRegex.MatchString(f), f)
   262  	}
   263  }
   264  
   265  func newFakePoller() fakePoller { return fakePoller{} }
   266  
   267  type fakePoller struct{}
   268  
   269  func (m fakePoller) Poll(
   270  	interval, duration time.Duration,
   271  	conditionFn func() (bool, error),
   272  ) error {
   273  	return nil
   274  }