Skip to content

Commit 8032a54

Browse files
Merge pull request #9093 from nutanix-cloud-native/CORS-3706
CORS-3706: allow to install an OCP Nutanix cluster using PC's existing RHCOS image
2 parents fe4f41c + 909a82a commit 8032a54

File tree

8 files changed

+131
-41
lines changed

8 files changed

+131
-41
lines changed

data/data/install.openshift.io_installconfigs.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4177,6 +4177,11 @@ spec:
41774177
- message: type is immutable once set
41784178
rule: oldSelf == '' || self == oldSelf
41794179
type: object
4180+
preloadedOSImageName:
4181+
description: PreloadedOSImageName uses the named preloaded RHCOS
4182+
image from PC/PE, instead of create and upload a new image for
4183+
each cluster.
4184+
type: string
41804185
prismCentral:
41814186
description: PrismCentral is the endpoint (address and port) and
41824187
credentials to connect to the Prism Central. This serves as

pkg/asset/installconfig/nutanix/validation.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import (
44
"context"
55
"fmt"
66
"strconv"
7+
"strings"
78
"time"
89

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

14+
"github.com/openshift/installer/pkg/rhcos"
1115
"github.com/openshift/installer/pkg/types"
1216
nutanixtypes "github.com/openshift/installer/pkg/types/nutanix"
1317
)
@@ -60,6 +64,14 @@ func ValidateForProvisioning(ic *types.InstallConfig) error {
6064
}
6165
}
6266

67+
// validate PreloadedOSImageName if configured
68+
if p.PreloadedOSImageName != "" {
69+
err = validatePreloadedImage(ctx, nc, p)
70+
if err != nil {
71+
errList = append(errList, field.Invalid(parentPath.Child("preloadedOSImageName"), p.PreloadedOSImageName, fmt.Sprintf("fail to validate the preloaded rhcos image: %v", err)))
72+
}
73+
}
74+
6375
// validate each FailureDomain configuration
6476
for _, fd := range p.FailureDomains {
6577
// validate whether the prism element with the UUID exists
@@ -101,3 +113,61 @@ func ValidateForProvisioning(ic *types.InstallConfig) error {
101113

102114
return errList.ToAggregate()
103115
}
116+
117+
func validatePreloadedImage(ctx context.Context, nc *nutanixclientv3.Client, p *nutanixtypes.Platform) error {
118+
// retrieve the rhcos release version
119+
rhcosStream, err := rhcos.FetchCoreOSBuild(ctx)
120+
if err != nil {
121+
return err
122+
}
123+
124+
arch, ok := rhcosStream.Architectures["x86_64"]
125+
if !ok {
126+
return fmt.Errorf("unable to find the x86_64 rhcos architecture")
127+
}
128+
artifacts, ok := arch.Artifacts["nutanix"]
129+
if !ok {
130+
return fmt.Errorf("unable to find the x86_64 nutanix rhcos artifacts")
131+
}
132+
rhcosReleaseVersion := artifacts.Release
133+
134+
// retrieve the rhcos version number from rhcosReleaseVersion
135+
rhcosVerNum, err := strconv.Atoi(strings.Split(rhcosReleaseVersion, ".")[0])
136+
if err != nil {
137+
return fmt.Errorf("failed to get the rhcos image version number from the version string %s: %w", rhcosReleaseVersion, err)
138+
}
139+
140+
// retrieve the rhcos version number from the preloaded image object
141+
imgUUID, err := nutanixtypes.FindImageUUIDByName(ctx, nc, p.PreloadedOSImageName)
142+
if err != nil {
143+
return err
144+
}
145+
imgResp, err := nc.V3.GetImage(ctx, *imgUUID)
146+
if err != nil {
147+
return fmt.Errorf("failed to retrieve the rhcos image with uuid %s: %w", *imgUUID, err)
148+
}
149+
imgSource := *imgResp.Status.Resources.SourceURI
150+
151+
si := strings.LastIndex(imgSource, "/rhcos-")
152+
if si < 0 {
153+
return fmt.Errorf("failed to get the rhcos image version from the preloaded image %s object's source_uri %s", p.PreloadedOSImageName, imgSource)
154+
}
155+
verStr := strings.Split(imgSource[si+7:], ".")[0]
156+
imgVerNum, err := strconv.Atoi(verStr)
157+
if err != nil {
158+
return fmt.Errorf("failed to get the rhcos image version number from the version string %s: %w", verStr, err)
159+
}
160+
161+
// verify that the image version numbers are compactible
162+
versionDiff := rhcosVerNum - imgVerNum
163+
switch {
164+
case versionDiff < 0:
165+
return fmt.Errorf("the preloaded image's rhcos version: %v is too many revisions ahead the installer bundled rhcos version: %v", imgVerNum, rhcosVerNum)
166+
case versionDiff >= 2:
167+
return fmt.Errorf("the preloaded image's rhcos version: %v is too many revisions behind the installer bundled rhcos version: %v", imgVerNum, rhcosVerNum)
168+
case versionDiff == 1:
169+
logrus.Warnf("the preloaded image's rhcos version: %v is behind the installer bundled rhcos version: %v, installation may fail", imgVerNum, rhcosVerNum)
170+
}
171+
172+
return nil
173+
}

pkg/asset/machines/clusterapi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ func (c *ClusterAPI) Generate(ctx context.Context, dependencies asset.Parents) e
452452
return fmt.Errorf("failed to generate Cluster API machine manifests for control-plane: %w", err)
453453
}
454454
pool.Platform.Nutanix = &mpool
455-
templateName := nutanixtypes.RHCOSImageName(clusterID.InfraID)
455+
templateName := nutanixtypes.RHCOSImageName(ic.Platform.Nutanix, clusterID.InfraID)
456456

457457
c.FileList, err = nutanixcapi.GenerateMachines(clusterID.InfraID, ic, &pool, templateName, "master")
458458
if err != nil {

pkg/asset/machines/master.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ func (m *Master) Generate(ctx context.Context, dependencies asset.Parents) error
502502
return errors.Wrap(err, "failed to create master machine objects")
503503
}
504504
pool.Platform.Nutanix = &mpool
505-
templateName := nutanixtypes.RHCOSImageName(clusterID.InfraID)
505+
templateName := nutanixtypes.RHCOSImageName(ic.Platform.Nutanix, clusterID.InfraID)
506506

507507
machines, controlPlaneMachineSet, err = nutanix.Machines(clusterID.InfraID, ic, &pool, templateName, "master", masterUserDataSecretName)
508508
if err != nil {

pkg/asset/machines/worker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ func (w *Worker) Generate(ctx context.Context, dependencies asset.Parents) error
652652
return errors.Wrap(err, "failed to create worker machine objects")
653653
}
654654
pool.Platform.Nutanix = &mpool
655-
imageName := nutanixtypes.RHCOSImageName(clusterID.InfraID)
655+
imageName := nutanixtypes.RHCOSImageName(ic.Platform.Nutanix, clusterID.InfraID)
656656

657657
sets, err := nutanix.MachineSets(clusterID.InfraID, ic, &pool, imageName, "worker", workerUserDataSecretName)
658658
if err != nil {

pkg/infrastructure/nutanix/clusterapi/clusterapi.go

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -77,45 +77,49 @@ func (p Provider) PreProvision(ctx context.Context, in infracapi.PreProvisionInp
7777
}
7878
logrus.Infof("created the category value %q with name %q", *respv.Value, *respv.Name)
7979

80-
// upload the rhcos image.
81-
imgName := nutanixtypes.RHCOSImageName(in.InfraID)
82-
imgURI := in.RhcosImage.ControlPlane
83-
imgReq := &nutanixclientv3.ImageIntentInput{}
84-
imgSpec := &nutanixclientv3.Image{
85-
Name: &imgName,
86-
Description: ptr.To("Created By OpenShift Installer"),
87-
Resources: &nutanixclientv3.ImageResources{
88-
ImageType: ptr.To("DISK_IMAGE"),
89-
SourceURI: &imgURI,
90-
},
91-
}
92-
imgReq.Spec = imgSpec
93-
imgMeta := &nutanixclientv3.Metadata{
94-
Kind: ptr.To("image"),
95-
Categories: map[string]string{categoryKey: nutanixtypes.CategoryValueOwned},
96-
}
97-
imgReq.Metadata = imgMeta
98-
respi, err := nutanixCl.V3.CreateImage(ctx, imgReq)
99-
if err != nil {
100-
return fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
101-
}
102-
imgUUID := *respi.Metadata.UUID
103-
logrus.Infof("creating the rhcos image %s (uuid: %s).", imgName, imgUUID)
80+
if ic.Nutanix.PreloadedOSImageName != "" {
81+
logrus.Infof("Using the existing rhcos image %q in PC", in.InstallConfig.Config.Nutanix.PreloadedOSImageName)
82+
} else {
83+
// upload the rhcos image.
84+
imgName := nutanixtypes.RHCOSImageName(in.InstallConfig.Config.Nutanix, in.InfraID)
85+
imgURI := in.RhcosImage.ControlPlane
86+
imgReq := &nutanixclientv3.ImageIntentInput{}
87+
imgSpec := &nutanixclientv3.Image{
88+
Name: &imgName,
89+
Description: ptr.To("Created By OpenShift Installer"),
90+
Resources: &nutanixclientv3.ImageResources{
91+
ImageType: ptr.To("DISK_IMAGE"),
92+
SourceURI: &imgURI,
93+
},
94+
}
95+
imgReq.Spec = imgSpec
96+
imgMeta := &nutanixclientv3.Metadata{
97+
Kind: ptr.To("image"),
98+
Categories: map[string]string{categoryKey: nutanixtypes.CategoryValueOwned},
99+
}
100+
imgReq.Metadata = imgMeta
101+
respi, err := nutanixCl.V3.CreateImage(ctx, imgReq)
102+
if err != nil {
103+
return fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
104+
}
105+
imgUUID := *respi.Metadata.UUID
106+
logrus.Infof("creating the rhcos image %s (uuid: %s).", imgName, imgUUID)
104107

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

108-
// Wait till the image creation task is successed.
109-
if err = nutanixtypes.WaitForTask(nutanixCl.V3, taskUUID); err != nil {
110-
e1 := fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
111-
logrus.Error(e1)
112-
return e1
111+
// Wait till the image creation task is successed.
112+
if err = nutanixtypes.WaitForTask(nutanixCl.V3, taskUUID); err != nil {
113+
e1 := fmt.Errorf("failed to create the rhcos image %q: %w", imgName, err)
114+
logrus.Error(e1)
115+
return e1
116+
}
117+
logrus.Infof("created and uploaded the rhcos image data %s (uuid: %s)", imgName, imgUUID)
118+
} else {
119+
err = fmt.Errorf("failed to convert the task UUID %v to string", respi.Status.ExecutionContext.TaskUUID)
120+
logrus.Errorf(err.Error())
121+
return err
113122
}
114-
logrus.Infof("created and uploaded the rhcos image data %s (uuid: %s)", imgName, imgUUID)
115-
} else {
116-
err = fmt.Errorf("failed to convert the task UUID %v to string", respi.Status.ExecutionContext.TaskUUID)
117-
logrus.Errorf(err.Error())
118-
return err
119123
}
120124

121125
return nil

pkg/types/nutanix/helpers.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,13 @@ func getTaskStatus(clientV3 nutanixclientv3.Service, taskUUID string) (string, e
190190
}
191191

192192
// RHCOSImageName is the unique image name for a given cluster.
193-
func RHCOSImageName(infraID string) string {
194-
return fmt.Sprintf("%s-rhcos", infraID)
193+
func RHCOSImageName(p *Platform, infraID string) string {
194+
imgName := p.PreloadedOSImageName
195+
if imgName == "" {
196+
imgName = fmt.Sprintf("%s-rhcos", infraID)
197+
}
198+
199+
return imgName
195200
}
196201

197202
// CategoryKey returns the cluster specific category key name.

pkg/types/nutanix/platform.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ type Platform struct {
2828
// +optional
2929
ClusterOSImage string `json:"clusterOSImage,omitempty"`
3030

31+
// PreloadedOSImageName uses the named preloaded RHCOS image from PC/PE,
32+
// instead of create and upload a new image for each cluster.
33+
//
34+
// +optional
35+
PreloadedOSImageName string `json:"preloadedOSImageName,omitempty"`
36+
3137
// DeprecatedAPIVIP is the virtual IP address for the api endpoint
3238
// Deprecated: use APIVIPs
3339
//

0 commit comments

Comments
 (0)