From 71d82dd236b69b2d8f65a6ac6f0f3440790c36c0 Mon Sep 17 00:00:00 2001 From: Sergio Regidor Date: Fri, 17 Jul 2026 13:31:44 +0000 Subject: [PATCH] Adapt vspehere bootimage tests to multi vcenter --- test/extended-priv/controlplanemachineset.go | 4 +- test/extended-priv/machineset.go | 46 ++++ test/extended-priv/mco_bootimages.go | 198 ++++++++++------- test/extended-priv/mco_bootimages_skew.go | 4 +- .../mco_controlplanemachineset.go | 24 +-- test/extended-priv/mco_scale.go | 17 +- test/extended-priv/util/vsphere.go | 203 +++++++++++------- 7 files changed, 323 insertions(+), 173 deletions(-) diff --git a/test/extended-priv/controlplanemachineset.go b/test/extended-priv/controlplanemachineset.go index 0d02be870e..0fb1b22427 100644 --- a/test/extended-priv/controlplanemachineset.go +++ b/test/extended-priv/controlplanemachineset.go @@ -182,12 +182,10 @@ func (cpms ControlPlaneMachineSet) GetCoreOsBootImage() (string, error) { return "", err } coreOsBootImagePath = fmt.Sprintf(`{.spec.template.machines_v1beta1_machine_openshift_io.spec.providerSpec.value.disks[%d].image}`, bootDiskIndex) - case VspherePlatform: - coreOsBootImagePath = `{.spec.template.machines_v1beta1_machine_openshift_io.spec.providerSpec.value.template}` case AzurePlatform: coreOsBootImagePath = `{.spec.template.machines_v1beta1_machine_openshift_io.spec.providerSpec.value.image}` default: - e2e.Failf("ControlPlaneMachineSet.GetCoreOsBootImage method is only supported for GCP, Vsphere, Azure and AWS infrastructure") + e2e.Failf("ControlPlaneMachineSet.GetCoreOsBootImage method is only supported for GCP, Azure and AWS infrastructure") } return cpms.Get(coreOsBootImagePath) diff --git a/test/extended-priv/machineset.go b/test/extended-priv/machineset.go index 92a00bb795..50cb0961cc 100644 --- a/test/extended-priv/machineset.go +++ b/test/extended-priv/machineset.go @@ -383,6 +383,11 @@ func (ms MachineSet) GetCoreOsBootImage() (string, error) { return ms.Get(coreOsBootImagePath) } +// GetWorkspaceFolder returns the workspace folder configured in the machineset's providerSpec +func (ms MachineSet) GetWorkspaceFolder() (string, error) { + return ms.Get(`{.spec.template.spec.providerSpec.value.workspace.folder}`) +} + // GetCoreOsBootImageOrFail returns the configured coreOsBootImage in this machineset and fails the test case if any error happened func (ms MachineSet) GetCoreOsBootImageOrFail() string { img, err := ms.GetCoreOsBootImage() @@ -727,3 +732,44 @@ func (ms MachineSet) SetAutoscalerLabels(labels string) error { fmt.Sprintf(`[{"op": "add", "path": "/metadata/annotations/capacity.cluster-autoscaler.kubernetes.io~1labels", "value": %s}]`, string(marshaledLabels))) } + +// GetVSphereFailureDomain returns the failure domain from the infrastructure resource that matches +// the given MachineSet's workspace. It matches by comparing the workspace server and datacenter +// against each failure domain's server and topology.datacenter. +func GetVSphereFailureDomain(ms *MachineSet) (string, error) { + workspace, err := ms.Get(`{.spec.template.spec.providerSpec.value.workspace}`) + if err != nil { + return "", fmt.Errorf("error getting workspace from MachineSet %s: %w", ms.GetName(), err) + } + + wsServer := gjson.Get(workspace, "server").String() + wsDataCenter := gjson.Get(workspace, "datacenter").String() + if wsServer == "" || wsDataCenter == "" { + return "", fmt.Errorf("workspace in MachineSet %s is missing server or datacenter", ms.GetName()) + } + + infra := NewResource(ms.GetOC().AsAdmin(), "infrastructure", "cluster") + failureDomains, err := infra.Get(`{.spec.platformSpec.vsphere.failureDomains}`) + if err != nil { + return "", fmt.Errorf("error getting failure domains from infrastructure resource: %w", err) + } + + for _, fd := range gjson.Parse(failureDomains).Array() { + if fd.Get("server").String() == wsServer && fd.Get("topology.datacenter").String() == wsDataCenter { + return fd.Raw, nil + } + } + + return "", fmt.Errorf("no failure domain found matching server=%s datacenter=%s for MachineSet %s", wsServer, wsDataCenter, ms.GetName()) +} + +// GetVSphereConnectionInfoForMachineSet returns the vSphere connection info for the failure domain +// that matches the given MachineSet's workspace. +func GetVSphereConnectionInfoForMachineSet(ms *MachineSet) (*exutil.VSphereConnectionInfo, error) { + fd, err := GetVSphereFailureDomain(ms) + if err != nil { + return nil, err + } + + return exutil.GetVSphereConnectionInfoFromFailureDomain(ms.GetOC().AsAdmin(), fd) +} diff --git a/test/extended-priv/mco_bootimages.go b/test/extended-priv/mco_bootimages.go index 040d3d6913..033581db88 100644 --- a/test/extended-priv/mco_bootimages.go +++ b/test/extended-priv/mco_bootimages.go @@ -61,7 +61,8 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati var ( duplicatedMachinesetName = fmt.Sprintf("cloned-tc-%s", GetCurrentTestPolarionIDNumber()) firstMachineSet = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), firstMachineSet) + fakeImageNameNoUpdate = "fake-noupdate-image-81403" ) exutil.By("Duplicate machineset for testing") @@ -71,13 +72,12 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Patch coreos boot image in MachineSet") - o.Expect(machineSet.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(machineSet.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error patching the value of the coreos boot image in %s", machineSet) logger.Infof("OK!\n") exutil.By("Check that the MachineSet is updated by MCO by default") - o.Eventually(machineSet.GetCoreOsBootImage, "3m", "20s").ShouldNot(o.Equal(fakeImageName), - "The machineset should be updated by MCO if the functionality is not enabled in the MachineConfiguration resource. %s", machineSet.PrettyString()) + CheckCurrentOSImageIsUpdated(machineSet, backdatedImageName) logger.Infof("OK!\n") // For none - mode i.e opt-out MachineSet are not updated with original value if we try to set with any fake value @@ -88,13 +88,12 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Patch coreos boot image in MachineSet") - o.Expect(machineSet.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(machineSet.SetCoreOsBootImage(fakeImageNameNoUpdate)).To(o.Succeed(), "Error patching the value of the coreos boot image in %s", machineSet) logger.Infof("OK!\n") exutil.By("Check that the MachineSet is not updated by MCO in opt-out") - o.Eventually(machineSet.GetCoreOsBootImage, "3m", "20s").Should(o.Equal(fakeImageName), - "The machineset should not be updated by MCO as we are opt-out in the MachineConfiguration resource. %s", machineSet.PrettyString()) + CheckCurrentOSImageIsNotUpdated(machineSet, fakeImageNameNoUpdate) logger.Infof("OK!\n") exutil.By("Opt-in boot images update") @@ -104,13 +103,12 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Patch coreos boot image in MachineSet") - o.Expect(machineSet.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(machineSet.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error patching the value of the coreos boot image in %s", machineSet) logger.Infof("OK!\n") exutil.By("Check that the MachineSet is updated by MCO for opt-in") - o.Eventually(machineSet.GetCoreOsBootImage, "3m", "20s").ShouldNot(o.Equal(fakeImageName), - "The machineset should not be updated by MCO if the functionality is not enabled in the MachineConfiguration resource. %s", machineSet.PrettyString()) + CheckCurrentOSImageIsUpdated(machineSet, backdatedImageName) logger.Infof("OK!\n") }) @@ -118,7 +116,8 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati g.It("[PolarionID:74240][OTP] ManagedBootImages. Restore All MachineSet images", g.Label("Platform:aws", "Platform:gce", "Platform:vsphere", "Platform:azure"), func() { var ( machineSet = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), machineSet) + fakeImageNameNoUpdate = "fake-noupdate-image-74240" clonedMSName = "cloned-tc-74240" clonedWrongBootImageMSName = "cloned-tc-74240-wrong-boot-image" clonedOwnedMSName = "cloned-tc-74240-owned" @@ -145,7 +144,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Clone first machineset but using a wrong ") - clonedWrongImageMS, err := DuplicateMachineSetWithCustomBootImage(machineSet, fakeImageName, clonedWrongBootImageMSName) + clonedWrongImageMS, err := DuplicateMachineSetWithCustomBootImage(machineSet, backdatedImageName, clonedWrongBootImageMSName) defer clonedWrongImageMS.Delete() o.Expect(err).NotTo(o.HaveOccurred(), "Error duplicating %s using a custom boot image", machineSet) logger.Infof("OK!\n") @@ -162,8 +161,9 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati exutil.By("All machinesets should use the right boot image") for _, ms := range NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail() { logger.Infof("Checking boot image in machineset %s", ms.GetName()) - // Check that the current boot image is the right one - CheckCurrentOSImageIsUpdated(ms) + // Check that the current boot image is the right one. + // Original machinesets were never set to backdatedImageName, so pass empty string. + CheckCurrentOSImageIsUpdated(ms, "") } logger.Infof("OK!\n") @@ -175,33 +175,31 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Patch cloned machinesets to use a wrong boot image") - o.Expect(clonedMS.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(clonedMS.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a new boot image in %s", clonedMS) - o.Expect(clonedWrongImageMS.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(clonedWrongImageMS.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a new boot image in %s", clonedWrongImageMS) - o.Expect(clonedOwnedMS.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(clonedOwnedMS.SetCoreOsBootImage(fakeImageNameNoUpdate)).To(o.Succeed(), "Error setting a new boot image in %s", clonedOwnedMS) logger.Infof("OK!\n") exutil.By("All machinesets should use the right boot image except the one with an owner") + clonedNames := map[string]bool{clonedMSName: true, clonedWrongBootImageMSName: true, clonedOwnedMSName: true} for _, ms := range NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail() { logger.Infof("Checking boot image in machineset %s", ms.GetName()) if ms.GetName() == clonedOwnedMSName { - o.Consistently(ms.GetCoreOsBootImage, "15s", "5s").Should(o.Equal(fakeImageName), - "%s was patched and it is using the right boot image. Machinesets with owners should NOT be patched.", ms) - - } else { - // Check that it was actually updated - o.Eventually(ms.GetCoreOsBootImage, "15m", "20s").ShouldNot(o.Or(o.Equal(fakeImageName), o.BeEmpty()), - "%s was NOT updated to use the right boot image", ms) - // Check that the updated image is the right one - CheckCurrentOSImageIsUpdated(ms) - // Check that the user-data secret is the right one + CheckCurrentOSImageIsNotUpdated(ms, fakeImageNameNoUpdate) + } else if clonedNames[ms.GetName()] { + // Cloned machinesets were patched with backdatedImageName + CheckCurrentOSImageIsUpdated(ms, backdatedImageName) o.Eventually(ms.GetUserDataSecret, "3m", "20s").ShouldNot(o.ContainSubstring("worker-user-data-managed"), "%s should NOT be using the worker-user-data-managed secret after updating the image", ms) + } else { + // Original machinesets were never patched + CheckCurrentOSImageIsUpdated(ms, "") } } logger.Infof("OK!\n") @@ -223,13 +221,14 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati g.It("[PolarionID:74239][OTP] ManagedBootImages. Restore Partial MachineSet images", g.Label("Platform:aws", "Platform:gce", "Platform:vsphere", "Platform:azure"), func() { var ( - machineSet = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) - clonedMSLabelName = "cloned-tc-74239-label" - clonedMSNoLabelName = "cloned-tc-74239-no-label" - clonedMSLabelOwnedName = "cloned-tc-74239-label-owned" - labelName = "test" - labelValue = "update" + machineSet = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), machineSet) + fakeImageNameNoUpdate = "fake-noupdate-image-74239" + clonedMSLabelName = "cloned-tc-74239-label" + clonedMSNoLabelName = "cloned-tc-74239-no-label" + clonedMSLabelOwnedName = "cloned-tc-74239-label-owned" + labelName = "test" + labelValue = "update" ) exutil.By("Opt-in boot images update") @@ -269,22 +268,18 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Patch the clonned machineset to configure a new boot image") - o.Expect(clonedMSLabel.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(clonedMSLabel.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a new boot image in %s", clonedMSLabel) - o.Expect(clonedMSNoLabel.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(clonedMSNoLabel.SetCoreOsBootImage(fakeImageNameNoUpdate)).To(o.Succeed(), "Error setting a new boot image in %s", clonedMSNoLabel) - o.Expect(clonedMSLabelOwned.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), + o.Expect(clonedMSLabelOwned.SetCoreOsBootImage(fakeImageNameNoUpdate)).To(o.Succeed(), "Error setting a new boot image in %s", clonedMSLabelOwned) logger.Infof("OK!\n") exutil.By("The labeled machineset without owner should be updated") - // Check that it was actually updated - o.Eventually(clonedMSLabel.GetCoreOsBootImage, "15m", "20s").ShouldNot(o.Or(o.Equal(fakeImageName), o.BeEmpty()), - "%s was NOT updated to use the right boot image", clonedMSLabel) - // Check that the updated image is the right one - CheckCurrentOSImageIsUpdated(clonedMSLabel) + CheckCurrentOSImageIsUpdated(clonedMSLabel, backdatedImageName) // Check that the user-data secret is the right one o.Eventually(clonedMSLabel.GetUserDataSecret, "3m", "20s").ShouldNot(o.ContainSubstring("worker-user-data-managed"), "%s should NOT be using the worker-user-data-managed secret after updating the image", clonedMSLabel) @@ -292,13 +287,11 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("The labeled machineset with owner should NOT be updated") - o.Consistently(clonedMSLabelOwned.GetCoreOsBootImage, "15s", "5s").Should(o.Equal(fakeImageName), - "%s was patched and it is using the right boot image. Machinesets with owners should NOT be patched.", clonedMSLabelOwned) + CheckCurrentOSImageIsNotUpdated(clonedMSLabelOwned, fakeImageNameNoUpdate) logger.Infof("OK!\n") exutil.By("The machineset without label should NOT be updated") - o.Consistently(clonedMSNoLabel.GetCoreOsBootImage, "15s", "5s").Should(o.Equal(fakeImageName), - "%s was patched and it is using the right boot image. Machinesets with owners should NOT be patched.", clonedMSNoLabel) + CheckCurrentOSImageIsNotUpdated(clonedMSNoLabel, fakeImageNameNoUpdate) logger.Infof("OK!\n") exutil.By("Scale up the fixed machinessetset to make sure that it is working fine") @@ -320,7 +313,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati var ( machineConfiguration = GetMachineConfiguration(oc.AsAdmin()) machineSet = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), machineSet) clonedMSName = "cloned-tc-74751-copy" labelName = "test" labelValue = "update" @@ -349,7 +342,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Set a wrong boot image in the cloned image") - o.Expect(clonedMS.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", clonedMS) + o.Expect(clonedMS.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", clonedMS) logger.Infof("OK!\n") exutil.By("Check that no failures are being reported") @@ -396,11 +389,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Check that the boot image was updated") - // Check that it was actually updated - o.Eventually(clonedMS.GetCoreOsBootImage, "15m", "20s").ShouldNot(o.Or(o.Equal(fakeImageName), o.BeEmpty()), - "%s was NOT updated to use the right boot image", clonedMS) - // Check that the updated image is the right one - CheckCurrentOSImageIsUpdated(clonedMS) + CheckCurrentOSImageIsUpdated(clonedMS, backdatedImageName) logger.Infof("OK!\n") }) @@ -508,7 +497,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati machineConfiguration = GetMachineConfiguration(oc.AsAdmin()) machineSet = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), machineSet) labelName = "test" labelValue = "update" @@ -545,7 +534,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Set a wrong boot image in the cloned image. Not Marketplace image. Updateable") - o.Expect(clonedMS.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", clonedMS) + o.Expect(clonedMS.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", clonedMS) logger.Infof("OK!\n") exutil.By("Label the cloned machineset so that its boot image is updated by MCO") @@ -562,11 +551,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati logger.Infof("OK!\n") exutil.By("Check that the boot image was updated with the right version") - // Check that it was actually updated - o.Eventually(clonedMS.GetCoreOsBootImage, "15m", "20s").ShouldNot(o.Or(o.Equal(fakeImageName), o.BeEmpty()), - "%s was NOT updated to use the right boot image", clonedMS) - // Check that the updated image is the right one - CheckCurrentOSImageIsUpdated(clonedMS) + CheckCurrentOSImageIsUpdated(clonedMS, backdatedImageName) logger.Infof("OK!\n") exutil.By("Scale up the updated machineset to make sure that they are working fine") @@ -806,7 +791,7 @@ func testUserDataUpdateFailure(oc *exutil.CLI, clonedMSName, clonedSecretName, e var ( machineConfiguration = GetMachineConfiguration(oc.AsAdmin()) machineSet = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), machineSet) labelName = "test" labelValue = "update" secondLabelValue = "update2" @@ -845,7 +830,7 @@ func testUserDataUpdateFailure(oc *exutil.CLI, clonedMSName, clonedSecretName, e "Error patching MachineSet %s to use the new secret %s", clonedMS.GetName(), clonedSecretName) logger.Infof("OK!\n") exutil.By("Set a wrong boot image in the cloned image") - o.Expect(clonedMS.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", clonedMS) + o.Expect(clonedMS.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", clonedMS) logger.Infof("OK!\n") exutil.By("Label the cloned machineset so that its boot image is updated by MCO") @@ -899,7 +884,7 @@ func checkManagedBootImagesStatus(mc *MachineConfiguration, mode string) { // getBackdatedBootImage returns a valid boot image value for testing based on platform // MCO will only update images previously published in the installer. This function returns one of those valid images -func getBackdatedBootImage(oc *exutil.CLI) string { +func getBackdatedBootImage(oc *exutil.CLI, ms *MachineSet) string { var ( platform = exutil.CheckPlatform(oc) ) @@ -975,7 +960,7 @@ func getBackdatedBootImage(oc *exutil.CLI) string { // To avoid collisions we will add prefix to identify our image baseImage = "mcotest-" + baseImage o.Expect( - uploadBaseImageToCloud(oc, platform, baseImageURL, baseImage), + uploadBaseImageToCloud(ms, platform, baseImageURL, baseImage), ).To(o.Succeed(), "Error uploading the base image %s to the cloud", baseImageURL) logger.Infof("Uplodated: %s", baseImage) logger.Infof("OK!\n") @@ -986,18 +971,37 @@ func getBackdatedBootImage(oc *exutil.CLI) string { } } -// getReleaseFromVsphereTemplate gets the release version from a vSphere template -func getReleaseFromVsphereTemplate(oc *exutil.CLI, vsphereTemplate string) (string, error) { - vsInfo, err := exutil.GetVSphereConnectionInfo(oc.AsAdmin()) +// getReleaseFromVsphereTemplate gets the release version from the vSphere template +// used by the given BootImageResource, using its matching failure domain. +// Only MachineSets are supported; ControlPlaneMachineSets will return an error. +func getReleaseFromVsphereTemplate(bir BootImageResource) (string, error) { + ms, ok := bir.(*MachineSet) + if !ok { + return "", fmt.Errorf("getReleaseFromVsphereTemplate only supports MachineSets") + } + + vsphereTemplate, err := bir.GetCoreOsBootImage() + if err != nil { + return "", err + } + + vsInfo, err := GetVSphereConnectionInfoForMachineSet(ms) if err != nil { return "", err } - return exutil.GetReleaseFromVsphereTemplate(vsphereTemplate, vsInfo.Server, vsInfo.DataCenter, vsInfo.User, vsInfo.Password) + folder, err := ms.GetWorkspaceFolder() + if err != nil { + return "", err + } + + return exutil.GetReleaseFromVsphereTemplate(vsphereTemplate, folder, vsInfo) } -// CheckCurrentOSImageIsUpdated checks that the machineset/controlplanemachineset is using the bootimage expected in the current cluster version -func CheckCurrentOSImageIsUpdated(bir BootImageResource) { +// CheckCurrentOSImageIsUpdated checks that the machineset/controlplanemachineset is using the bootimage expected in the current cluster version. +// It also verifies that the image reference changed from fakeImageName (on non-vSphere platforms) or that the +// template name was preserved (on vSphere, where the MCO updates the OVA in-place without renaming the template). +func CheckCurrentOSImageIsUpdated(bir BootImageResource, fakeImageName string) { var ( oc = bir.GetOC() platform = exutil.CheckPlatform(oc) @@ -1014,15 +1018,19 @@ func CheckCurrentOSImageIsUpdated(bir BootImageResource) { case AWSPlatform, GCPPlatform: o.Eventually(bir.GetCoreOsBootImage, "5m", "20s").Should(o.ContainSubstring(currentCoreOsBootImage), "%s was NOT updated to use the right boot image", bir) + if fakeImageName != "" { + o.Expect(bir.GetCoreOsBootImage()).NotTo(o.Equal(fakeImageName), + "%s boot image was not updated, it still has the fake image", bir) + } case VspherePlatform: o.Eventually(func() (string, error) { - bootImage, err := bir.GetCoreOsBootImage() - if err != nil { - return "", err - } - return getReleaseFromVsphereTemplate(oc.AsAdmin(), bootImage) + return getReleaseFromVsphereTemplate(bir) }, "5m", "20s"). - Should(o.Equal(currentCoreOsBootImage), "The image used to update %s doen't have the right version", bir) + Should(o.Equal(currentCoreOsBootImage), "The image used to update %s doesn't have the right version", bir) + if fakeImageName != "" { + o.Expect(bir.GetCoreOsBootImage()).To(o.Equal(fakeImageName), + "%s template name was changed, but MCO should update the OVA in-place without renaming the template", bir) + } case AzurePlatform: parsedImage := gjson.Parse(currentCoreOsBootImage) sku := parsedImage.Get("sku").String() @@ -1038,11 +1046,49 @@ func CheckCurrentOSImageIsUpdated(bir BootImageResource) { HavePathWithValue("resourceID", o.BeEmpty()), HavePathWithValue("type", o.Equal("MarketplaceNoPlan"))), "%s was NOT updated to use the right boot image", bir) + if fakeImageName != "" { + o.Expect(bir.GetCoreOsBootImage()).NotTo(o.Equal(fakeImageName), + "%s boot image was not updated, it still has the fake image", bir) + } default: e2e.Failf("Platform not supported in CheckCurrentOSImageIsUpdated: %s", platform) } } +// CheckCurrentOSImageIsNotUpdated checks that the machineset/controlplanemachineset is NOT using the current cluster bootimage, +// i.e. the MCO has not updated it. On vSphere, where the template name doesn't change during updates, it checks that the +// RHCOS version inside the template has not been updated to the current version. +func CheckCurrentOSImageIsNotUpdated(bir BootImageResource, fakeImageName string) { + var ( + oc = bir.GetOC() + platform = exutil.CheckPlatform(oc) + ) + + switch platform { + case VspherePlatform: + var ( + region = getCurrentRegionOrFail(oc) + arch = bir.GetArchitectureOrFail() + coreosBootimagesCM = NewConfigMap(oc.AsAdmin(), MachineConfigNamespace, "coreos-bootimages") + ) + currentCoreOsBootImage := getCoreOsBootImageFromConfigMapOrFail(platform, region, arch, coreosBootimagesCM) + o.Expect(currentCoreOsBootImage).NotTo(o.BeEmpty(), "Could not find the right coreOS image for this platform") + + o.Consistently(func() string { + release, err := getReleaseFromVsphereTemplate(bir) + if err != nil { + // A non-existing template means the MCO did not update it + return "" + } + return release + }, "15s", "5s").ShouldNot(o.Equal(currentCoreOsBootImage), + "%s was updated but it should NOT have been", bir) + default: + o.Consistently(bir.GetCoreOsBootImage, "15s", "5s").Should(o.Equal(fakeImageName), + "%s was updated but it should NOT have been", bir) + } +} + // setArchitectureAndCheckStatus sets the capacity labels annotation on the cloned machineset and checks the status. // If archValue already contains "kubernetes.io/arch=", it is used as the raw annotation value. // Otherwise, "kubernetes.io/arch=" is prepended automatically. diff --git a/test/extended-priv/mco_bootimages_skew.go b/test/extended-priv/mco_bootimages_skew.go index 44a12ba8a0..6427d83d15 100644 --- a/test/extended-priv/mco_bootimages_skew.go +++ b/test/extended-priv/mco_bootimages_skew.go @@ -114,7 +114,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/disruptive }() // Patch the boot image to an older version to trigger an update loop - backdatedBootImage := getBackdatedBootImage(oc) + backdatedBootImage := getBackdatedBootImage(oc, machineSetUnderTest) o.Expect(machineSetUnderTest.SetCoreOsBootImage(backdatedBootImage)).To(o.Succeed()) logger.Infof("Set backdated boot image '%s' in MachineSet %s to trigger update loop", backdatedBootImage, machineSetUnderTest.name) @@ -159,7 +159,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/disruptive logger.Infof("Set non-existent user data secret '%s' in MachineSet %s", nonExistentSecret, machineSetUnderTest.name) // Patch the boot image to an older version to trigger an update loop - backdatedBootImage := getBackdatedBootImage(oc) + backdatedBootImage := getBackdatedBootImage(oc, machineSetUnderTest) o.Expect(machineSetUnderTest.SetCoreOsBootImage(backdatedBootImage)).To(o.Succeed()) logger.Infof("Set backdated boot image '%s' in MachineSet %s to trigger update loop", backdatedBootImage, machineSetUnderTest.name) diff --git a/test/extended-priv/mco_controlplanemachineset.go b/test/extended-priv/mco_controlplanemachineset.go index c5527d636c..27a099c38b 100644 --- a/test/extended-priv/mco_controlplanemachineset.go +++ b/test/extended-priv/mco_controlplanemachineset.go @@ -158,7 +158,7 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag var ( machineConfiguration = GetMachineConfiguration(oc.AsAdmin()) - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), nil) userDataJSONVersionPath = `ignition.version` ) @@ -197,12 +197,12 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag logger.Infof("OK!\n") exutil.By("Set a wrong boot image in the ControlPlaneMachineSet") - o.Expect(cpms.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", cpms) + o.Expect(cpms.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", cpms) logger.Infof("OK!\n") exutil.By("Check that the boot image was NOT updated") - // With Mode: None, the boot image should remain unchanged (still using the fake image) - o.Consistently(cpms.GetCoreOsBootImage, "3m", "30s").Should(o.Equal(fakeImageName), + // With Mode: None, the boot image should remain unchanged (still using the backdated image) + o.Consistently(cpms.GetCoreOsBootImage, "3m", "30s").Should(o.Equal(backdatedImageName), "The boot image was unexpectedly updated when Mode: None was configured") logger.Infof("OK!\n") @@ -218,7 +218,7 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag g.It("[PolarionID:85480][OTP] ControlPlaneMachineSets. Not updated when owner reference [apigroup:machineconfiguration.openshift.io]", func() { var ( - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), nil) userDataJSONVersionPath = `ignition.version` ) @@ -266,7 +266,7 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag logger.Infof("OK!\n") exutil.By("Set a wrong boot image in the ControlPlaneMachineSet") - o.Expect(cpms.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", cpms) + o.Expect(cpms.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", cpms) logger.Infof("OK!\n") exutil.By("Configure MachineConfiguration resource with mode All for controlplanemachinesets") @@ -278,7 +278,7 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag exutil.By("Check that the boot image was NOT updated") // With owner reference, the boot image should remain unchanged even with Mode: All - o.Consistently(cpms.GetCoreOsBootImage, "3m", "30s").Should(o.Equal(fakeImageName), + o.Consistently(cpms.GetCoreOsBootImage, "3m", "30s").Should(o.Equal(backdatedImageName), "The boot image was unexpectedly updated when owner reference was present") logger.Infof("OK!\n") @@ -331,7 +331,7 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag g.It("[PolarionID:85467][OTP] ControlPlaneMachineSets. Bootimage upgrade stub ignition to spec 3 [apigroup:machineconfiguration.openshift.io]", func() { var ( - fakeImageName = getBackdatedBootImage(oc.AsAdmin()) + backdatedImageName = getBackdatedBootImage(oc.AsAdmin(), nil) userDataJSONVersionPath = `ignition.version` ) @@ -370,7 +370,7 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag logger.Infof("OK!\n") exutil.By("Set a wrong boot image in the ControlPlaneMachineSet") - o.Expect(cpms.SetCoreOsBootImage(fakeImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", cpms) + o.Expect(cpms.SetCoreOsBootImage(backdatedImageName)).To(o.Succeed(), "Error setting a fake boot image in %s", cpms) logger.Infof("OK!\n") exutil.By("Check that the user-data secret is updated to the latest ignition version") @@ -381,11 +381,7 @@ var _ = g.Describe("[sig-mco][Serial][Disruptive][OCPFeatureGate:ManagedBootImag logger.Infof("OK!\n") exutil.By("Check that the boot image was updated with the right version") - // Check that it was actually updated - o.Eventually(cpms.GetCoreOsBootImage, "5m", "20s").ShouldNot(o.Or(o.Equal(fakeImageName), o.BeEmpty()), - "%s was NOT updated to use the right boot image", cpms) - // Check that the updated image is the right one - CheckCurrentOSImageIsUpdated(cpms) + CheckCurrentOSImageIsUpdated(cpms, backdatedImageName) logger.Infof("OK!\n") exutil.By("Delete one machine and wait for it to be recreated") diff --git a/test/extended-priv/mco_scale.go b/test/extended-priv/mco_scale.go index 171e3e2af6..ca5203c954 100644 --- a/test/extended-priv/mco_scale.go +++ b/test/extended-priv/mco_scale.go @@ -350,7 +350,7 @@ func cloneMachineSet(oc *exutil.CLI, ms *MachineSet, newMsName, imageVersion, ig baseImage = "mcotest-" + baseImage } o.Expect( - uploadBaseImageToCloud(oc, platform, baseImageURL, baseImage), + uploadBaseImageToCloud(ms, platform, baseImageURL, baseImage), ).To(o.Succeed(), "Error uploading the base image %s to the cloud", baseImageURL) logger.Infof("OK!\n") @@ -671,7 +671,7 @@ func getBaseImageURLFromRHCOSImageInfo(version, stream, platform, format, string return fmt.Sprintf("%s/%s", strings.Replace(strings.Trim(baseURI.String(), "/"), "releases-art-rhcos.svc.ci.openshift.org", "rhcos.mirror.openshift.com", 1), strings.Trim(baseImageURL.String(), "/")), nil } -func uploadBaseImageToCloud(oc *exutil.CLI, platform, baseImageURL, baseImage string) error { +func uploadBaseImageToCloud(ms *MachineSet, platform, baseImageURL, baseImage string) error { switch platform { case AWSPlatform: @@ -681,16 +681,25 @@ func uploadBaseImageToCloud(oc *exutil.CLI, platform, baseImageURL, baseImage st logger.Infof("No need to updload images in GCP") return nil case VspherePlatform: - vsInfo, err := exutil.GetVSphereConnectionInfo(oc.AsAdmin()) + vsInfo, err := GetVSphereConnectionInfoForMachineSet(ms) if err != nil { return err } - err = exutil.UploadBaseImageToVsphere(baseImageURL, baseImage, vsInfo) + folder, _ := ms.GetWorkspaceFolder() + + err = exutil.UploadBaseImageToVsphere(baseImageURL, baseImage, vsInfo, folder) if err != nil { return err } + g.DeferCleanup(func() { + logger.Infof("Cleaning up vSphere template %s", baseImage) + if err := exutil.DeleteVsphereTemplate(baseImage, folder, vsInfo); err != nil { + logger.Warnf("Failed to delete vSphere template %s: %v", baseImage, err) + } + }) + return nil default: return fmt.Errorf("platform %s is not supported, base image cannot be updloaded", platform) diff --git a/test/extended-priv/util/vsphere.go b/test/extended-priv/util/vsphere.go index 5ec501436e..22c7ce4450 100644 --- a/test/extended-priv/util/vsphere.go +++ b/test/extended-priv/util/vsphere.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "path" "strings" "github.com/tidwall/gjson" @@ -80,7 +81,7 @@ func DownloadOVAIfURL(ovaPath string) (string, error) { // UploadBaseImageToVsphere uploads a base image OVA to vSphere and converts it to a template. // The baseImageSrc can be either a local file path or a URL. -func UploadBaseImageToVsphere(baseImageSrc, baseImageDest string, vsInfo *VSphereConnectionInfo) error { +func UploadBaseImageToVsphere(baseImageSrc, baseImageDest string, vsInfo *VSphereConnectionInfo, folder string) error { ctx := context.Background() // Build vSphere URL without credentials @@ -124,16 +125,27 @@ func UploadBaseImageToVsphere(baseImageSrc, baseImageDest string, vsInfo *VSpher } // Find VM folder - folders, err := dc.Folders(ctx) - if err != nil { - return fmt.Errorf("failed to get datacenter folders: %w", err) + var vmFolder *object.Folder + if folder != "" { + vmFolder, err = finder.Folder(ctx, folder) + if err != nil { + return fmt.Errorf("failed to find folder %s: %w", folder, err) + } + logger.Infof("Using workspace folder %s", folder) + } else { + folders, err := dc.Folders(ctx) + if err != nil { + return fmt.Errorf("failed to get datacenter folders: %w", err) + } + vmFolder = folders.VmFolder + logger.Infof("Using datacenter root VM folder") } - // Check if VM already exists + // Check if VM already exists in the workspace folder var vm *object.VirtualMachine existingVM, err := finder.VirtualMachine(ctx, baseImageDest) - if err == nil { - logger.Infof("Image %s already exists in the cloud, we don't upload it again", baseImageDest) + if err == nil && (vmFolder == nil || path.Dir(existingVM.InventoryPath) == vmFolder.InventoryPath) { + logger.Infof("Image %s already exists in the workspace folder, we don't upload it again", baseImageDest) vm = existingVM } else { // Download OVA if it's a URL @@ -183,7 +195,7 @@ func UploadBaseImageToVsphere(baseImageSrc, baseImageDest string, vsInfo *VSpher Datacenter: dc, Datastore: ds, ResourcePool: pool, - Folder: folders.VmFolder, + Folder: vmFolder, Log: func(s string) (int, error) { logger.Infof("%s", s) return len(s), nil @@ -237,42 +249,93 @@ func UploadBaseImageToVsphere(baseImageSrc, baseImageDest string, vsInfo *VSpher return nil } -// GetReleaseFromVsphereTemplate gets the release version from a vSphere template -func GetReleaseFromVsphereTemplate(vsphereTemplate, server, dataCenter, user, password string) (string, error) { +// DeleteVsphereTemplate deletes a VM/template from vSphere by name, but only if it +// exists in the specified folder. Templates in other folders are left untouched. +// It is a no-op if the template does not exist or is in a different folder. +func DeleteVsphereTemplate(templateName, folder string, vsInfo *VSphereConnectionInfo) error { ctx := context.Background() - // Build vSphere URL without credentials - u, err := url.Parse(fmt.Sprintf("https://%s/sdk", server)) + u, err := url.Parse(fmt.Sprintf("https://%s/sdk", vsInfo.Server)) if err != nil { - return "", fmt.Errorf("failed to parse vSphere URL for server %s", server) + return fmt.Errorf("failed to parse vSphere URL for server %s", vsInfo.Server) } + u.User = url.UserPassword(vsInfo.User, vsInfo.Password) - // Set credentials separately - u.User = url.UserPassword(user, password) + c, err := govmomi.NewClient(ctx, u, true) + if err != nil { + return fmt.Errorf("failed to connect to vSphere: %w", err) + } + defer c.Logout(ctx) + + finder := find.NewFinder(c.Client, true) + + dc, err := finder.Datacenter(ctx, vsInfo.DataCenter) + if err != nil { + return fmt.Errorf("failed to find datacenter %s: %w", vsInfo.DataCenter, err) + } + finder.SetDatacenter(dc) + + // Search by full path within the folder to avoid matching templates in other folders + searchPath := templateName + if folder != "" { + searchPath = folder + "/" + templateName + } + + vm, err := finder.VirtualMachine(ctx, searchPath) + if err != nil { + logger.Infof("Template %s not found in folder %s, nothing to delete", templateName, folder) + return nil + } + + logger.Infof("Deleting vSphere template %s", templateName) + destroyTask, err := vm.Destroy(ctx) + if err != nil { + return fmt.Errorf("failed to initiate destroy of template %s: %w", templateName, err) + } + if err = destroyTask.Wait(ctx); err != nil { + return fmt.Errorf("failed to destroy template %s: %w", templateName, err) + } + logger.Infof("Deleted vSphere template %s", templateName) + return nil +} + +// GetReleaseFromVsphereTemplate gets the release version from a vSphere template. +// If folder is non-empty, the template is looked up within that folder to avoid +// matching identically-named templates in other folders. +func GetReleaseFromVsphereTemplate(vsphereTemplate, folder string, vsInfo *VSphereConnectionInfo) (string, error) { + ctx := context.Background() + + u, err := url.Parse(fmt.Sprintf("https://%s/sdk", vsInfo.Server)) + if err != nil { + return "", fmt.Errorf("failed to parse vSphere URL for server %s", vsInfo.Server) + } + u.User = url.UserPassword(vsInfo.User, vsInfo.Password) logger.Infof("Getting information about vsphere template %s", vsphereTemplate) - // Connect to vSphere c, err := govmomi.NewClient(ctx, u, true) if err != nil { return "", fmt.Errorf("failed to connect to vSphere: %w", err) } defer c.Logout(ctx) - // Create finder finder := find.NewFinder(c.Client, true) - // Find datacenter - dc, err := finder.Datacenter(ctx, dataCenter) + dc, err := finder.Datacenter(ctx, vsInfo.DataCenter) if err != nil { - return "", fmt.Errorf("failed to find datacenter %s: %w", dataCenter, err) + return "", fmt.Errorf("failed to find datacenter %s: %w", vsInfo.DataCenter, err) } finder.SetDatacenter(dc) - // Find the VM/template - vm, err := finder.VirtualMachine(ctx, vsphereTemplate) + // Search by full path within the folder to avoid matching templates in other folders + searchPath := vsphereTemplate + if folder != "" { + searchPath = folder + "/" + vsphereTemplate + } + + vm, err := finder.VirtualMachine(ctx, searchPath) if err != nil { - return "", fmt.Errorf("failed to find VM/template %s: %w", vsphereTemplate, err) + return "", fmt.Errorf("failed to find VM/template %s: %w", searchPath, err) } // Get VM properties @@ -302,48 +365,25 @@ type VSphereConnectionInfo struct { Password string } -// GetVSphereConnectionInfo extracts vSphere connection parameters from the infrastructure resource and credentials secret -func GetVSphereConnectionInfo(oc *CLI) (*VSphereConnectionInfo, error) { - var info VSphereConnectionInfo - failureDomain, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("infrastructure", "cluster", "-o", "jsonpath={.spec.platformSpec.vsphere.failureDomains[0]}").Output() - if err != nil { - return nil, fmt.Errorf("cannot get the failureDomain from the infrastructure resource: %w", err) - } +// GetVSphereConnectionInfoFromFailureDomain builds a VSphereConnectionInfo from a failure domain JSON string +func GetVSphereConnectionInfoFromFailureDomain(oc *CLI, failureDomain string) (*VSphereConnectionInfo, error) { if failureDomain == "" { - return nil, fmt.Errorf("empty failure domain in the infrastructure resource") - } - - gserver := gjson.Get(failureDomain, "server") - if !gserver.Exists() { - return nil, fmt.Errorf("cannot get the server value from failureDomain") + return nil, fmt.Errorf("empty failure domain") } - info.Server = gserver.String() - gdataCenter := gjson.Get(failureDomain, "topology.datacenter") - if !gdataCenter.Exists() { - return nil, fmt.Errorf("cannot get the data center value from failureDomain") + info := &VSphereConnectionInfo{ + Server: gjson.Get(failureDomain, "server").String(), + DataCenter: gjson.Get(failureDomain, "topology.datacenter").String(), + DataStore: gjson.Get(failureDomain, "topology.datastore").String(), + ResourcePool: gjson.Get(failureDomain, "topology.resourcePool").String(), + Network: gjson.Get(failureDomain, "topology.networks.0").String(), } - info.DataCenter = gdataCenter.String() - gdataStore := gjson.Get(failureDomain, "topology.datastore") - if !gdataStore.Exists() { - return nil, fmt.Errorf("cannot get the data store value from failureDomain") + if info.Server == "" || info.DataCenter == "" || info.DataStore == "" || info.ResourcePool == "" || info.Network == "" { + return nil, fmt.Errorf("incomplete failure domain: server=%s datacenter=%s datastore=%s resourcePool=%s network=%s", + info.Server, info.DataCenter, info.DataStore, info.ResourcePool, info.Network) } - info.DataStore = gdataStore.String() - gresourcePool := gjson.Get(failureDomain, "topology.resourcePool") - if !gresourcePool.Exists() { - return nil, fmt.Errorf("cannot get the resourcepool value from failureDomain") - } - info.ResourcePool = gresourcePool.String() - - gnetwork := gjson.Get(failureDomain, "topology.networks.0") - if !gnetwork.Exists() { - return nil, fmt.Errorf("cannot get the network value from failureDomain") - } - info.Network = gnetwork.String() - - // Get credentials from vsphere-creds secret secretData, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("secret", "vsphere-creds", "-n", "kube-system", "-o", "jsonpath={.data}").Output() if err != nil { return nil, err @@ -354,25 +394,40 @@ func GetVSphereConnectionInfo(oc *CLI) (*VSphereConnectionInfo, error) { return nil, err } - for k, vb64 := range dataMap { - v, decErr := base64.StdEncoding.DecodeString(vb64) - if decErr != nil { - return nil, fmt.Errorf("cannot decode secret value for key %s: %w", k, decErr) - } - if strings.Contains(k, "username") { - info.User = string(v) - } - if strings.Contains(k, "password") { - info.Password = string(v) - } + // The secret keys are formatted as ".username" and ".password" + userKey := info.Server + ".username" + passKey := info.Server + ".password" + + userB64, ok := dataMap[userKey] + if !ok { + return nil, fmt.Errorf("vsphere credentials key %s not found in vsphere-creds secret", userKey) + } + userBytes, err := base64.StdEncoding.DecodeString(userB64) + if err != nil { + return nil, fmt.Errorf("cannot decode secret value for key %s: %w", userKey, err) } + info.User = string(userBytes) - if info.User == "" { - return nil, fmt.Errorf("the vsphere user is empty") + passB64, ok := dataMap[passKey] + if !ok { + return nil, fmt.Errorf("vsphere credentials key %s not found in vsphere-creds secret", passKey) } - if info.Password == "" { - return nil, fmt.Errorf("the vsphere password is empty") + passBytes, err := base64.StdEncoding.DecodeString(passB64) + if err != nil { + return nil, fmt.Errorf("cannot decode secret value for key %s: %w", passKey, err) + } + info.Password = string(passBytes) + + return info, nil +} + +// GetVSphereConnectionInfo extracts vSphere connection parameters from the first failure domain +// in the infrastructure resource and the credentials secret +func GetVSphereConnectionInfo(oc *CLI) (*VSphereConnectionInfo, error) { + failureDomain, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("infrastructure", "cluster", "-o", "jsonpath={.spec.platformSpec.vsphere.failureDomains[0]}").Output() + if err != nil { + return nil, fmt.Errorf("cannot get the failureDomain from the infrastructure resource: %w", err) } - return &info, nil + return GetVSphereConnectionInfoFromFailureDomain(oc, failureDomain) }