From 3205b00252b8e4898c44412bf4bfd9fa44a6b21a Mon Sep 17 00:00:00 2001 From: Sid Shukla Date: Fri, 14 Jan 2022 17:14:03 +0530 Subject: [PATCH 1/5] Add Nutanix specific data structures in pkg/types/nutanix - Create the directory for nutanix specific types in pkg/types - Create the OWNERS file - Add Nutanix-specific structs i.e. MachinePool, Platform, Metadata, and OSDisk --- pkg/types/clustermetadata.go | 5 +++ pkg/types/installconfig.go | 1 + pkg/types/machinepools.go | 6 ++++ pkg/types/nutanix/OWNERS | 7 ++++ pkg/types/nutanix/doc.go | 6 ++++ pkg/types/nutanix/machinepool.go | 57 ++++++++++++++++++++++++++++++++ pkg/types/nutanix/metadata.go | 13 ++++++++ pkg/types/nutanix/platform.go | 50 ++++++++++++++++++++++++++++ 8 files changed, 145 insertions(+) create mode 100644 pkg/types/nutanix/OWNERS create mode 100644 pkg/types/nutanix/doc.go create mode 100644 pkg/types/nutanix/machinepool.go create mode 100644 pkg/types/nutanix/metadata.go create mode 100644 pkg/types/nutanix/platform.go diff --git a/pkg/types/clustermetadata.go b/pkg/types/clustermetadata.go index 5c6dcc2d03f..40ffca921e1 100644 --- a/pkg/types/clustermetadata.go +++ b/pkg/types/clustermetadata.go @@ -8,6 +8,7 @@ import ( "github.com/openshift/installer/pkg/types/gcp" "github.com/openshift/installer/pkg/types/ibmcloud" "github.com/openshift/installer/pkg/types/libvirt" + "github.com/openshift/installer/pkg/types/nutanix" "github.com/openshift/installer/pkg/types/openstack" "github.com/openshift/installer/pkg/types/ovirt" "github.com/openshift/installer/pkg/types/powervs" @@ -39,6 +40,7 @@ type ClusterPlatformMetadata struct { Ovirt *ovirt.Metadata `json:"ovirt,omitempty"` PowerVS *powervs.Metadata `json:"powervs,omitempty"` VSphere *vsphere.Metadata `json:"vsphere,omitempty"` + Nutanix *nutanix.Metadata `json:"nutanix,omitempty"` } // Platform returns a string representation of the platform @@ -81,5 +83,8 @@ func (cpm *ClusterPlatformMetadata) Platform() string { if cpm.VSphere != nil { return vsphere.Name } + if cpm.Nutanix != nil { + return nutanix.Name + } return "" } diff --git a/pkg/types/installconfig.go b/pkg/types/installconfig.go index e18c1fab253..3b19a6000c1 100644 --- a/pkg/types/installconfig.go +++ b/pkg/types/installconfig.go @@ -40,6 +40,7 @@ var ( azure.Name, gcp.Name, ibmcloud.Name, + nutanix.Name, openstack.Name, ovirt.Name, powervs.Name, diff --git a/pkg/types/machinepools.go b/pkg/types/machinepools.go index f3ea6602293..bc9a8cd8530 100644 --- a/pkg/types/machinepools.go +++ b/pkg/types/machinepools.go @@ -8,6 +8,7 @@ import ( "github.com/openshift/installer/pkg/types/gcp" "github.com/openshift/installer/pkg/types/ibmcloud" "github.com/openshift/installer/pkg/types/libvirt" + "github.com/openshift/installer/pkg/types/nutanix" "github.com/openshift/installer/pkg/types/openstack" "github.com/openshift/installer/pkg/types/ovirt" "github.com/openshift/installer/pkg/types/powervs" @@ -104,6 +105,9 @@ type MachinePoolPlatform struct { // PowerVS is the configuration used when installing on IBM Power VS. PowerVS *powervs.MachinePool `json:"powervs,omitempty"` + + // Nutanix is the configuration used when installing on Nutanix. + Nutanix *nutanix.MachinePool `json:"nutanix,omitempty"` } // Name returns a string representation of the platform (e.g. "aws" if @@ -135,6 +139,8 @@ func (p *MachinePoolPlatform) Name() string { return ovirt.Name case p.PowerVS != nil: return powervs.Name + case p.Nutanix != nil: + return nutanix.Name default: return "" } diff --git a/pkg/types/nutanix/OWNERS b/pkg/types/nutanix/OWNERS new file mode 100644 index 00000000000..5bd30cae915 --- /dev/null +++ b/pkg/types/nutanix/OWNERS @@ -0,0 +1,7 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# This file just uses aliases defined in OWNERS_ALIASES. + +approvers: + - nutanix-approvers +reviewers: + - nutanix-reviewers diff --git a/pkg/types/nutanix/doc.go b/pkg/types/nutanix/doc.go new file mode 100644 index 00000000000..96939d2566c --- /dev/null +++ b/pkg/types/nutanix/doc.go @@ -0,0 +1,6 @@ +// Package nutanix contains Nutanix-specific structures for installer +// configuration and management. +package nutanix + +// Name is the platform in the package +const Name = "nutanix" diff --git a/pkg/types/nutanix/machinepool.go b/pkg/types/nutanix/machinepool.go new file mode 100644 index 00000000000..e2469eae035 --- /dev/null +++ b/pkg/types/nutanix/machinepool.go @@ -0,0 +1,57 @@ +package nutanix + +// MachinePool stores the configuration for a machine pool installed +// on Nutanix. +type MachinePool struct { + // NumCPUs is the total number of virtual processor cores to assign a vm. + // + // +optional + NumCPUs int64 `json:"cpus,omitempty"` + + // NumCoresPerSocket is the number of cores per socket in a vm. The number + // of vCPUs on the vm will be NumCPUs/NumCoresPerSocket. + // + // +optional + NumCoresPerSocket int64 `json:"coresPerSocket,omitempty"` + + // Memory is the size of a VM's memory in MiB. + // + // +optional + MemoryMiB int64 `json:"memoryMiB,omitempty"` + + // OSDisk defines the storage for instance. + // + // +optional + OSDisk `json:"osDisk,omitempty"` +} + +// OSDisk defines the disk for a virtual machine. +type OSDisk struct { + // DiskSizeMiB defines the size of disk in MiB. + // + // +optional + DiskSizeMiB int64 `json:"diskSizeMiB,omitempty"` +} + +// Set sets the values from `required` to `p`. +func (p *MachinePool) Set(required *MachinePool) { + if required == nil || p == nil { + return + } + + if required.NumCPUs != 0 { + p.NumCPUs = required.NumCPUs + } + + if required.NumCoresPerSocket != 0 { + p.NumCoresPerSocket = required.NumCoresPerSocket + } + + if required.MemoryMiB != 0 { + p.MemoryMiB = required.MemoryMiB + } + + if required.OSDisk.DiskSizeMiB != 0 { + p.OSDisk.DiskSizeMiB = required.OSDisk.DiskSizeMiB + } +} diff --git a/pkg/types/nutanix/metadata.go b/pkg/types/nutanix/metadata.go new file mode 100644 index 00000000000..c3677cab591 --- /dev/null +++ b/pkg/types/nutanix/metadata.go @@ -0,0 +1,13 @@ +package nutanix + +// Metadata contains Nutanix metadata (e.g. for uninstalling the cluster). +type Metadata struct { + // PrismCentral is the domain name or IP address of the Prism Central. + PrismCentral string `json:"prismCentral"` + // Username is the name of the user to use to connect to the Prism Central. + Username string `json:"username"` + // Password is the password for the user to use to connect to the Prism Central. + Password string `json:"password"` + // Port is the port used to connect to the Prism Central. + Port string `json:"port"` +} diff --git a/pkg/types/nutanix/platform.go b/pkg/types/nutanix/platform.go new file mode 100644 index 00000000000..810de165824 --- /dev/null +++ b/pkg/types/nutanix/platform.go @@ -0,0 +1,50 @@ +package nutanix + +// Platform stores any global configuration used for Nutanix platforms. +type Platform struct { + // PrismCentral is the domain name or IP address of the Prism Central. + PrismCentral string `json:"prismCentral"` + + // Port is the port to use to connect to the Prism Central. + Port string `json:"port"` + + // Username is the name of the user to use to connect to the Prism Central. + Username string `json:"username"` + + // Password is the password for the user to use to connect to the Prism Central. + Password string `json:"password"` + + // PrismElementUUID is the UUID of the Prism Element cluster to use in the Prism Central. + PrismElementUUID string `json:"prismElementUUID"` + + // DefaultStorageContainer is the default datastore to use for provisioning volumes. + DefaultStorageContainer string `json:"defaultStorageContainer"` + + // ClusterOSImage overrides the url provided in rhcos.json to download the RHCOS Image + // + // +optional + ClusterOSImage string `json:"clusterOSImage,omitempty"` + + // APIVIP is the virtual IP address for the api endpoint + // + // +kubebuilder:validation:format=ip + // +optional + APIVIP string `json:"apiVIP,omitempty"` + + // IngressVIP is the virtual IP address for ingress + // + // +kubebuilder:validation:format=ip + // +optional + IngressVIP string `json:"ingressVIP,omitempty"` + + // DefaultMachinePlatform is the default configuration used when + // installing on Nutanix for machine pools which do not define their own + // platform configuration. + // +optional + DefaultMachinePlatform *MachinePool `json:"defaultMachinePlatform,omitempty"` + + // SubnetUUID specifies the UUID of the subnet to be used by the cluster. + // + // +optional + SubnetUUID string `json:"subnetUUID,omitempty"` +} From 3e1a3b6d11aa4cc07b3297dbb48f423536d62209 Mon Sep 17 00:00:00 2001 From: Sid Shukla Date: Fri, 14 Jan 2022 17:35:11 +0530 Subject: [PATCH 2/5] Add the package defaults for Nutanix platform This enables us to set defaults for nutanix later down the line. --- pkg/types/defaults/installconfig.go | 4 +++ pkg/types/installconfig.go | 7 +++++ pkg/types/nutanix/defaults/platform.go | 8 +++++ pkg/types/nutanix/defaults/platform_test.go | 34 +++++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 pkg/types/nutanix/defaults/platform.go create mode 100644 pkg/types/nutanix/defaults/platform_test.go diff --git a/pkg/types/defaults/installconfig.go b/pkg/types/defaults/installconfig.go index 720b82b98d2..6661a2f4181 100644 --- a/pkg/types/defaults/installconfig.go +++ b/pkg/types/defaults/installconfig.go @@ -12,6 +12,7 @@ import ( ibmclouddefaults "github.com/openshift/installer/pkg/types/ibmcloud/defaults" libvirtdefaults "github.com/openshift/installer/pkg/types/libvirt/defaults" nonedefaults "github.com/openshift/installer/pkg/types/none/defaults" + nutanixdefaults "github.com/openshift/installer/pkg/types/nutanix/defaults" openstackdefaults "github.com/openshift/installer/pkg/types/openstack/defaults" ovirtdefaults "github.com/openshift/installer/pkg/types/ovirt/defaults" powervsdefaults "github.com/openshift/installer/pkg/types/powervs/defaults" @@ -110,5 +111,8 @@ func SetInstallConfigDefaults(c *types.InstallConfig) { powervsdefaults.SetPlatformDefaults(c.Platform.PowerVS) case c.Platform.None != nil: nonedefaults.SetPlatformDefaults(c.Platform.None) + case c.Platform.Nutanix != nil: + nutanixdefaults.SetPlatformDefaults(c.Platform.Nutanix) } + } diff --git a/pkg/types/installconfig.go b/pkg/types/installconfig.go index 3b19a6000c1..e3512136051 100644 --- a/pkg/types/installconfig.go +++ b/pkg/types/installconfig.go @@ -16,6 +16,7 @@ import ( "github.com/openshift/installer/pkg/types/ibmcloud" "github.com/openshift/installer/pkg/types/libvirt" "github.com/openshift/installer/pkg/types/none" + "github.com/openshift/installer/pkg/types/nutanix" "github.com/openshift/installer/pkg/types/openstack" "github.com/openshift/installer/pkg/types/ovirt" "github.com/openshift/installer/pkg/types/powervs" @@ -226,6 +227,10 @@ type Platform struct { // Ovirt is the configuration used when installing on oVirt. // +optional Ovirt *ovirt.Platform `json:"ovirt,omitempty"` + + // Nutanix is the configuration used when installing on Nutanix. + // +optional + Nutanix *nutanix.Platform `json:"nutanix,omitempty"` } // Name returns a string representation of the platform (e.g. "aws" if @@ -259,6 +264,8 @@ func (p *Platform) Name() string { return ovirt.Name case p.PowerVS != nil: return powervs.Name + case p.Nutanix != nil: + return nutanix.Name default: return "" } diff --git a/pkg/types/nutanix/defaults/platform.go b/pkg/types/nutanix/defaults/platform.go new file mode 100644 index 00000000000..e1b48b0e908 --- /dev/null +++ b/pkg/types/nutanix/defaults/platform.go @@ -0,0 +1,8 @@ +package defaults + +import ( + "github.com/openshift/installer/pkg/types/nutanix" +) + +// SetPlatformDefaults sets the defaults for the platform. +func SetPlatformDefaults(p *nutanix.Platform) {} diff --git a/pkg/types/nutanix/defaults/platform_test.go b/pkg/types/nutanix/defaults/platform_test.go new file mode 100644 index 00000000000..ecaab856d1e --- /dev/null +++ b/pkg/types/nutanix/defaults/platform_test.go @@ -0,0 +1,34 @@ +package defaults + +import ( + "testing" + + "github.com/openshift/installer/pkg/types/nutanix" + "github.com/stretchr/testify/assert" +) + +const testClusterName = "test-cluster" + +func defaultPlatform() *nutanix.Platform { + return &nutanix.Platform{} +} + +func TestSetPlatformDefaults(t *testing.T) { + cases := []struct { + name string + platform *nutanix.Platform + expected *nutanix.Platform + }{ + { + name: "empty", + platform: &nutanix.Platform{}, + expected: defaultPlatform(), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + SetPlatformDefaults(tc.platform) + assert.Equal(t, tc.expected, tc.platform, "unexpected platform") + }) + } +} From 88fdcce9e1b13e1fed3cb4d5da7e436f7803e229 Mon Sep 17 00:00:00 2001 From: Sid Shukla Date: Thu, 10 Feb 2022 19:50:06 +0530 Subject: [PATCH 3/5] Add validations for Nutanix platform structs Add validations and tests for validations in pkg/types/nutanix/validation. --- pkg/types/nutanix/validation/machinepool.go | 28 +++ .../nutanix/validation/machinepool_test.go | 67 +++++++ pkg/types/nutanix/validation/platform.go | 70 ++++++++ pkg/types/nutanix/validation/platform_test.go | 167 ++++++++++++++++++ pkg/types/validation/installconfig.go | 7 + pkg/types/validation/installconfig_test.go | 39 +++- 6 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 pkg/types/nutanix/validation/machinepool.go create mode 100644 pkg/types/nutanix/validation/machinepool_test.go create mode 100644 pkg/types/nutanix/validation/platform.go create mode 100644 pkg/types/nutanix/validation/platform_test.go diff --git a/pkg/types/nutanix/validation/machinepool.go b/pkg/types/nutanix/validation/machinepool.go new file mode 100644 index 00000000000..4ea34480af4 --- /dev/null +++ b/pkg/types/nutanix/validation/machinepool.go @@ -0,0 +1,28 @@ +package validation + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/openshift/installer/pkg/types/nutanix" +) + +// ValidateMachinePool checks that the specified machine pool is valid. +func ValidateMachinePool(p *nutanix.MachinePool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if p.DiskSizeMiB < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("diskSizeMiB"), p.DiskSizeMiB, "storage disk size must be positive")) + } + if p.MemoryMiB < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("memoryMiB"), p.MemoryMiB, "memory size must be positive")) + } + if p.NumCPUs < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("cpus"), p.NumCPUs, "number of CPUs must be positive")) + } + if p.NumCoresPerSocket < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("coresPerSocket"), p.NumCoresPerSocket, "cores per socket must be positive")) + } + if p.NumCoresPerSocket >= 0 && p.NumCPUs >= 0 && p.NumCoresPerSocket > p.NumCPUs { + allErrs = append(allErrs, field.Invalid(fldPath.Child("coresPerSocket"), p.NumCoresPerSocket, "cores per socket must be less than number of CPUs")) + } + return allErrs +} diff --git a/pkg/types/nutanix/validation/machinepool_test.go b/pkg/types/nutanix/validation/machinepool_test.go new file mode 100644 index 00000000000..fcdebc87ee9 --- /dev/null +++ b/pkg/types/nutanix/validation/machinepool_test.go @@ -0,0 +1,67 @@ +package validation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/openshift/installer/pkg/types/nutanix" +) + +func TestValidateMachinePool(t *testing.T) { + cases := []struct { + name string + pool *nutanix.MachinePool + expectedErrMsg string + }{ + { + name: "empty", + pool: &nutanix.MachinePool{}, + expectedErrMsg: "", + }, { + name: "negative disk size", + pool: &nutanix.MachinePool{ + OSDisk: nutanix.OSDisk{ + DiskSizeMiB: -1, + }, + }, + expectedErrMsg: `^test-path\.diskSizeMiB: Invalid value: -1: storage disk size must be positive$`, + }, { + name: "negative CPUs", + pool: &nutanix.MachinePool{ + NumCPUs: -1, + }, + expectedErrMsg: `^test-path\.cpus: Invalid value: -1: number of CPUs must be positive$`, + }, { + name: "negative cores", + pool: &nutanix.MachinePool{ + NumCoresPerSocket: -1, + }, + expectedErrMsg: `^test-path\.coresPerSocket: Invalid value: -1: cores per socket must be positive$`, + }, { + name: "negative memory", + pool: &nutanix.MachinePool{ + MemoryMiB: -1, + }, + expectedErrMsg: `^test-path\.memoryMiB: Invalid value: -1: memory size must be positive$`, + }, { + name: "less CPUs than cores per socket", + pool: &nutanix.MachinePool{ + NumCPUs: 1, + NumCoresPerSocket: 8, + }, + expectedErrMsg: `^test-path\.coresPerSocket: Invalid value: 8: cores per socket must be less than number of CPUs$`, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := ValidateMachinePool(tc.pool, field.NewPath("test-path")).ToAggregate() + if tc.expectedErrMsg == "" { + assert.NoError(t, err) + } else { + assert.Regexp(t, tc.expectedErrMsg, err) + } + }) + } +} diff --git a/pkg/types/nutanix/validation/platform.go b/pkg/types/nutanix/validation/platform.go new file mode 100644 index 00000000000..6f7f4ff8083 --- /dev/null +++ b/pkg/types/nutanix/validation/platform.go @@ -0,0 +1,70 @@ +package validation + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/openshift/installer/pkg/types/nutanix" + "github.com/openshift/installer/pkg/validate" +) + +// ValidatePlatform checks that the specified platform is valid. +// TODO(nutanix): Revisit for further expanding the validation logic +func ValidatePlatform(p *nutanix.Platform, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(p.PrismCentral) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("prismCentral"), "must specify the Prism Central")) + } + if len(p.PrismElementUUID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("prismElement"), "must specify the Prism Element")) + } + if len(p.Username) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("username"), "must specify the username")) + } + if len(p.Password) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("password"), "must specify the password")) + } + if len(p.Port) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("port"), "must specify the port")) + } + if len(p.DefaultStorageContainer) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("defaultStorageContainer"), "must specify the default storage container")) + } + if len(p.SubnetUUID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("subnet"), "must specify the subnet")) + } + if len(p.PrismCentral) != 0 { + if err := validate.Host(p.PrismCentral); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("prismCentral"), p.PrismCentral, "must be the domain name or IP address of the Prism Central")) + } + } + + // If all VIPs are empty, skip IP validation. All VIPs are required to be defined together. + if p.APIVIP != "" || p.IngressVIP != "" { + allErrs = append(allErrs, validateVIPs(p, fldPath)...) + } + + return allErrs +} + +// validateVIPs checks that all required VIPs are provided and are valid IP addresses. +func validateVIPs(p *nutanix.Platform, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(p.APIVIP) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("apiVIP"), "must specify a VIP for the API")) + } else if err := validate.IP(p.APIVIP); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVIP"), p.APIVIP, err.Error())) + } + + if len(p.IngressVIP) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("ingressVIP"), "must specify a VIP for Ingress")) + } else if err := validate.IP(p.IngressVIP); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("ingressVIP"), p.IngressVIP, err.Error())) + } + + if p.APIVIP == p.IngressVIP { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVIP"), p.APIVIP, "IPs for both API and Ingress should not be the same")) + } + + return allErrs +} diff --git a/pkg/types/nutanix/validation/platform_test.go b/pkg/types/nutanix/validation/platform_test.go new file mode 100644 index 00000000000..ccfa29b39a5 --- /dev/null +++ b/pkg/types/nutanix/validation/platform_test.go @@ -0,0 +1,167 @@ +package validation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/openshift/installer/pkg/types/nutanix" +) + +func validPlatform() *nutanix.Platform { + return &nutanix.Platform{ + PrismCentral: "test-pc", + PrismElementUUID: "12992bc3-e919-454b-980e-8b51e217c9bd", + DefaultStorageContainer: "test-storage-container", + Username: "test-username", + Password: "test-password", + SubnetUUID: "b06179c8-dea3-4f8e-818a-b2e88fbc2201", + Port: "8080", + } +} + +func TestValidatePlatform(t *testing.T) { + cases := []struct { + name string + platform *nutanix.Platform + expectedError string + }{ + { + name: "minimal", + platform: validPlatform(), + }, + { + name: "missing Prism Central name", + platform: func() *nutanix.Platform { + p := validPlatform() + p.PrismCentral = "" + return p + }(), + expectedError: `^test-path\.prismCentral: Required value: must specify the Prism Central$`, + }, + { + name: "missing username", + platform: func() *nutanix.Platform { + p := validPlatform() + p.Username = "" + return p + }(), + expectedError: `^test-path\.username: Required value: must specify the username$`, + }, + { + name: "missing password", + platform: func() *nutanix.Platform { + p := validPlatform() + p.Password = "" + return p + }(), + expectedError: `^test-path\.password: Required value: must specify the password$`, + }, + { + name: "missing prism element", + platform: func() *nutanix.Platform { + p := validPlatform() + p.PrismElementUUID = "" + return p + }(), + expectedError: `^test-path\.prismElement: Required value: must specify the Prism Element$`, + }, + { + name: "missing default storage container", + platform: func() *nutanix.Platform { + p := validPlatform() + p.DefaultStorageContainer = "" + return p + }(), + expectedError: `^test-path\.defaultStorageContainer: Required value: must specify the default storage container$`, + }, + { + name: "valid VIPs", + platform: func() *nutanix.Platform { + p := validPlatform() + p.APIVIP = "192.168.111.2" + p.IngressVIP = "192.168.111.3" + return p + }(), + }, + { + name: "missing API VIP", + platform: func() *nutanix.Platform { + p := validPlatform() + p.APIVIP = "" + p.IngressVIP = "192.168.111.3" + return p + }(), + expectedError: `^test-path\.apiVIP: Required value: must specify a VIP for the API$`, + }, + { + name: "missing Ingress VIP", + platform: func() *nutanix.Platform { + p := validPlatform() + p.APIVIP = "192.168.111.2" + p.IngressVIP = "" + return p + }(), + expectedError: `^test-path\.ingressVIP: Required value: must specify a VIP for Ingress$`, + }, + { + name: "Invalid API VIP", + platform: func() *nutanix.Platform { + p := validPlatform() + p.APIVIP = "192.168.111" + p.IngressVIP = "192.168.111.2" + return p + }(), + expectedError: `^test-path\.apiVIP: Invalid value: "192.168.111": "192.168.111" is not a valid IP$`, + }, + { + name: "Invalid Ingress VIP", + platform: func() *nutanix.Platform { + p := validPlatform() + p.APIVIP = "192.168.111.1" + p.IngressVIP = "192.168.111" + return p + }(), + expectedError: `^test-path\.ingressVIP: Invalid value: "192.168.111": "192.168.111" is not a valid IP$`, + }, + { + name: "Same API and Ingress VIP", + platform: func() *nutanix.Platform { + p := validPlatform() + p.APIVIP = "192.168.111.1" + p.IngressVIP = "192.168.111.1" + return p + }(), + expectedError: `^test-path\.apiVIP: Invalid value: "192.168.111.1": IPs for both API and Ingress should not be the same$`, + }, + { + name: "Capital letters in Prism Central", + platform: func() *nutanix.Platform { + p := validPlatform() + p.PrismCentral = "tEsT-PrismCentral" + return p + }(), + expectedError: `^test-path\.prismCentral: Invalid value: "tEsT-PrismCentral": must be the domain name or IP address of the Prism Central$`, + }, + { + name: "URL as Prism Central", + platform: func() *nutanix.Platform { + p := validPlatform() + p.PrismCentral = "https://test-pc" + return p + }(), + expectedError: `^test-path\.prismCentral: Invalid value: "https://test-pc": must be the domain name or IP address of the Prism Central$`, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := ValidatePlatform(tc.platform, field.NewPath("test-path")).ToAggregate() + if tc.expectedError == "" { + assert.NoError(t, err) + } else { + assert.Regexp(t, tc.expectedError, err) + } + }) + } +} diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index a29158e4da5..a66553e8baf 100644 --- a/pkg/types/validation/installconfig.go +++ b/pkg/types/validation/installconfig.go @@ -36,6 +36,8 @@ import ( ibmcloudvalidation "github.com/openshift/installer/pkg/types/ibmcloud/validation" "github.com/openshift/installer/pkg/types/libvirt" libvirtvalidation "github.com/openshift/installer/pkg/types/libvirt/validation" + "github.com/openshift/installer/pkg/types/nutanix" + nutanixvalidation "github.com/openshift/installer/pkg/types/nutanix/validation" "github.com/openshift/installer/pkg/types/openstack" openstackvalidation "github.com/openshift/installer/pkg/types/openstack/validation" "github.com/openshift/installer/pkg/types/ovirt" @@ -509,6 +511,11 @@ func validatePlatform(platform *types.Platform, fldPath *field.Path, network *ty return ovirtvalidation.ValidatePlatform(platform.Ovirt, f) }) } + if platform.Nutanix != nil { + validate(nutanix.Name, platform.Nutanix, func(f *field.Path) field.ErrorList { + return nutanixvalidation.ValidatePlatform(platform.Nutanix, f) + }) + } return allErrs } diff --git a/pkg/types/validation/installconfig_test.go b/pkg/types/validation/installconfig_test.go index 42d9f347a3d..4cad0453b3b 100644 --- a/pkg/types/validation/installconfig_test.go +++ b/pkg/types/validation/installconfig_test.go @@ -21,6 +21,7 @@ import ( "github.com/openshift/installer/pkg/types/ibmcloud" "github.com/openshift/installer/pkg/types/libvirt" "github.com/openshift/installer/pkg/types/none" + "github.com/openshift/installer/pkg/types/nutanix" "github.com/openshift/installer/pkg/types/openstack" "github.com/openshift/installer/pkg/types/ovirt" "github.com/openshift/installer/pkg/types/powervs" @@ -162,6 +163,18 @@ func validOpenStackPlatform() *openstack.Platform { } } +func validNutanixPlatform() *nutanix.Platform { + return &nutanix.Platform{ + PrismCentral: "test-pc", + PrismElementUUID: "test-pe", + DefaultStorageContainer: "test-storage-container", + Username: "test-username", + Password: "test-password", + SubnetUUID: "test-subnet", + Port: "8080", + } +} + func validIPv4NetworkingConfig() *types.Networking { return &types.Networking{ NetworkType: "OpenShiftSDN", @@ -532,7 +545,7 @@ func TestValidateInstallConfig(t *testing.T) { c.Platform = types.Platform{} return c }(), - expectedError: `^platform: Invalid value: "": must specify one of the platforms \(alibabacloud, aws, azure, baremetal, gcp, ibmcloud, none, openstack, ovirt, powervs, vsphere\)$`, + expectedError: `^platform: Invalid value: "": must specify one of the platforms \(alibabacloud, aws, azure, baremetal, gcp, ibmcloud, none, nutanix, openstack, ovirt, powervs, vsphere\)$`, }, { name: "multiple platforms", @@ -563,7 +576,7 @@ func TestValidateInstallConfig(t *testing.T) { } return c }(), - expectedError: `^platform: Invalid value: "libvirt": must specify one of the platforms \(alibabacloud, aws, azure, baremetal, gcp, ibmcloud, none, openstack, ovirt, powervs, vsphere\)$`, + expectedError: `^platform: Invalid value: "libvirt": must specify one of the platforms \(alibabacloud, aws, azure, baremetal, gcp, ibmcloud, none, nutanix, openstack, ovirt, powervs, vsphere\)$`, }, { name: "invalid libvirt platform", @@ -575,7 +588,7 @@ func TestValidateInstallConfig(t *testing.T) { c.Platform.Libvirt.URI = "" return c }(), - expectedError: `^\[platform: Invalid value: "libvirt": must specify one of the platforms \(alibabacloud, aws, azure, baremetal, gcp, ibmcloud, none, openstack, ovirt, powervs, vsphere\), platform\.libvirt\.uri: Invalid value: "": invalid URI "" \(no scheme\)]$`, + expectedError: `^\[platform: Invalid value: "libvirt": must specify one of the platforms \(alibabacloud, aws, azure, baremetal, gcp, ibmcloud, none, nutanix, openstack, ovirt, powervs, vsphere\), platform\.libvirt\.uri: Invalid value: "": invalid URI "" \(no scheme\)]$`, }, { name: "valid none platform", @@ -1450,6 +1463,26 @@ func TestValidateInstallConfig(t *testing.T) { c.Publish = types.InternalPublishingStrategy return c }(), + }, { + name: "valid nutanix platform", + installConfig: func() *types.InstallConfig { + c := validInstallConfig() + c.Platform = types.Platform{ + Nutanix: validNutanixPlatform(), + } + return c + }(), + }, { + name: "invalid nutanix platform", + installConfig: func() *types.InstallConfig { + c := validInstallConfig() + c.Platform = types.Platform{ + Nutanix: validNutanixPlatform(), + } + c.Platform.Nutanix.PrismCentral = "" + return c + }(), + expectedError: `^platform\.nutanix\.prismCentral: Required value: must specify the Prism Central$`, }, { name: "valid baseline capability set", From 70a60cd5665162f88ce093e53276a2085345520a Mon Sep 17 00:00:00 2001 From: Sid Shukla Date: Fri, 11 Feb 2022 17:53:31 +0530 Subject: [PATCH 4/5] Add generated installconfigs Generated from running `hack/verify-codegen.sh` --- .../install.openshift.io_installconfigs.yaml | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index 08180c8613d..7311f97e0f7 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -374,6 +374,35 @@ spec: description: Libvirt is the configuration used when installing on libvirt. type: object + nutanix: + description: Nutanix is the configuration used when installing + on Nutanix. + properties: + coresPerSocket: + description: NumCoresPerSocket is the number of cores per + socket in a vm. The number of vCPUs on the vm will be + NumCPUs/NumCoresPerSocket. + format: int64 + type: integer + cpus: + description: NumCPUs is the total number of virtual processor + cores to assign a vm. + format: int64 + type: integer + memoryMiB: + description: Memory is the size of a VM's memory in MiB. + format: int64 + type: integer + osDisk: + description: OSDisk defines the storage for instance. + properties: + diskSizeMiB: + description: DiskSizeMiB defines the size of disk in + MiB. + format: int64 + type: integer + type: object + type: object openstack: description: OpenStack is the configuration used when installing on OpenStack. @@ -888,6 +917,33 @@ spec: description: Libvirt is the configuration used when installing on libvirt. type: object + nutanix: + description: Nutanix is the configuration used when installing + on Nutanix. + properties: + coresPerSocket: + description: NumCoresPerSocket is the number of cores per + socket in a vm. The number of vCPUs on the vm will be NumCPUs/NumCoresPerSocket. + format: int64 + type: integer + cpus: + description: NumCPUs is the total number of virtual processor + cores to assign a vm. + format: int64 + type: integer + memoryMiB: + description: Memory is the size of a VM's memory in MiB. + format: int64 + type: integer + osDisk: + description: OSDisk defines the storage for instance. + properties: + diskSizeMiB: + description: DiskSizeMiB defines the size of disk in MiB. + format: int64 + type: integer + type: object + type: object openstack: description: OpenStack is the configuration used when installing on OpenStack. @@ -2019,6 +2075,83 @@ spec: description: None is the empty configuration used when installing on an unsupported platform. type: object + nutanix: + description: Nutanix is the configuration used when installing on + Nutanix. + properties: + apiVIP: + description: APIVIP is the virtual IP address for the api endpoint + type: string + clusterOSImage: + description: ClusterOSImage overrides the url provided in rhcos.json + to download the RHCOS Image + type: string + defaultMachinePlatform: + description: DefaultMachinePlatform is the default configuration + used when installing on Nutanix for machine pools which do not + define their own platform configuration. + properties: + coresPerSocket: + description: NumCoresPerSocket is the number of cores per + socket in a vm. The number of vCPUs on the vm will be NumCPUs/NumCoresPerSocket. + format: int64 + type: integer + cpus: + description: NumCPUs is the total number of virtual processor + cores to assign a vm. + format: int64 + type: integer + memoryMiB: + description: Memory is the size of a VM's memory in MiB. + format: int64 + type: integer + osDisk: + description: OSDisk defines the storage for instance. + properties: + diskSizeMiB: + description: DiskSizeMiB defines the size of disk in MiB. + format: int64 + type: integer + type: object + type: object + defaultStorageContainer: + description: DefaultStorageContainer is the default datastore + to use for provisioning volumes. + type: string + ingressVIP: + description: IngressVIP is the virtual IP address for ingress + type: string + password: + description: Password is the password for the user to use to connect + to the Prism Central. + type: string + port: + description: Port is the port to use to connect to the Prism Central. + type: string + prismCentral: + description: PrismCentral is the domain name or IP address of + the Prism Central. + type: string + prismElementUUID: + description: PrismElementUUID is the UUID of the Prism Element + cluster to use in the Prism Central. + type: string + subnetUUID: + description: SubnetUUID specifies the UUID of the subnet to be + used by the cluster. + type: string + username: + description: Username is the name of the user to use to connect + to the Prism Central. + type: string + required: + - defaultStorageContainer + - password + - port + - prismCentral + - prismElementUUID + - username + type: object openstack: description: OpenStack is the configuration used when installing on OpenStack. From 06c7bc820c63a2fc9fd52e91d0875efb8a9c5066 Mon Sep 17 00:00:00 2001 From: Sid Shukla Date: Wed, 23 Feb 2022 19:33:38 +0100 Subject: [PATCH 5/5] Update printer_test.go Update Test_PrintFields to expect Nutanix platform. --- pkg/explain/printer_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/explain/printer_test.go b/pkg/explain/printer_test.go index 84f812694ed..9f6e6185bb4 100644 --- a/pkg/explain/printer_test.go +++ b/pkg/explain/printer_test.go @@ -111,6 +111,9 @@ func Test_PrintFields(t *testing.T) { none None is the empty configuration used when installing on an unsupported platform. + nutanix + Nutanix is the configuration used when installing on Nutanix. + openstack OpenStack is the configuration used when installing on OpenStack.