k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/controlplane/transformation/transformation_test.go (about) 1 /* 2 Copyright 2017 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 transformation 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "fmt" 24 "os" 25 "path/filepath" 26 "strconv" 27 "strings" 28 "testing" 29 "time" 30 31 clientv3 "go.etcd.io/etcd/client/v3" 32 33 appsv1 "k8s.io/api/apps/v1" 34 batchv1 "k8s.io/api/batch/v1" 35 corev1 "k8s.io/api/core/v1" 36 "k8s.io/apimachinery/pkg/api/errors" 37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 39 "k8s.io/apimachinery/pkg/runtime/schema" 40 "k8s.io/apimachinery/pkg/util/wait" 41 apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1" 42 "k8s.io/apiserver/pkg/storage/storagebackend" 43 "k8s.io/apiserver/pkg/storage/value" 44 "k8s.io/client-go/dynamic" 45 "k8s.io/client-go/kubernetes" 46 "k8s.io/client-go/rest" 47 "k8s.io/component-base/metrics/legacyregistry" 48 "k8s.io/klog/v2" 49 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 50 "k8s.io/kubernetes/test/integration" 51 "k8s.io/kubernetes/test/integration/etcd" 52 "k8s.io/kubernetes/test/integration/framework" 53 "k8s.io/kubernetes/test/utils/ktesting" 54 "k8s.io/utils/pointer" 55 "sigs.k8s.io/yaml" 56 ) 57 58 const ( 59 secretKey = "api_key" 60 secretVal = "086a7ffc-0225-11e8-ba89-0ed5f89f718b" // Fake value for testing. 61 encryptionConfigFileName = "encryption.conf" 62 testNamespace = "secret-encryption-test" 63 testSecret = "test-secret" 64 testConfigmap = "test-configmap" 65 metricsPrefix = "apiserver_storage_" 66 configMapKey = "foo" 67 configMapVal = "bar" 68 69 // precomputed key and secret for use with AES CBC 70 // this looks exactly the same as the AES GCM secret but with a different value 71 oldAESCBCKey = "e0/+tts8FS254BZimFZWtUsOCOUDSkvzB72PyimMlkY=" 72 oldSecret = "azhzAAoMCgJ2MRIGU2VjcmV0En4KXwoLdGVzdC1zZWNyZXQSABoWc2VjcmV0LWVuY3J5cHRpb24tdGVzdCIAKiQ3MmRmZTVjNC0xNDU2LTQyMzktYjFlZC1hZGZmYTJmMWY3YmEyADgAQggI5Jy/7wUQAHoAEhMKB2FwaV9rZXkSCPCfpJfwn5C8GgZPcGFxdWUaACIA" 73 oldSecretVal = "\xf0\x9f\xa4\x97\xf0\x9f\x90\xbc" 74 ) 75 76 type unSealSecret func(ctx context.Context, cipherText []byte, dataCtx value.Context, config apiserverv1.ProviderConfiguration) ([]byte, error) 77 78 type transformTest struct { 79 ktesting.TContext 80 storageConfig *storagebackend.Config 81 configDir string 82 transformerConfig string 83 kubeAPIServer kubeapiservertesting.TestServer 84 restClient *kubernetes.Clientset 85 ns *corev1.Namespace 86 secret *corev1.Secret 87 } 88 89 func newTransformTest(tb testing.TB, transformerConfigYAML string, reload bool, configDir string, storageConfig *storagebackend.Config) (*transformTest, error) { 90 tCtx := ktesting.Init(tb) 91 if storageConfig == nil { 92 storageConfig = framework.SharedEtcd() 93 } 94 e := transformTest{ 95 TContext: tCtx, 96 transformerConfig: transformerConfigYAML, 97 storageConfig: storageConfig, 98 } 99 100 var err error 101 // create config dir with provided config yaml 102 if transformerConfigYAML != "" && configDir == "" { 103 if e.configDir, err = e.createEncryptionConfig(); err != nil { 104 e.cleanUp() 105 return nil, fmt.Errorf("error while creating KubeAPIServer encryption config: %w", err) 106 } 107 } else { 108 // configDir already exists. api-server must be restarting with existing encryption config 109 e.configDir = configDir 110 } 111 configFile := filepath.Join(e.configDir, encryptionConfigFileName) 112 _, err = os.ReadFile(configFile) 113 if err != nil { 114 e.cleanUp() 115 return nil, fmt.Errorf("failed to read config file: %w", err) 116 } 117 118 if e.kubeAPIServer, err = kubeapiservertesting.StartTestServer(tb, nil, e.getEncryptionOptions(reload), e.storageConfig); err != nil { 119 e.cleanUp() 120 return nil, fmt.Errorf("failed to start KubeAPI server: %w", err) 121 } 122 klog.Infof("Started kube-apiserver %v", e.kubeAPIServer.ClientConfig.Host) 123 124 if e.restClient, err = kubernetes.NewForConfig(e.kubeAPIServer.ClientConfig); err != nil { 125 e.cleanUp() 126 return nil, fmt.Errorf("error while creating rest client: %w", err) 127 } 128 129 if e.ns, err = e.createNamespace(testNamespace); err != nil { 130 e.cleanUp() 131 return nil, err 132 } 133 134 if transformerConfigYAML != "" && reload { 135 // when reloading is enabled, this healthz endpoint is always present 136 mustBeHealthy(tCtx, "/kms-providers", "ok", e.kubeAPIServer.ClientConfig) 137 mustNotHaveLivez(tCtx, "/kms-providers", "404 page not found", e.kubeAPIServer.ClientConfig) 138 139 // excluding healthz endpoints even if they do not exist should work 140 mustBeHealthy(tCtx, "", `warn: some health checks cannot be excluded: no matches for "kms-provider-0","kms-provider-1","kms-provider-2","kms-provider-3"`, 141 e.kubeAPIServer.ClientConfig, "kms-provider-0", "kms-provider-1", "kms-provider-2", "kms-provider-3") 142 } 143 144 return &e, nil 145 } 146 147 func (e *transformTest) cleanUp() { 148 if e.configDir != "" { 149 os.RemoveAll(e.configDir) 150 } 151 152 if e.kubeAPIServer.ClientConfig != nil { 153 e.shutdownAPIServer() 154 } 155 } 156 157 func (e *transformTest) shutdownAPIServer() { 158 e.kubeAPIServer.TearDownFn() 159 } 160 161 func (e *transformTest) runResource(l kubeapiservertesting.Logger, unSealSecretFunc unSealSecret, expectedEnvelopePrefix, 162 group, 163 version, 164 resource, 165 name, 166 namespaceName string, 167 ) { 168 response, err := e.readRawRecordFromETCD(e.getETCDPathForResource(e.storageConfig.Prefix, group, resource, name, namespaceName)) 169 if err != nil { 170 l.Errorf("failed to read from etcd: %v", err) 171 return 172 } 173 174 if !bytes.HasPrefix(response.Kvs[0].Value, []byte(expectedEnvelopePrefix)) { 175 l.Errorf("expected data to be prefixed with %s, but got %s", 176 expectedEnvelopePrefix, response.Kvs[0].Value) 177 return 178 } 179 180 // etcd path of the key is used as the authenticated context - need to pass it to decrypt 181 ctx := context.Background() 182 dataCtx := value.DefaultContext(e.getETCDPathForResource(e.storageConfig.Prefix, group, resource, name, namespaceName)) 183 // Envelope header precedes the cipherTextPayload 184 sealedData := response.Kvs[0].Value[len(expectedEnvelopePrefix):] 185 transformerConfig, err := e.getEncryptionConfig() 186 if err != nil { 187 l.Errorf("failed to parse transformer config: %v", err) 188 } 189 v, err := unSealSecretFunc(ctx, sealedData, dataCtx, *transformerConfig) 190 if err != nil { 191 l.Errorf("failed to unseal secret: %v", err) 192 return 193 } 194 if resource == "secrets" { 195 if !strings.Contains(string(v), secretVal) { 196 l.Errorf("expected %q after decryption, but got %q", secretVal, string(v)) 197 } 198 } else if resource == "configmaps" { 199 if !strings.Contains(string(v), configMapVal) { 200 l.Errorf("expected %q after decryption, but got %q", configMapVal, string(v)) 201 } 202 } else { 203 if !strings.Contains(string(v), name) { 204 l.Errorf("expected %q after decryption, but got %q", name, string(v)) 205 } 206 } 207 208 // Data should be un-enveloped on direct reads from Kube API Server. 209 if resource == "secrets" { 210 s, err := e.restClient.CoreV1().Secrets(testNamespace).Get(context.TODO(), name, metav1.GetOptions{}) 211 if err != nil { 212 l.Fatalf("failed to get Secret from %s, err: %v", testNamespace, err) 213 } 214 if secretVal != string(s.Data[secretKey]) { 215 l.Errorf("expected %s from KubeAPI, but got %s", secretVal, string(s.Data[secretKey])) 216 } 217 } else if resource == "configmaps" { 218 s, err := e.restClient.CoreV1().ConfigMaps(namespaceName).Get(context.TODO(), name, metav1.GetOptions{}) 219 if err != nil { 220 l.Fatalf("failed to get ConfigMap from %s, err: %v", namespaceName, err) 221 } 222 if configMapVal != string(s.Data[configMapKey]) { 223 l.Errorf("expected %s from KubeAPI, but got %s", configMapVal, string(s.Data[configMapKey])) 224 } 225 } else if resource == "pods" { 226 p, err := e.restClient.CoreV1().Pods(namespaceName).Get(context.TODO(), name, metav1.GetOptions{}) 227 if err != nil { 228 l.Fatalf("failed to get Pod from %s, err: %v", namespaceName, err) 229 } 230 if p.Name != name { 231 l.Errorf("expected %s from KubeAPI, but got %s", name, p.Name) 232 } 233 } else { 234 l.Logf("Get object with dynamic client") 235 fooResource := schema.GroupVersionResource{Group: group, Version: version, Resource: resource} 236 obj, err := dynamic.NewForConfigOrDie(e.kubeAPIServer.ClientConfig).Resource(fooResource).Namespace(namespaceName).Get(context.TODO(), name, metav1.GetOptions{}) 237 if err != nil { 238 l.Fatalf("Failed to get test instance: %v, name: %s", err, name) 239 } 240 if obj.GetObjectKind().GroupVersionKind().Group == group && obj.GroupVersionKind().Version == version && obj.GetKind() == resource && obj.GetNamespace() == namespaceName && obj.GetName() != name { 241 l.Errorf("expected %s from KubeAPI, but got %s", name, obj.GetName()) 242 } 243 } 244 } 245 246 func (e *transformTest) benchmark(b *testing.B) { 247 for i := 0; i < b.N; i++ { 248 _, err := e.createSecret(e.secret.Name+strconv.Itoa(i), e.ns.Name) 249 if err != nil { 250 b.Fatalf("failed to create a secret: %v", err) 251 } 252 } 253 } 254 255 func (e *transformTest) getETCDPathForResource(storagePrefix, group, resource, name, namespaceName string) string { 256 groupResource := resource 257 if group != "" { 258 groupResource = fmt.Sprintf("%s/%s", group, resource) 259 } 260 if namespaceName == "" { 261 return fmt.Sprintf("/%s/%s/%s", storagePrefix, groupResource, name) 262 } 263 return fmt.Sprintf("/%s/%s/%s/%s", storagePrefix, groupResource, namespaceName, name) 264 } 265 266 func (e *transformTest) getRawSecretFromETCD() ([]byte, error) { 267 secretETCDPath := e.getETCDPathForResource(e.storageConfig.Prefix, "", "secrets", e.secret.Name, e.secret.Namespace) 268 etcdResponse, err := e.readRawRecordFromETCD(secretETCDPath) 269 if err != nil { 270 return nil, fmt.Errorf("failed to read %s from etcd: %v", secretETCDPath, err) 271 } 272 return etcdResponse.Kvs[0].Value, nil 273 } 274 275 func (e *transformTest) getEncryptionOptions(reload bool) []string { 276 if e.transformerConfig != "" { 277 return []string{ 278 "--encryption-provider-config", filepath.Join(e.configDir, encryptionConfigFileName), 279 fmt.Sprintf("--encryption-provider-config-automatic-reload=%v", reload), 280 "--disable-admission-plugins", "ServiceAccount"} 281 } 282 283 return nil 284 } 285 286 func (e *transformTest) createEncryptionConfig() ( 287 filePathForEncryptionConfig string, 288 err error, 289 ) { 290 tempDir, err := os.MkdirTemp("", "secrets-encryption-test") 291 if err != nil { 292 return "", fmt.Errorf("failed to create temp directory: %v", err) 293 } 294 295 if err = os.WriteFile(filepath.Join(tempDir, encryptionConfigFileName), []byte(e.transformerConfig), 0644); err != nil { 296 os.RemoveAll(tempDir) 297 return tempDir, fmt.Errorf("error while writing encryption config: %v", err) 298 } 299 300 return tempDir, nil 301 } 302 303 func (e *transformTest) getEncryptionConfig() (*apiserverv1.ProviderConfiguration, error) { 304 var config apiserverv1.EncryptionConfiguration 305 err := yaml.Unmarshal([]byte(e.transformerConfig), &config) 306 if err != nil { 307 return nil, fmt.Errorf("failed to extract transformer key: %v", err) 308 } 309 310 return &config.Resources[0].Providers[0], nil 311 } 312 313 func (e *transformTest) createNamespace(name string) (*corev1.Namespace, error) { 314 ns := &corev1.Namespace{ 315 ObjectMeta: metav1.ObjectMeta{ 316 Name: name, 317 }, 318 } 319 320 if _, err := e.restClient.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}); err != nil { 321 if errors.IsAlreadyExists(err) { 322 existingNs, err := e.restClient.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{}) 323 if err != nil { 324 return nil, fmt.Errorf("unable to get testing namespace, err: [%v]", err) 325 } 326 return existingNs, nil 327 } 328 return nil, fmt.Errorf("unable to create testing namespace, err: [%v]", err) 329 } 330 331 return ns, nil 332 } 333 334 func (e *transformTest) createSecret(name, namespace string) (*corev1.Secret, error) { 335 secret := &corev1.Secret{ 336 ObjectMeta: metav1.ObjectMeta{ 337 Name: name, 338 Namespace: namespace, 339 }, 340 Data: map[string][]byte{ 341 secretKey: []byte(secretVal), 342 }, 343 } 344 if _, err := e.restClient.CoreV1().Secrets(secret.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil { 345 return nil, fmt.Errorf("error while writing secret: %v", err) 346 } 347 348 return secret, nil 349 } 350 351 func (e *transformTest) createConfigMap(name, namespace string) (*corev1.ConfigMap, error) { 352 cm := &corev1.ConfigMap{ 353 ObjectMeta: metav1.ObjectMeta{ 354 Name: name, 355 Namespace: namespace, 356 }, 357 Data: map[string]string{ 358 configMapKey: configMapVal, 359 }, 360 } 361 if _, err := e.restClient.CoreV1().ConfigMaps(cm.Namespace).Create(context.TODO(), cm, metav1.CreateOptions{}); err != nil { 362 return nil, fmt.Errorf("error while writing configmap: %v", err) 363 } 364 365 return cm, nil 366 } 367 368 // create jobs 369 func (e *transformTest) createJob(name, namespace string) (*batchv1.Job, error) { 370 job := &batchv1.Job{ 371 ObjectMeta: metav1.ObjectMeta{ 372 Name: name, 373 Namespace: namespace, 374 }, 375 Spec: batchv1.JobSpec{ 376 Template: corev1.PodTemplateSpec{ 377 Spec: corev1.PodSpec{ 378 Containers: []corev1.Container{ 379 { 380 Name: "test", 381 Image: "test", 382 }, 383 }, 384 RestartPolicy: corev1.RestartPolicyNever, 385 }, 386 }, 387 }, 388 } 389 if _, err := e.restClient.BatchV1().Jobs(job.Namespace).Create(context.TODO(), job, metav1.CreateOptions{}); err != nil { 390 return nil, fmt.Errorf("error while creating job: %v", err) 391 } 392 393 return job, nil 394 } 395 396 // create deployment 397 func (e *transformTest) createDeployment(name, namespace string) (*appsv1.Deployment, error) { 398 deployment := &appsv1.Deployment{ 399 ObjectMeta: metav1.ObjectMeta{ 400 Name: name, 401 Namespace: namespace, 402 }, 403 Spec: appsv1.DeploymentSpec{ 404 Replicas: pointer.Int32(2), 405 Selector: &metav1.LabelSelector{ 406 MatchLabels: map[string]string{ 407 "app": "nginx", 408 }, 409 }, 410 Template: corev1.PodTemplateSpec{ 411 ObjectMeta: metav1.ObjectMeta{ 412 Labels: map[string]string{ 413 "app": "nginx", 414 }, 415 }, 416 Spec: corev1.PodSpec{ 417 Containers: []corev1.Container{ 418 { 419 Name: "nginx", 420 Image: "nginx:1.17", 421 Ports: []corev1.ContainerPort{ 422 { 423 Name: "http", 424 Protocol: corev1.ProtocolTCP, 425 ContainerPort: 80, 426 }, 427 }, 428 }, 429 }, 430 }, 431 }, 432 }, 433 } 434 if _, err := e.restClient.AppsV1().Deployments(deployment.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}); err != nil { 435 return nil, fmt.Errorf("error while creating deployment: %v", err) 436 } 437 438 return deployment, nil 439 } 440 441 func gvr(group, version, resource string) schema.GroupVersionResource { 442 return schema.GroupVersionResource{Group: group, Version: version, Resource: resource} 443 } 444 445 func createResource(client dynamic.Interface, gvr schema.GroupVersionResource, ns string) (*unstructured.Unstructured, error) { 446 stubObj, err := getStubObj(gvr) 447 if err != nil { 448 return nil, err 449 } 450 return client.Resource(gvr).Namespace(ns).Create(context.TODO(), stubObj, metav1.CreateOptions{}) 451 } 452 453 func inplaceUpdateResource(client dynamic.Interface, gvr schema.GroupVersionResource, ns string, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { 454 return client.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, metav1.UpdateOptions{}) 455 } 456 457 func getStubObj(gvr schema.GroupVersionResource) (*unstructured.Unstructured, error) { 458 stub := "" 459 if data, ok := etcd.GetEtcdStorageDataForNamespace(testNamespace)[gvr]; ok { 460 stub = data.Stub 461 } 462 if len(stub) == 0 { 463 return nil, fmt.Errorf("no stub data for %#v", gvr) 464 } 465 466 stubObj := &unstructured.Unstructured{Object: map[string]interface{}{}} 467 if err := json.Unmarshal([]byte(stub), &stubObj.Object); err != nil { 468 return nil, fmt.Errorf("error unmarshaling stub for %#v: %v", gvr, err) 469 } 470 return stubObj, nil 471 } 472 473 func (e *transformTest) createPod(namespace string, dynamicInterface dynamic.Interface) (*unstructured.Unstructured, error) { 474 podGVR := gvr("", "v1", "pods") 475 pod, err := createResource(dynamicInterface, podGVR, namespace) 476 if err != nil { 477 return nil, fmt.Errorf("error while writing pod: %v", err) 478 } 479 return pod, nil 480 } 481 482 func (e *transformTest) deletePod(namespace string, dynamicInterface dynamic.Interface) error { 483 podGVR := gvr("", "v1", "pods") 484 stubObj, err := getStubObj(podGVR) 485 if err != nil { 486 return err 487 } 488 return dynamicInterface.Resource(podGVR).Namespace(namespace).Delete(context.TODO(), stubObj.GetName(), metav1.DeleteOptions{}) 489 } 490 491 func (e *transformTest) inplaceUpdatePod(namespace string, obj *unstructured.Unstructured, dynamicInterface dynamic.Interface) (*unstructured.Unstructured, error) { 492 podGVR := gvr("", "v1", "pods") 493 pod, err := inplaceUpdateResource(dynamicInterface, podGVR, namespace, obj) 494 if err != nil { 495 return nil, fmt.Errorf("error while writing pod: %v", err) 496 } 497 return pod, nil 498 } 499 500 func (e *transformTest) readRawRecordFromETCD(path string) (*clientv3.GetResponse, error) { 501 rawClient, etcdClient, err := integration.GetEtcdClients(e.kubeAPIServer.ServerOpts.Etcd.StorageConfig.Transport) 502 if err != nil { 503 return nil, fmt.Errorf("failed to create etcd client: %v", err) 504 } 505 // kvClient is a wrapper around rawClient and to avoid leaking goroutines we need to 506 // close the client (which we can do by closing rawClient). 507 defer rawClient.Close() 508 509 response, err := etcdClient.Get(context.Background(), path, clientv3.WithPrefix()) 510 if err != nil { 511 return nil, fmt.Errorf("failed to retrieve secret from etcd %v", err) 512 } 513 514 return response, nil 515 } 516 517 func (e *transformTest) writeRawRecordToETCD(path string, data []byte) (*clientv3.PutResponse, error) { 518 rawClient, etcdClient, err := integration.GetEtcdClients(e.kubeAPIServer.ServerOpts.Etcd.StorageConfig.Transport) 519 if err != nil { 520 return nil, fmt.Errorf("failed to create etcd client: %v", err) 521 } 522 // kvClient is a wrapper around rawClient and to avoid leaking goroutines we need to 523 // close the client (which we can do by closing rawClient). 524 defer rawClient.Close() 525 526 response, err := etcdClient.Put(context.Background(), path, string(data)) 527 if err != nil { 528 return nil, fmt.Errorf("failed to write secret to etcd %v", err) 529 } 530 531 return response, nil 532 } 533 534 func (e *transformTest) printMetrics() error { 535 e.Logf("Transformation Metrics:") 536 metrics, err := legacyregistry.DefaultGatherer.Gather() 537 if err != nil { 538 return fmt.Errorf("failed to gather metrics: %s", err) 539 } 540 541 for _, mf := range metrics { 542 if strings.HasPrefix(*mf.Name, metricsPrefix) { 543 e.Logf("%s", *mf.Name) 544 for _, metric := range mf.GetMetric() { 545 e.Logf("%v", metric) 546 } 547 } 548 } 549 550 return nil 551 } 552 553 func mustBeHealthy(t kubeapiservertesting.Logger, checkName, wantBodyContains string, clientConfig *rest.Config, excludes ...string) { 554 t.Helper() 555 var restErr error 556 pollErr := wait.PollUntilContextTimeout(context.TODO(), 1*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) { 557 body, ok, err := getHealthz(checkName, clientConfig, excludes...) 558 restErr = err 559 if err != nil { 560 return false, err 561 } 562 done := ok && strings.Contains(body, wantBodyContains) 563 if !done { 564 t.Logf("expected server check %q to be healthy with message %q but it is not: %s", checkName, wantBodyContains, body) 565 } 566 return done, nil 567 }) 568 569 if pollErr != nil { 570 t.Fatalf("failed to get the expected healthz status of OK for check: %s, error: %v, debug inner error: %v", checkName, pollErr, restErr) 571 } 572 } 573 574 func mustBeUnHealthy(t kubeapiservertesting.Logger, checkName, wantBodyContains string, clientConfig *rest.Config, excludes ...string) { 575 t.Helper() 576 var restErr error 577 pollErr := wait.PollUntilContextTimeout(context.TODO(), 1*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) { 578 body, ok, err := getHealthz(checkName, clientConfig, excludes...) 579 restErr = err 580 if err != nil { 581 return false, err 582 } 583 done := !ok && strings.Contains(body, wantBodyContains) 584 if !done { 585 t.Logf("expected server check %q to be unhealthy with message %q but it is not: %s", checkName, wantBodyContains, body) 586 } 587 return done, nil 588 }) 589 590 if pollErr != nil { 591 t.Fatalf("failed to get the expected healthz status of !OK for check: %s, error: %v, debug inner error: %v", checkName, pollErr, restErr) 592 } 593 } 594 595 func mustNotHaveLivez(t kubeapiservertesting.Logger, checkName, wantBodyContains string, clientConfig *rest.Config, excludes ...string) { 596 t.Helper() 597 var restErr error 598 pollErr := wait.PollUntilContextTimeout(context.TODO(), 1*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) { 599 body, ok, err := getLivez(checkName, clientConfig, excludes...) 600 restErr = err 601 if err != nil { 602 return false, err 603 } 604 done := !ok && strings.Contains(body, wantBodyContains) 605 if !done { 606 t.Logf("expected server check %q with message %q but it is not: %s", checkName, wantBodyContains, body) 607 } 608 return done, nil 609 }) 610 611 if pollErr != nil { 612 t.Fatalf("failed to get the expected livez status of !OK for check: %s, error: %v, debug inner error: %v", checkName, pollErr, restErr) 613 } 614 } 615 616 func getHealthz(checkName string, clientConfig *rest.Config, excludes ...string) (string, bool, error) { 617 client, err := kubernetes.NewForConfig(clientConfig) 618 if err != nil { 619 return "", false, fmt.Errorf("failed to create a client: %v", err) 620 } 621 622 req := client.CoreV1().RESTClient().Get().AbsPath(fmt.Sprintf("/healthz%v", checkName)).Param("verbose", "true") 623 for _, exclude := range excludes { 624 req.Param("exclude", exclude) 625 } 626 body, err := req.DoRaw(context.TODO()) // we can still have a response body during an error case 627 return string(body), err == nil, nil 628 } 629 630 func getLivez(checkName string, clientConfig *rest.Config, excludes ...string) (string, bool, error) { 631 client, err := kubernetes.NewForConfig(clientConfig) 632 if err != nil { 633 return "", false, fmt.Errorf("failed to create a client: %v", err) 634 } 635 636 req := client.CoreV1().RESTClient().Get().AbsPath(fmt.Sprintf("/livez%v", checkName)).Param("verbose", "true") 637 for _, exclude := range excludes { 638 req.Param("exclude", exclude) 639 } 640 body, err := req.DoRaw(context.TODO()) // we can still have a response body during an error case 641 return string(body), err == nil, nil 642 }