Skip to content

Commit

Permalink
Merge pull request #9093 from nutanix-cloud-native/CORS-3706
Browse files Browse the repository at this point in the history
CORS-3706: allow to install an OCP Nutanix cluster using PC's existing RHCOS image
  • Loading branch information
openshift-merge-bot[bot] authored Oct 23, 2024
2 parents fe4f41c + 909a82a commit 8032a54
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 41 deletions.
5 changes: 5 additions & 0 deletions data/data/install.openshift.io_installconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4177,6 +4177,11 @@ spec:
- message: type is immutable once set
rule: oldSelf == '' || self == oldSelf
type: object
preloadedOSImageName:
description: PreloadedOSImageName uses the named preloaded RHCOS
image from PC/PE, instead of create and upload a new image for
each cluster.
type: string
prismCentral:
description: PrismCentral is the endpoint (address and port) and
credentials to connect to the Prism Central. This serves as
Expand Down
70 changes: 70 additions & 0 deletions pkg/asset/installconfig/nutanix/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import (
"context"
"fmt"
"strconv"
"strings"
"time"

nutanixclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/validation/field"

"github.com/openshift/installer/pkg/rhcos"
"github.com/openshift/installer/pkg/types"
nutanixtypes "github.com/openshift/installer/pkg/types/nutanix"
)
Expand Down Expand Up @@ -60,6 +64,14 @@ func ValidateForProvisioning(ic *types.InstallConfig) error {
}
}

// validate PreloadedOSImageName if configured
if p.PreloadedOSImageName != "" {
err = validatePreloadedImage(ctx, nc, p)
if err != nil {
errList = append(errList, field.Invalid(parentPath.Child("preloadedOSImageName"), p.PreloadedOSImageName, fmt.Sprintf("fail to validate the preloaded rhcos image: %v", err)))
}
}

// validate each FailureDomain configuration
for _, fd := range p.FailureDomains {
// validate whether the prism element with the UUID exists
Expand Down Expand Up @@ -101,3 +113,61 @@ func ValidateForProvisioning(ic *types.InstallConfig) error {

return errList.ToAggregate()
}

func validatePreloadedImage(ctx context.Context, nc *nutanixclientv3.Client, p *nutanixtypes.Platform) error {
// retrieve the rhcos release version
rhcosStream, err := rhcos.FetchCoreOSBuild(ctx)
if err != nil {
return err
}

arch, ok := rhcosStream.Architectures["x86_64"]
if !ok {
return fmt.Errorf("unable to find the x86_64 rhcos architecture")
}
artifacts, ok := arch.Artifacts["nutanix"]
if !ok {
return fmt.Errorf("unable to find the x86_64 nutanix rhcos artifacts")
}
rhcosReleaseVersion := artifacts.Release

// retrieve the rhcos version number from rhcosReleaseVersion
rhcosVerNum, err := strconv.Atoi(strings.Split(rhcosReleaseVersion, ".")[0])
if err != nil {
return fmt.Errorf("failed to get the rhcos image version number from the version string %s: %w", rhcosReleaseVersion, err)
}

// retrieve the rhcos version number from the preloaded image object
imgUUID, err := nutanixtypes.FindImageUUIDByName(ctx, nc, p.PreloadedOSImageName)
if err != nil {
return err
}
imgResp, err := nc.V3.GetImage(ctx, *imgUUID)
if err != nil {
return fmt.Errorf("failed to retrieve the rhcos image with uuid %s: %w", *imgUUID, err)
}
imgSource := *imgResp.Status.Resources.SourceURI

si := strings.LastIndex(imgSource, "/rhcos-")
if si < 0 {
return fmt.Errorf("failed to get the rhcos image version from the preloaded image %s object's source_uri %s", p.PreloadedOSImageName, imgSource)
}
verStr := strings.Split(imgSource[si+7:], ".")[0]
imgVerNum, err := strconv.Atoi(verStr)
if err != nil {
return fmt.Errorf("failed to get the rhcos image version number from the version string %s: %w", verStr, err)
}

// verify that the image version numbers are compactible
versionDiff := rhcosVerNum - imgVerNum
switch {
case versionDiff < 0:
return fmt.Errorf("the preloaded image's rhcos version: %v is too many revisions ahead the installer bundled rhcos version: %v", imgVerNum, rhcosVerNum)
case versionDiff >= 2:
return fmt.Errorf("the preloaded image's rhcos version: %v is too many revisions behind the installer bundled rhcos version: %v", imgVerNum, rhcosVerNum)
case versionDiff == 1:
logrus.Warnf("the preloaded image's rhcos version: %v is behind the installer bundled rhcos version: %v, installation may fail", imgVerNum, rhcosVerNum)
}

return nil
}
2 changes: 1 addition & 1 deletion pkg/asset/machines/clusterapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ func (c *ClusterAPI) Generate(ctx context.Context, dependencies asset.Parents) e
return fmt.Errorf("failed to generate Cluster API machine manifests for control-plane: %w", err)
}
pool.Platform.Nutanix = &mpool
templateName := nutanixtypes.RHCOSImageName(clusterID.InfraID)
templateName := nutanixtypes.RHCOSImageName(ic.Platform.Nutanix, clusterID.InfraID)

c.FileList, err = nutanixcapi.GenerateMachines(clusterID.InfraID, ic, &pool, templateName, "master")
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/asset/machines/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ func (m *Master) Generate(ctx context.Context, dependencies asset.Parents) error
return errors.Wrap(err, "failed to create master machine objects")
}
pool.Platform.Nutanix = &mpool
templateName := nutanixtypes.RHCOSImageName(clusterID.InfraID)
templateName := nutanixtypes.RHCOSImageName(ic.Platform.Nutanix, clusterID.InfraID)

machines, controlPlaneMachineSet, err = nutanix.Machines(clusterID.InfraID, ic, &pool, templateName, "master", masterUserDataSecretName)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/asset/machines/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ func (w *Worker) Generate(ctx context.Context, dependencies asset.Parents) error
return errors.Wrap(err, "failed to create worker machine objects")
}
pool.Platform.Nutanix = &mpool
imageName := nutanixtypes.RHCOSImageName(clusterID.InfraID)
imageName := nutanixtypes.RHCOSImageName(ic.Platform.Nutanix, clusterID.InfraID)

sets, err := nutanix.MachineSets(clusterID.InfraID, ic, &pool, imageName, "worker", workerUserDataSecretName)
if err != nil {
Expand Down
76 changes: 40 additions & 36 deletions pkg/infrastructure/nutanix/clusterapi/clusterapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,45 +77,49 @@ func (p Provider) PreProvision(ctx context.Context, in infracapi.PreProvisionInp
}
logrus.Infof("created the category value %q with name %q", *respv.Value, *respv.Name)

// upload the rhcos image.
imgName := nutanixtypes.RHCOSImageName(in.InfraID)
imgURI := in.RhcosImage.ControlPlane
imgReq := &nutanixclientv3.ImageIntentInput{}
imgSpec := &nutanixclientv3.Image{
Name: &imgName,
Description: ptr.To("Created By OpenShift Installer"),
Resources: &nutanixclientv3.ImageResources{
ImageType: ptr.To("DISK_IMAGE"),
SourceURI: &imgURI,
},
}
imgReq.Spec = imgSpec
imgMeta := &nutanixclientv3.Metadata{
Kind: ptr.To("image"),
Categories: map[string]string{categoryKey: nutanixtypes.CategoryValueOwned},
}
imgReq.Metadata = imgMeta
respi, err := nutanixCl.V3.CreateImage(ctx, imgReq)
if err != nil {
return fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
}
imgUUID := *respi.Metadata.UUID
logrus.Infof("creating the rhcos image %s (uuid: %s).", imgName, imgUUID)
if ic.Nutanix.PreloadedOSImageName != "" {
logrus.Infof("Using the existing rhcos image %q in PC", in.InstallConfig.Config.Nutanix.PreloadedOSImageName)
} else {
// upload the rhcos image.
imgName := nutanixtypes.RHCOSImageName(in.InstallConfig.Config.Nutanix, in.InfraID)
imgURI := in.RhcosImage.ControlPlane
imgReq := &nutanixclientv3.ImageIntentInput{}
imgSpec := &nutanixclientv3.Image{
Name: &imgName,
Description: ptr.To("Created By OpenShift Installer"),
Resources: &nutanixclientv3.ImageResources{
ImageType: ptr.To("DISK_IMAGE"),
SourceURI: &imgURI,
},
}
imgReq.Spec = imgSpec
imgMeta := &nutanixclientv3.Metadata{
Kind: ptr.To("image"),
Categories: map[string]string{categoryKey: nutanixtypes.CategoryValueOwned},
}
imgReq.Metadata = imgMeta
respi, err := nutanixCl.V3.CreateImage(ctx, imgReq)
if err != nil {
return fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
}
imgUUID := *respi.Metadata.UUID
logrus.Infof("creating the rhcos image %s (uuid: %s).", imgName, imgUUID)

if taskUUID, ok := respi.Status.ExecutionContext.TaskUUID.(string); ok {
logrus.Infof("waiting the image data uploading from %s, taskUUID: %s.", imgURI, taskUUID)
if taskUUID, ok := respi.Status.ExecutionContext.TaskUUID.(string); ok {
logrus.Infof("waiting the image data uploading from %s, taskUUID: %s.", imgURI, taskUUID)

// Wait till the image creation task is successed.
if err = nutanixtypes.WaitForTask(nutanixCl.V3, taskUUID); err != nil {
e1 := fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
logrus.Error(e1)
return e1
// Wait till the image creation task is successed.
if err = nutanixtypes.WaitForTask(nutanixCl.V3, taskUUID); err != nil {
e1 := fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
logrus.Error(e1)
return e1
}
logrus.Infof("created and uploaded the rhcos image data %s (uuid: %s)", imgName, imgUUID)
} else {
err = fmt.Errorf("failed to convert the task UUID %v to string", respi.Status.ExecutionContext.TaskUUID)
logrus.Errorf(err.Error())
return err
}
logrus.Infof("created and uploaded the rhcos image data %s (uuid: %s)", imgName, imgUUID)
} else {
err = fmt.Errorf("failed to convert the task UUID %v to string", respi.Status.ExecutionContext.TaskUUID)
logrus.Errorf(err.Error())
return err
}

return nil
Expand Down
9 changes: 7 additions & 2 deletions pkg/types/nutanix/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,13 @@ func getTaskStatus(clientV3 nutanixclientv3.Service, taskUUID string) (string, e
}

// RHCOSImageName is the unique image name for a given cluster.
func RHCOSImageName(infraID string) string {
return fmt.Sprintf("%s-rhcos", infraID)
func RHCOSImageName(p *Platform, infraID string) string {
imgName := p.PreloadedOSImageName
if imgName == "" {
imgName = fmt.Sprintf("%s-rhcos", infraID)
}

return imgName
}

// CategoryKey returns the cluster specific category key name.
Expand Down
6 changes: 6 additions & 0 deletions pkg/types/nutanix/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ type Platform struct {
// +optional
ClusterOSImage string `json:"clusterOSImage,omitempty"`

// PreloadedOSImageName uses the named preloaded RHCOS image from PC/PE,
// instead of create and upload a new image for each cluster.
//
// +optional
PreloadedOSImageName string `json:"preloadedOSImageName,omitempty"`

// DeprecatedAPIVIP is the virtual IP address for the api endpoint
// Deprecated: use APIVIPs
//
Expand Down

0 comments on commit 8032a54

Please sign in to comment.