github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/clusterapi/capi/helpers.go (about) 1 // Copyright (c) 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package capi 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "github.com/pkg/errors" 11 "github.com/verrazzano/verrazzano/pkg/k8s/resource" 12 "github.com/verrazzano/verrazzano/pkg/k8sutil" 13 "github.com/verrazzano/verrazzano/tests/e2e/backup/helpers" 14 "go.uber.org/zap" 15 apierrors "k8s.io/apimachinery/pkg/api/errors" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 18 "k8s.io/apimachinery/pkg/runtime/schema" 19 "k8s.io/apimachinery/pkg/types" 20 "k8s.io/client-go/dynamic" 21 "k8s.io/client-go/kubernetes" 22 "os" 23 "path/filepath" 24 clusterapi "sigs.k8s.io/cluster-api/cmd/clusterctl/client" 25 "strings" 26 "text/tabwriter" 27 "time" 28 ) 29 30 const ( 31 nodeLabel = "node-role.kubernetes.io/node" 32 controlPlaneLabel = "node-role.kubernetes.io/control-plane" 33 34 // env var names 35 CAPINodeSSHKey = "OCI_SSH_KEY" 36 OCICredsKey = "OCI_CREDENTIALS_KEY" 37 OCIPrivateCredsKeyBase64 = "OCI_CREDENTIALS_KEY_B64" 38 OCITenancyIDKeyBase64 = "OCI_TENANCY_ID_B64" 39 OCICredsFingerprintKeyBase64 = "OCI_CREDENTIALS_FINGERPRINT_B64" 40 OCIUserIDKeyBase64 = "OCI_USER_ID_B64" 41 OCIRegionKeyBase64 = "OCI_REGION_B64" 42 OCIImageIDKey = "OCI_IMAGE_ID" 43 OCIVCNKey = "OCI_VCN_ID" 44 OCISubnetKey = "OCI_SUBNET_ID" 45 ocneServiceLbName = "ocne-service-lb" 46 ) 47 48 var capiInitFunc = clusterapi.New 49 50 // PrintYamlOutput is used to print yaml templates to stdout or a file 51 func (c CAPITestImpl) PrintYamlOutput(printer clusterapi.YamlPrinter, outputFile string) error { 52 yaml, err := printer.Yaml() 53 if err != nil { 54 return err 55 } 56 yaml = append(yaml, '\n') 57 outputFile = strings.TrimSpace(outputFile) 58 if outputFile == "" || outputFile == "-" { 59 if _, err := os.Stdout.Write(yaml); err != nil { 60 return errors.Wrap(err, "failed to write yaml to Stdout") 61 } 62 return nil 63 } 64 outputFile = filepath.Clean(outputFile) 65 if err := os.WriteFile(outputFile, yaml, 0600); err != nil { 66 return errors.Wrap(err, "failed to write to destination file") 67 } 68 return nil 69 } 70 71 // ClusterTemplateGenerate used for cluster template generation 72 func (c CAPITestImpl) ClusterTemplateGenerate(clusterName, templatePath string, log *zap.SugaredLogger) (string, error) { 73 log.Infof("Generate called for clustername '%s'...", clusterName) 74 capiClient, err := capiInitFunc("") 75 if err != nil { 76 return "", err 77 } 78 kubeconfigPath, err := k8sutil.GetKubeConfigLocation() 79 if err != nil { 80 log.Errorf("Unable to fetch kubeconfig url due to %v", zap.Error(err)) 81 return "", err 82 } 83 84 templateOptions := clusterapi.GetClusterTemplateOptions{ 85 Kubeconfig: clusterapi.Kubeconfig{ 86 Path: kubeconfigPath, 87 Context: ""}, 88 URLSource: &clusterapi.URLSourceOptions{ 89 URL: templatePath, 90 }, 91 ClusterName: clusterName, 92 TargetNamespace: OCNENamespace, 93 } 94 95 template, err := capiClient.GetClusterTemplate(templateOptions) 96 if err != nil { 97 log.Errorf("template '%s' generation error = %v", templatePath, zap.Error(err)) 98 return "", err 99 } 100 101 tmpFile, err := os.CreateTemp(os.TempDir(), clusterName) 102 if err != nil { 103 return "", fmt.Errorf("Failed to create temporary file: %v", err) 104 } 105 106 if err := c.PrintYamlOutput(template, tmpFile.Name()); err != nil { 107 return "", err 108 } 109 return tmpFile.Name(), nil 110 } 111 112 // GetUnstructuredData common utility to fetch unstructured data 113 func (c CAPITestImpl) GetUnstructuredData(group, version, resource, resourceName, nameSpaceName string, log *zap.SugaredLogger) (*unstructured.Unstructured, error) { 114 var dataFetched *unstructured.Unstructured 115 var err error 116 config, err := k8sutil.GetKubeConfig() 117 if err != nil { 118 log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err)) 119 return nil, err 120 } 121 dclient, err := dynamic.NewForConfig(config) 122 if err != nil { 123 log.Errorf("Unable to create dynamic client %v", zap.Error(err)) 124 return nil, err 125 } 126 127 gvr := schema.GroupVersionResource{ 128 Group: group, 129 Version: version, 130 Resource: resource, 131 } 132 133 if nameSpaceName != "" { 134 log.Infof("fetching '%s' '%s' in namespace '%s'", resource, resourceName, nameSpaceName) 135 dataFetched, err = dclient.Resource(gvr).Namespace(nameSpaceName).Get(context.TODO(), resourceName, metav1.GetOptions{}) 136 } else { 137 log.Infof("fetching '%s' '%s'", resource, resourceName) 138 dataFetched, err = dclient.Resource(gvr).Get(context.TODO(), resourceName, metav1.GetOptions{}) 139 } 140 if err != nil { 141 if apierrors.IsNotFound(err) { 142 log.Errorf("resource %s %s not found", resource, resourceName) 143 return nil, nil 144 } 145 log.Errorf("Unable to fetch %s %s due to '%v'", resource, resourceName, zap.Error(err)) 146 return nil, err 147 } 148 return dataFetched, nil 149 } 150 151 // GetUnstructuredData common utility to fetch list of unstructured data 152 func (c CAPITestImpl) GetUnstructuredDataList(group, version, resource, nameSpaceName string, log *zap.SugaredLogger) (*unstructured.UnstructuredList, error) { 153 config, err := k8sutil.GetKubeConfig() 154 if err != nil { 155 log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err)) 156 return nil, err 157 } 158 dclient, err := dynamic.NewForConfig(config) 159 if err != nil { 160 log.Errorf("Unable to create dynamic client %v", zap.Error(err)) 161 return nil, err 162 } 163 164 log.Infof("Fetching resource %s", resource) 165 gvr := schema.GroupVersionResource{ 166 Group: group, 167 Version: version, 168 Resource: resource, 169 } 170 171 dataFetched, err := dclient.Resource(gvr).Namespace(nameSpaceName).List(context.TODO(), metav1.ListOptions{}) 172 if err != nil { 173 log.Errorf("Unable to fetch resource %s due to '%v'", resource, zap.Error(err)) 174 return nil, err 175 } 176 177 return dataFetched, nil 178 } 179 180 // GetCluster is used to fetch capi cluster info given a cluster name and namespace, if it exists 181 func (c CAPITestImpl) GetCluster(namespace, clusterName string, log *zap.SugaredLogger) (*Cluster, error) { 182 var capiCluster Cluster 183 clusterFetched, err := c.GetUnstructuredData("cluster.x-k8s.io", "v1beta1", "clusters", clusterName, namespace, log) 184 if err != nil { 185 log.Errorf("Unable to fetch CAPI cluster '%s' due to '%v'", clusterName, zap.Error(err)) 186 return nil, err 187 } 188 189 if clusterFetched == nil { 190 log.Infof("No CAPI clusters with name '%s' in namespace '%s' was detected", clusterName, namespace) 191 return &capiCluster, nil 192 } 193 194 bdata, err := json.Marshal(clusterFetched) 195 if err != nil { 196 log.Errorf("Json marshalling error %v", zap.Error(err)) 197 return nil, err 198 } 199 err = json.Unmarshal(bdata, &capiCluster) 200 if err != nil { 201 log.Errorf("Json unmarshall error %v", zap.Error(err)) 202 return nil, err 203 } 204 205 return &capiCluster, nil 206 } 207 208 // GetOCNEControlPlane is used to fetch OCNE control plane info given a control plane name and namespace, if it exists 209 func (c CAPITestImpl) GetOCNEControlPlane(namespace string, log *zap.SugaredLogger) (*OCNEControlPlane, error) { 210 ocnecpesFetched, err := c.GetUnstructuredDataList("controlplane.cluster.x-k8s.io", "v1alpha1", "ocnecontrolplanes", namespace, log) 211 if err != nil { 212 log.Errorf("Unable to fetch machines due to '%v'", zap.Error(err)) 213 return nil, err 214 } 215 216 if ocnecpesFetched == nil { 217 log.Infof("No OCNE control plane in namespace '%s' was detected", namespace) 218 } 219 220 if len(ocnecpesFetched.Items) > 1 { 221 log.Errorf("More than one OCNE control plane in namespace '%s' was detected !!", namespace) 222 } 223 224 var ocneControlPlane OCNEControlPlane 225 var bdata []byte 226 for _, ocnecp := range ocnecpesFetched.Items { 227 bdata, err = json.Marshal(ocnecp.Object) 228 if err != nil { 229 log.Errorf("Json marshalling error %v", zap.Error(err)) 230 return nil, err 231 } 232 err = json.Unmarshal(bdata, &ocneControlPlane) 233 if err != nil { 234 log.Errorf("Json unmarshall error %v", zap.Error(err)) 235 return nil, err 236 } 237 } 238 os.Setenv("OCNE_CONTROL_PLANE_NAME", ocneControlPlane.Metadata.Name) 239 os.Setenv("OCI_MACHINE_TEMPLATE_NAME", ocneControlPlane.Spec.MachineTemplate.InfrastructureRef.Name) 240 return &ocneControlPlane, nil 241 } 242 243 // GetCapiClusterKubeConfig returns the content of the kubeconfig file of an OCNE cluster if it exists. 244 func (c CAPITestImpl) GetCapiClusterKubeConfig(clusterName string, log *zap.SugaredLogger) ([]byte, error) { 245 clientset, err := k8sutil.GetKubernetesClientset() 246 if err != nil { 247 log.Errorf("Failed to get clientset with error: %v", err) 248 return nil, err 249 } 250 251 secret, err := clientset.CoreV1().Secrets(clusterName).Get(context.TODO(), fmt.Sprintf("%s-kubeconfig", clusterName), metav1.GetOptions{}) 252 if err != nil { 253 log.Infof("Error fetching secret ", zap.Error(err)) 254 return nil, err 255 } 256 257 return secret.Data["value"], nil 258 } 259 260 // GetCapiClusterK8sClient returns the K8s client of an OCNE cluster if it exists. 261 func (c CAPITestImpl) GetCapiClusterK8sClient(clusterName string, log *zap.SugaredLogger) (client *kubernetes.Clientset, err error) { 262 capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log) 263 if err != nil { 264 return nil, err 265 } 266 tmpFile, err := os.CreateTemp(os.TempDir(), clusterName) 267 if err != nil { 268 return nil, errors.Wrap(err, "Failed to create temporary file") 269 } 270 271 if err := os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil { 272 return nil, errors.Wrap(err, "failed to write to destination file") 273 } 274 275 k8sRestConfig, err := k8sutil.GetKubeConfigGivenPathAndContext(tmpFile.Name(), fmt.Sprintf("%s-admin@%s", clusterName, clusterName)) 276 if err != nil { 277 return nil, errors.Wrap(err, "Failed to get k8s rest config") 278 } 279 280 return k8sutil.GetKubernetesClientsetWithConfig(k8sRestConfig) 281 } 282 283 // TriggerCapiClusterCreation starts the OCNE workload cluster creation by applying the template YAML 284 func (c CAPITestImpl) TriggerCapiClusterCreation(clusterName, templateName string, log *zap.SugaredLogger) error { 285 tmpFilePath, err := c.ClusterTemplateGenerate(clusterName, templateName, log) 286 if err != nil { 287 log.Errorf("unable to generate template for cluster : %v", zap.Error(err)) 288 return err 289 } 290 defer os.RemoveAll(tmpFilePath) 291 clusterTemplateData, err := os.ReadFile(tmpFilePath) 292 if err != nil { 293 return nil 294 } 295 err = resource.CreateOrUpdateResourceFromBytes(clusterTemplateData, log) 296 if err != nil { 297 log.Errorf("Error creating cluster from template ", zap.Error(err)) 298 return err 299 } 300 log.Infof("Wait for 30 seconds before verification") 301 time.Sleep(30 * time.Second) 302 return nil 303 } 304 305 // DeployClusterInfraClusterResourceSets deploys the ClusterResourceSets by deploying the addon template YAML 306 // ClusterResourceSets are used to deploy the following on OCNE workload clusters 307 // 1. CCM Secrets 308 // 2. Calico Module 309 // 3. CCM Module 310 func (c CAPITestImpl) DeployClusterInfraClusterResourceSets(clusterName, templateName string, log *zap.SugaredLogger) error { 311 log.Info("Preparing to deploy Clusterresourcesets...") 312 oci, err := NewClient(GetOCIConfigurationProvider(log)) 313 if err != nil { 314 log.Error("Unable to create OCI client %v", zap.Error(err)) 315 return err 316 } 317 318 OCIVcnID, err = oci.GetVcnIDByName(context.TODO(), OCICompartmentID, clusterName, log) 319 if err != nil { 320 return err 321 } 322 323 OCISubnetID, err = oci.GetSubnetIDByName(context.TODO(), OCICompartmentID, OCIVcnID, ocneServiceLbName, log) 324 if err != nil { 325 return err 326 } 327 328 os.Setenv(OCIVCNKey, OCIVcnID) 329 os.Setenv(OCISubnetKey, OCISubnetID) 330 331 tmpFilePath, err := c.ClusterTemplateGenerate(clusterName, templateName, log) 332 if err != nil { 333 log.Errorf("unable to generate template for clusterresourcesets : %v", zap.Error(err)) 334 return err 335 } 336 defer os.RemoveAll(tmpFilePath) 337 clusterTemplateData, err := os.ReadFile(tmpFilePath) 338 if err != nil { 339 log.Errorf("unable to get read file : %v", zap.Error(err)) 340 return err 341 } 342 343 err = resource.CreateOrUpdateResourceFromBytes(clusterTemplateData, log) 344 if err != nil { 345 log.Error("unable to get create clusterresourcesets on workload cluster :", zap.Error(err)) 346 return err 347 } 348 349 log.Infof("Wait for 30 seconds for cluster resourceset resources to deploy") 350 time.Sleep(30 * time.Second) 351 localTest := getEnvDefault("LOCAL_TEST", "false") 352 if strings.ToLower(localTest) == "false" { 353 // When running on Jenkins instance 354 return c.CreateImagePullSecrets(clusterName, log) 355 } 356 return nil 357 } 358 359 // DeployAnyClusterResourceSets deploys the VZ ClusterResourceSets by deploying the addon template YAML 360 func (c CAPITestImpl) DeployAnyClusterResourceSets(clusterName, templateName string, log *zap.SugaredLogger) error { 361 log.Info("Preparing to deploy VZ Clusterresourcesets...") 362 363 tmpFilePath, err := c.ClusterTemplateGenerate(clusterName, templateName, log) 364 if err != nil { 365 log.Errorf("unable to generate template for clusterresourcesets : %v", zap.Error(err)) 366 return err 367 } 368 369 log.Infof("Filename = %v", tmpFilePath) 370 371 //defer os.RemoveAll(tmpFilePath) 372 clusterTemplateData, err := os.ReadFile(tmpFilePath) 373 if err != nil { 374 log.Errorf("unable to read file : %v", zap.Error(err)) 375 return err 376 } 377 378 err = resource.CreateOrUpdateResourceFromBytes(clusterTemplateData, log) 379 if err != nil { 380 log.Error("unable to create clusterresourcesets on workload cluster :", zap.Error(err)) 381 return err 382 } 383 384 log.Infof("Wait for 5 seconds for controllers to reconcile...") 385 time.Sleep(5 * time.Second) 386 387 return nil 388 } 389 390 // EnsureMachinesAreProvisioned fetches the machines that are deployed during OCNE cluster creation. 391 func (c CAPITestImpl) EnsureMachinesAreProvisioned(namespace, clusterName string, log *zap.SugaredLogger) error { 392 machinesFetched, err := c.GetUnstructuredDataList("cluster.x-k8s.io", "v1beta1", "machines", namespace, log) 393 if err != nil { 394 log.Errorf("Unable to fetch machines due to '%v'", zap.Error(err)) 395 return err 396 } 397 398 if machinesFetched == nil { 399 log.Infof("No machines for cluster '%s' in namespace '%s' was detected", clusterName, namespace) 400 } 401 402 log.Infof("OCNE machine details:") 403 writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight) 404 fmt.Fprintln(writer, "Name\tCluster\tNodename\tProviderID\tPhase\tAge\tVersion") 405 406 var healthTracker []bool 407 408 for _, ma := range machinesFetched.Items { 409 var machine Machine 410 bdata, err := json.Marshal(ma.Object) 411 if err != nil { 412 log.Errorf("Json marshalling error %v", zap.Error(err)) 413 return err 414 } 415 err = json.Unmarshal(bdata, &machine) 416 if err != nil { 417 log.Errorf("Json unmarshall error %v", zap.Error(err)) 418 return err 419 } 420 fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v\t%v", 421 machine.Metadata.Name, machine.Metadata.Labels.ClusterXK8SIoClusterName, machine.Status.NodeRef.Name, 422 machine.Spec.ProviderID, machine.Status.Phase, time.Until(machine.Metadata.CreationTimestamp).Abs(), machine.Spec.Version)) 423 424 if strings.ToLower(machine.Status.Phase) == "running" { 425 healthTracker = append(healthTracker, true) 426 } else { 427 healthTracker = append(healthTracker, false) 428 } 429 } 430 writer.Flush() 431 432 if checkAll(healthTracker) { 433 log.Infof("All machines for cluster '%s' in namesapce '%s' are in 'Running' state", clusterName, namespace) 434 return nil 435 } 436 437 msg := fmt.Sprintf("Not all machines for cluster '%s' in namesapce '%s' are in 'Running' state", clusterName, namespace) 438 log.Errorf(msg) 439 return fmt.Errorf(msg) 440 } 441 442 func (c CAPITestImpl) MonitorCapiClusterDeletion(clusterName string, log *zap.SugaredLogger) error { 443 var err error 444 config, err := k8sutil.GetKubeConfig() 445 if err != nil { 446 log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err)) 447 return err 448 } 449 dclient, err := dynamic.NewForConfig(config) 450 if err != nil { 451 log.Errorf("Unable to create dynamic client %v", zap.Error(err)) 452 return err 453 } 454 455 gvr := schema.GroupVersionResource{ 456 Group: "cluster.x-k8s.io", 457 Version: "v1beta1", 458 Resource: "clusters", 459 } 460 var capiCluster Cluster 461 462 kluster, err := dclient.Resource(gvr).Namespace(OCNENamespace).Get(context.TODO(), clusterName, metav1.GetOptions{}) 463 if err != nil { 464 if err != nil { 465 if apierrors.IsNotFound(err) { 466 log.Errorf("cluster resource %s not found", clusterName) 467 return nil 468 } 469 log.Errorf("Unable to fetch %s %s due to '%v'", clusterName, zap.Error(err)) 470 return err 471 } 472 } 473 bdata, err := json.Marshal(kluster) 474 if err != nil { 475 log.Errorf("Json marshalling error %v", zap.Error(err)) 476 return err 477 } 478 err = json.Unmarshal(bdata, &capiCluster) 479 if err != nil { 480 log.Errorf("Json unmarshall error %v", zap.Error(err)) 481 return err 482 } 483 484 return fmt.Errorf("cluster '%s' is in '%s' state", clusterName, capiCluster.Status.Phase) 485 } 486 487 // MonitorCapiClusterCreation fetches the workload OCNE cluster elements and prints them as a formatted table. 488 // Returns an error if Cluster is not Ready 489 func (c CAPITestImpl) MonitorCapiClusterCreation(clusterName string, log *zap.SugaredLogger) error { 490 klusterData, err := c.GetCluster(OCNENamespace, clusterName, log) 491 if err != nil { 492 return err 493 } 494 495 log.Infof("----------- Cluster Events ---------------------") 496 err = c.ShowEvents(OCNENamespace, log) 497 if err != nil { 498 return err 499 } 500 501 log.Infof("----------- OCNE Control Plane ---------------------") 502 ocneCP, err := c.GetOCNEControlPlane(OCNENamespace, log) 503 if err != nil { 504 return err 505 } 506 507 writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight) 508 fmt.Fprintln(writer, "Name\tCluster\tInitialized\tReplicas\tUpdated\tUnavailable\tReady\tAge") 509 fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v", 510 ocneCP.Metadata.Name, ocneCP.Metadata.Labels.ClusterXK8SIoClusterName, ocneCP.Status.Initialized, ocneCP.Status.Replicas, 511 ocneCP.Status.UpdatedReplicas, ocneCP.Status.UnavailableReplicas, ocneCP.Status.ReadyReplicas, time.Until(ocneCP.Metadata.CreationTimestamp).Abs())) 512 writer.Flush() 513 514 log.Infof("----------- CAPI Machines ---------------------") 515 err = c.EnsureMachinesAreProvisioned(OCNENamespace, clusterName, log) 516 if err != nil { 517 return err 518 } 519 520 // OCNE cluster is ready when both control plane and worker nodes are up 521 switch OCNEK8sVersion { 522 case "1.25.7", "1.25.11": 523 if klusterData.Status.ControlPlaneReady && klusterData.Status.InfrastructureReady { 524 log.Infof("Cluster '%s' phase is => '%s'. All machines are also in '%s' state.", clusterName, klusterData.Status.Phase, klusterData.Status.Phase) 525 return nil 526 } 527 default: 528 if klusterData.Status.InfrastructureReady { 529 log.Infof("Cluster '%s' phase is => '%s'. All machines are also in '%s' state.", clusterName, klusterData.Status.Phase, klusterData.Status.Phase) 530 return nil 531 } 532 } 533 534 msg := fmt.Sprintf("cluster '%s' not ready. cluster phase is in '%s' state.", clusterName, klusterData.Status.Phase) 535 log.Errorf(msg) 536 return fmt.Errorf(msg) 537 } 538 539 func (c CAPITestImpl) TriggerCapiClusterDeletion(clusterName, nameSpaceName string, log *zap.SugaredLogger) error { 540 var err error 541 config, err := k8sutil.GetKubeConfig() 542 if err != nil { 543 log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err)) 544 return err 545 } 546 dclient, err := dynamic.NewForConfig(config) 547 if err != nil { 548 log.Errorf("Unable to create dynamic client %v", zap.Error(err)) 549 return err 550 } 551 552 gvr := schema.GroupVersionResource{ 553 Group: "cluster.x-k8s.io", 554 Version: "v1beta1", 555 Resource: "clusters", 556 } 557 558 err = dclient.Resource(gvr).Namespace(nameSpaceName).Delete(context.TODO(), clusterName, metav1.DeleteOptions{}) 559 if err != nil { 560 log.Errorf("Unable to delete cluster %s due to '%v'", clusterName, zap.Error(err)) 561 return err 562 } 563 return nil 564 } 565 566 // ShowNodeInfo displays the nodes of workload OCNE cluster as a formatted table. 567 func (c CAPITestImpl) ShowNodeInfo(client *kubernetes.Clientset, clustername string, log *zap.SugaredLogger) error { 568 writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight) 569 fmt.Fprintln(writer, "Name\tRole\tVersion\tInternalIP\tExternalIP\tOSImage\tKernelVersion\tContainerRuntime\tAge") 570 nodeList, err := client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) 571 if err != nil { 572 return errors.Wrap(err, fmt.Sprintf("failed to get list of nodes from cluster '%s'", clustername)) 573 } 574 for _, node := range nodeList.Items { 575 labels := node.GetLabels() 576 _, nodeOK := labels[nodeLabel] 577 _, controlPlaneOK := labels[nodeLabel] 578 var role, internalIP string 579 if nodeOK { 580 role = strings.Split(nodeLabel, "/")[len(strings.Split(nodeLabel, "/"))-1] 581 } 582 if controlPlaneOK { 583 role = strings.Split(controlPlaneLabel, "/")[len(strings.Split(controlPlaneLabel, "/"))-1] 584 } 585 586 addresses := node.Status.Addresses 587 for _, address := range addresses { 588 if address.Type == "InternalIP" { 589 internalIP = address.Address 590 break 591 } 592 } 593 fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v", 594 node.GetName(), role, node.Status.NodeInfo.KubeletVersion, internalIP, "None", node.Status.NodeInfo.OSImage, node.Status.NodeInfo.KernelVersion, 595 node.Status.NodeInfo.ContainerRuntimeVersion, time.Until(node.GetCreationTimestamp().Time).Abs())) 596 } 597 writer.Flush() 598 return nil 599 } 600 601 // ShowPodInfo displays the pods of workload OCNE cluster as a formatted table. 602 func (c CAPITestImpl) ShowPodInfo(client *kubernetes.Clientset, clusterName string, log *zap.SugaredLogger) error { 603 nsList, err := client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 604 if err != nil { 605 return errors.Wrap(err, fmt.Sprintf("failed to get list of namespaces from cluster '%s'", clusterName)) 606 } 607 writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight) 608 fmt.Fprintln(writer, "Name\tNamespace\tStatus\tIP\tNode\tAge") 609 //var dnsPod, ccmPod, calicokubePod *v1.Pod 610 for _, ns := range nsList.Items { 611 podList, err := client.CoreV1().Pods(ns.Name).List(context.TODO(), metav1.ListOptions{}) 612 if err != nil { 613 return errors.Wrap(err, fmt.Sprintf("failed to get list of pods from cluster '%s'", clusterName)) 614 } 615 for _, pod := range podList.Items { 616 podData, err := client.CoreV1().Pods(ns.Name).Get(context.TODO(), pod.Name, metav1.GetOptions{}) 617 if err != nil { 618 if apierrors.IsNotFound(err) { 619 log.Infof("No pods in namespace '%s'", ns.Name) 620 } else { 621 return errors.Wrap(err, fmt.Sprintf("failed to get pod '%s' from cluster '%s'", pod.Name, clusterName)) 622 } 623 } 624 625 fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v", 626 podData.GetName(), podData.GetNamespace(), podData.Status.Phase, podData.Status.PodIP, podData.Spec.NodeName, 627 time.Until(podData.GetCreationTimestamp().Time).Abs())) 628 } 629 } 630 writer.Flush() 631 return nil 632 } 633 634 // ShowEvents displays the events from a specific namespace 635 func (c CAPITestImpl) ShowEvents(namespace string, log *zap.SugaredLogger) error { 636 log.Infof("Showing events for namespace '%s'", namespace) 637 k8sclient, err := k8sutil.GetKubernetesClientset() 638 if err != nil { 639 log.Errorf("Failed to get clientset with error: %v", err) 640 return err 641 } 642 643 events, err := k8sclient.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{}) 644 if err != nil { 645 log.Info("Failed to get events from namespace: %v", zap.Error(err)) 646 return err 647 } 648 649 writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight) 650 fmt.Fprintln(writer, "Namespace\tLast Seen\tType\tReason\tObject\tMessage") 651 652 for _, event := range events.Items { 653 fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v", 654 event.Namespace, event.LastTimestamp, event.Type, event.Reason, fmt.Sprintf("%s/%s", event.InvolvedObject.Kind, event.InvolvedObject.Name), 655 event.Message)) 656 657 } 658 writer.Flush() 659 return nil 660 } 661 662 // DisplayWorkloadClusterResources displays the pods of workload OCNE cluster as a formatted table. 663 func (c CAPITestImpl) DisplayWorkloadClusterResources(clusterName string, log *zap.SugaredLogger) error { 664 client, err := c.GetCapiClusterK8sClient(clusterName, log) 665 if err != nil { 666 return errors.Wrap(err, "Failed to get k8s client for workload cluster") 667 } 668 669 log.Infof("----------- Node in workload cluster ---------------------") 670 err = c.ShowNodeInfo(client, clusterName, log) 671 if err != nil { 672 return err 673 } 674 675 log.Infof("----------- Pods running on workload cluster ---------------------") 676 return c.ShowPodInfo(client, clusterName, log) 677 } 678 679 // UpdateOCINSG allows updating nsg based on source subnet cidr using the source subnet name 680 func (c CAPITestImpl) UpdateOCINSG(clusterName, nsgDisplayNameToUpdate, nsgDisplayNameInRule, info string, rule *SecurityRuleDetails, log *zap.SugaredLogger) error { 681 log.Infof("Updating NSG rules for cluster '%s' and nsg '%s' for '%s'", clusterName, nsgDisplayNameToUpdate, info) 682 oci, err := NewClient(GetOCIConfigurationProvider(log)) 683 if err != nil { 684 log.Error("Unable to create OCI client %v", zap.Error(err)) 685 return err 686 } 687 688 vcnID, err := oci.GetVcnIDByName(context.TODO(), OCICompartmentID, clusterName, log) 689 if err != nil { 690 return err 691 } 692 693 nsgID, err := oci.GetNsgIDByName(context.TODO(), OCICompartmentID, vcnID, nsgDisplayNameToUpdate, log) 694 if err != nil { 695 return err 696 } 697 698 ruleCIDR, err := oci.GetSubnetCIDRByName(context.TODO(), OCICompartmentID, vcnID, nsgDisplayNameInRule, log) 699 if err != nil { 700 return err 701 } 702 rule.Source = ruleCIDR 703 704 return oci.UpdateNSG(context.TODO(), nsgID, rule, log) 705 } 706 707 // UpdateOCINSGEW allows updating nsg based on vcn cidr 708 func (c CAPITestImpl) UpdateOCINSGEW(clusterName, nsgDisplayNameToUpdate, info string, rule *SecurityRuleDetails, log *zap.SugaredLogger) error { 709 log.Infof("Updating NSG rules for cluster '%s' and nsg '%s' for '%s'", clusterName, nsgDisplayNameToUpdate, info) 710 oci, err := NewClient(GetOCIConfigurationProvider(log)) 711 if err != nil { 712 log.Error("Unable to create OCI client %v", zap.Error(err)) 713 return err 714 } 715 716 vcnID, err := oci.GetVcnIDByName(context.TODO(), OCICompartmentID, clusterName, log) 717 if err != nil { 718 return err 719 } 720 721 nsgID, err := oci.GetNsgIDByName(context.TODO(), OCICompartmentID, vcnID, nsgDisplayNameToUpdate, log) 722 if err != nil { 723 return err 724 } 725 726 ruleCIDR, err := oci.GetVcnCIDRByName(context.TODO(), OCICompartmentID, clusterName, log) 727 if err != nil { 728 return err 729 } 730 rule.Source = ruleCIDR 731 732 return oci.UpdateNSG(context.TODO(), nsgID, rule, log) 733 } 734 735 func (c CAPITestImpl) GetCapiClusterDynamicClient(clusterName string, log *zap.SugaredLogger) (dynamic.Interface, error) { 736 capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log) 737 if err != nil { 738 return nil, err 739 } 740 tmpFile, err := os.CreateTemp(os.TempDir(), fmt.Sprintf("%s-kubeconfig", clusterName)) 741 if err != nil { 742 log.Errorf("Failed to create temporary file : %v", zap.Error(err)) 743 return nil, err 744 } 745 746 if err := os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil { 747 log.Errorf("failed to write to destination file : %v", zap.Error(err)) 748 return nil, err 749 } 750 751 k8sRestConfig, err := k8sutil.GetKubeConfigGivenPathAndContext(tmpFile.Name(), fmt.Sprintf("%s-admin@%s", clusterName, clusterName)) 752 if err != nil { 753 log.Errorf("failed to obtain k8s rest config : %v", zap.Error(err)) 754 return nil, err 755 } 756 757 dclient, err := dynamic.NewForConfig(k8sRestConfig) 758 if err != nil { 759 log.Errorf("unable to create dynamic client for workload cluster %v", zap.Error(err)) 760 return nil, err 761 } 762 return dclient, nil 763 764 } 765 766 func (c CAPITestImpl) GetVerrazzano(clusterName, namespace, vzinstallname string, log *zap.SugaredLogger) (*unstructured.Unstructured, error) { 767 dclient, err := c.GetCapiClusterDynamicClient(clusterName, log) 768 if err != nil { 769 log.Errorf("unable to get workload kubeconfig ", zap.Error(err)) 770 return nil, err 771 } 772 773 gvr := schema.GroupVersionResource{ 774 Group: "install.verrazzano.io", 775 Version: "v1beta1", 776 Resource: "verrazzanos", 777 } 778 779 return dclient.Resource(gvr).Namespace(namespace).Get(context.TODO(), vzinstallname, metav1.GetOptions{}) 780 } 781 782 func (c CAPITestImpl) EnsureVerrazzano(clusterName string, log *zap.SugaredLogger) error { 783 784 vzFetched, err := c.GetVerrazzano(clusterName, "default", "workload-verrazzano", log) 785 if err != nil { 786 log.Errorf("unable to fetch vz resource from %s due to '%v'", clusterName, zap.Error(err)) 787 return err 788 } 789 var vz Verrazzano 790 modBinaryData, err := json.Marshal(vzFetched) 791 if err != nil { 792 log.Error("json marshalling error ", zap.Error(err)) 793 return err 794 } 795 796 err = json.Unmarshal(modBinaryData, &vz) 797 if err != nil { 798 log.Error("json unmarshalling error ", zap.Error(err)) 799 return err 800 } 801 802 curState := "InstallStarted" 803 for _, cond := range vz.Status.Conditions { 804 if cond.Type == "InstallComplete" { 805 curState = cond.Type 806 } 807 } 808 809 writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight) 810 fmt.Fprintln(writer, "Name\tAvailable\tStatus\tVersion") 811 fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v", 812 vz.Metadata.Name, vz.Status.Available, curState, vz.Status.Version)) 813 writer.Flush() 814 815 if curState == "InstallComplete" { 816 return nil 817 } 818 return fmt.Errorf("All components are not ready: Current State = %v", curState) 819 } 820 821 func (c CAPITestImpl) DebugSVCOutput(clusterName string, log *zap.SugaredLogger) error { 822 823 capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log) 824 if err != nil { 825 return err 826 } 827 tmpFile, err := os.CreateTemp(os.TempDir(), clusterName) 828 if err != nil { 829 log.Error("Failed to create temporary file ", zap.Error(err)) 830 return err 831 } 832 833 if err = os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil { 834 log.Error("failed to write to destination file ", zap.Error(err)) 835 return err 836 } 837 838 var cmdArgs []string 839 var bcmd helpers.BashCommand 840 dockerSecretCommand := fmt.Sprintf("kubectl --kubeconfig %s get svc -A", tmpFile.Name()) 841 cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand) 842 bcmd.CommandArgs = cmdArgs 843 debugCmdResponse := helpers.Runner(&bcmd, log) 844 if debugCmdResponse.CommandError != nil { 845 return debugCmdResponse.CommandError 846 } 847 848 cmdArgs = []string{} 849 dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s get pod -A", tmpFile.Name()) 850 cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand) 851 bcmd.CommandArgs = cmdArgs 852 debugCmdResponse = helpers.Runner(&bcmd, log) 853 if debugCmdResponse.CommandError != nil { 854 return debugCmdResponse.CommandError 855 } 856 857 cmdArgs = []string{} 858 dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s get vz", tmpFile.Name()) 859 cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand) 860 bcmd.CommandArgs = cmdArgs 861 debugCmdResponse = helpers.Runner(&bcmd, log) 862 if debugCmdResponse.CommandError != nil { 863 return debugCmdResponse.CommandError 864 } 865 866 return nil 867 } 868 869 func (c CAPITestImpl) CreateImagePullSecrets(clusterName string, log *zap.SugaredLogger) error { 870 log.Infof("Creating image pull secrets on workload cluster ...") 871 872 capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log) 873 if err != nil { 874 return err 875 } 876 tmpFile, err := os.CreateTemp(os.TempDir(), clusterName) 877 if err != nil { 878 log.Error("Failed to create temporary file ", zap.Error(err)) 879 return err 880 } 881 882 if err = os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil { 883 log.Error("failed to write to destination file ", zap.Error(err)) 884 return err 885 } 886 887 var cmdArgs []string 888 var bcmd helpers.BashCommand 889 dockerSecretCommand := fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry %s --docker-server=%s --docker-username=%s --docker-password=%s", tmpFile.Name(), ImagePullSecret, DockerRepo, DockerCredsUser, DockerCredsPassword) 890 cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand) 891 bcmd.CommandArgs = cmdArgs 892 secretCreateResponse := helpers.Runner(&bcmd, log) 893 if secretCreateResponse.CommandError != nil { 894 return secretCreateResponse.CommandError 895 } 896 897 cmdArgs = []string{} 898 dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry %s --docker-server=%s --docker-username=%s --docker-password=%s -n verrazzano-install", tmpFile.Name(), ImagePullSecret, DockerRepo, DockerCredsUser, DockerCredsPassword) 899 cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand) 900 bcmd.CommandArgs = cmdArgs 901 secretCreateResponse = helpers.Runner(&bcmd, log) 902 if secretCreateResponse.CommandError != nil { 903 return secretCreateResponse.CommandError 904 } 905 906 cmdArgs = []string{} 907 dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry github-packages --docker-server=%s --docker-username=%s --docker-password=%s", tmpFile.Name(), DockerRepo, DockerCredsUser, DockerCredsPassword) 908 cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand) 909 bcmd.CommandArgs = cmdArgs 910 secretCreateResponse = helpers.Runner(&bcmd, log) 911 if secretCreateResponse.CommandError != nil { 912 return secretCreateResponse.CommandError 913 } 914 915 cmdArgs = []string{} 916 dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry ocr --docker-server=%s --docker-username=%s --docker-password=%s", tmpFile.Name(), DockerRepo, DockerCredsUser, DockerCredsPassword) 917 cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand) 918 bcmd.CommandArgs = cmdArgs 919 secretCreateResponse = helpers.Runner(&bcmd, log) 920 if secretCreateResponse.CommandError != nil { 921 return secretCreateResponse.CommandError 922 } 923 924 return nil 925 926 } 927 928 func (c CAPITestImpl) CheckOCNEControlPlaneStatus(clusterName, expectedStatusType, expectedStatus, expectedReason string, log *zap.SugaredLogger) bool { 929 ocneCP, err := c.GetOCNEControlPlane(OCNENamespace, log) 930 if err != nil { 931 log.Error("unable to fetch OCNE control plane : ", zap.Error(err)) 932 return false 933 } 934 935 for _, cond := range ocneCP.Status.Conditions { 936 if cond.Type == expectedStatusType { 937 if cond.Status == expectedStatus { 938 switch cond.Status { 939 case "False": 940 if cond.Reason == expectedReason { 941 // all matched 942 return true 943 } 944 case "True": 945 // reason is irrelevant 946 return true 947 } 948 } 949 log.Errorf("HAVE status type = %s, value = %s, reason = %s. WANT status type = %s, value= %s.", cond.Type, cond.Status, cond.Reason, expectedStatusType, expectedStatus) 950 return false 951 } 952 } 953 954 log.Errorf("OCNE controlplane check failure. All conditions %+v", ocneCP.Status.Conditions) 955 return false 956 } 957 958 // ToggleModules toggles module operator or VPO 959 func (c CAPITestImpl) ToggleModules(group, version, resource, clusterName, nameSpaceName string, toggle bool, log *zap.SugaredLogger) error { 960 961 config, err := k8sutil.GetKubeConfig() 962 if err != nil { 963 log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err)) 964 return err 965 } 966 dclient, err := dynamic.NewForConfig(config) 967 if err != nil { 968 log.Errorf("Unable to create dynamic client %v", zap.Error(err)) 969 return err 970 } 971 972 log.Infof("Fetching resource %s", resource) 973 gvr := schema.GroupVersionResource{ 974 Group: group, 975 Version: version, 976 Resource: resource, 977 } 978 979 var patch []interface{} 980 if !toggle { 981 patch = []interface{}{ 982 map[string]interface{}{ 983 "op": "replace", 984 "path": "/spec/topology/variables/0", 985 "value": map[string]interface{}{ 986 "name": "moduleOperatorEnabled", 987 "value": toggle, 988 }, 989 }, 990 map[string]interface{}{ 991 "op": "replace", 992 "path": "/spec/topology/variables/1", 993 "value": map[string]interface{}{ 994 "name": "verrazzanoPlatformOperatorEnabled", 995 "value": toggle, 996 }, 997 }, 998 } 999 } else { 1000 patch = []interface{}{ 1001 map[string]interface{}{ 1002 "op": "replace", 1003 "path": "/spec/topology/variables/0", 1004 "value": map[string]interface{}{ 1005 "name": "moduleOperatorEnabled", 1006 "value": toggle, 1007 }, 1008 }, 1009 map[string]interface{}{ 1010 "op": "replace", 1011 "path": "/spec/topology/variables/1", 1012 "value": map[string]interface{}{ 1013 "name": "verrazzanoPlatformOperatorEnabled", 1014 "value": toggle, 1015 }, 1016 }, 1017 map[string]interface{}{ 1018 "op": "replace", 1019 "path": "/spec/topology/variables/2", 1020 "value": map[string]interface{}{ 1021 "name": "imagePullSecret", 1022 "value": ImagePullSecret, 1023 }, 1024 }, 1025 map[string]interface{}{ 1026 "op": "replace", 1027 "path": "/spec/topology/variables/3", 1028 "value": map[string]interface{}{ 1029 "name": "imageName", 1030 "value": ImageName, 1031 }, 1032 }, 1033 map[string]interface{}{ 1034 "op": "replace", 1035 "path": "/spec/topology/variables/4", 1036 "value": map[string]interface{}{ 1037 "name": "imageTag", 1038 "value": ImageTag, 1039 }, 1040 }, 1041 } 1042 } 1043 1044 payload, err := json.Marshal(patch) 1045 if err != nil { 1046 return err 1047 } 1048 1049 _, err = dclient.Resource(gvr).Namespace(nameSpaceName).Patch(context.TODO(), clusterName, types.JSONPatchType, payload, metav1.PatchOptions{}) 1050 if err != nil { 1051 log.Errorf("unable to patch object %v", zap.Error(err)) 1052 return err 1053 } 1054 return nil 1055 }