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 }