diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index 08180c8613d..59a0decdda8 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -2492,6 +2492,99 @@ spec: vCenter: description: VCenter is the domain name or IP address of the vCenter. type: string + vcenters: + description: VCenters slice is the configuration for the use of + Regions and Zones. Also deploying openshift cluster virtual + machines in multiple datacenters and clusters. + items: + description: VCenter stores the vCenter connection fields and + the Region slice that is used to create virtual machines in + various datacenters and clusters + properties: + password: + description: Password is the password for the user to use + to connect to the vCenter. + type: string + port: + description: Port is the vCenter API TCP port that the vCenter + SDK clients connect to. + type: integer + regions: + description: Regions slice is the configuration of the vCenter + datacenter and Zones that will be used in deployment of + virtual machines, cloud provider and tags. + items: + description: Region stores the name of the region, the + datacenter that defines the region and the Zones that + are a part of a particular region. + properties: + datacenter: + description: Datacenter is the name of the datacenter + to use in the vCenter. + type: string + name: + description: Name is the region name that will be + used for cloud provider and tag + type: string + zones: + description: Zones slice is the configuration of the + vCenter Resource Pool (optionally), Cluster, Network + and Datastore to deploy virtual machines. + items: + description: Zone stores the name of the zone, the + cluster associated with the zone. ResourcePool, + Network and Datastore is provided for virtual + machine deployment. + properties: + cluster: + description: Cluster is the name of the cluster + virtual machines will be cloned into. + type: string + datastore: + description: Datastore is the default datastore + to use for provisioning volumes. + type: string + name: + description: Name is the zone name that will + be used for the cloud provider and tag + type: string + network: + description: Network specifies the name of the + network to be used by the cluster. + type: string + resourcePool: + description: ResourcePool is the name of the + already configured Resource Pool to use when + deploying virtual machines. + type: string + required: + - cluster + - datastore + - name + - network + type: object + type: array + required: + - datacenter + - name + - zones + type: object + type: array + server: + description: Server is the domain name or IP address of + the vCenter. + type: string + user: + description: User is the name of the user to use to connect + to the vCenter. + type: string + required: + - password + - regions + - server + - user + type: object + type: array required: - datacenter - defaultDatastore diff --git a/pkg/asset/installconfig/vsphere/validation_test.go b/pkg/asset/installconfig/vsphere/validation_test.go index f19bd682939..8272c61cc06 100644 --- a/pkg/asset/installconfig/vsphere/validation_test.go +++ b/pkg/asset/installconfig/vsphere/validation_test.go @@ -18,6 +18,46 @@ var ( validCIDR = "10.0.0.0/16" ) +func validIPIZoningInstallConfig() *types.InstallConfig { + return &types.InstallConfig{ + Networking: &types.Networking{ + MachineNetwork: []types.MachineNetworkEntry{ + {CIDR: *ipnet.MustParseCIDR(validCIDR)}, + }, + }, + Publish: types.ExternalPublishingStrategy, + Platform: types.Platform{ + VSphere: &vsphere.Platform{ + Cluster: "valid_cluster", + Datacenter: "valid_dc", + DefaultDatastore: "valid_ds", + Network: "valid_network", + Password: "valid_password", + Username: "valid_username", + VCenter: "valid-vcenter", + APIVIP: "192.168.111.0", + IngressVIP: "192.168.111.1", + VCenters: []vsphere.VCenter{{ + Server: "valid-vcenter", + Password: "valid_password", + User: "valid_username", + Regions: []vsphere.Region{{ + Name: "valid_region_name", + Datacenter: "valid_dc", + Zones: []vsphere.Zone{{ + Name: "valid_zone_name", + Cluster: "valid_cluster", + Network: "valid_network", + ResourcePool: "valid_rp", + Datastore: "valid_ds", + }}, + }}, + }}, + }, + }, + } +} + func validIPIInstallConfig() *types.InstallConfig { return &types.InstallConfig{ Networking: &types.Networking{ @@ -52,6 +92,10 @@ func TestValidate(t *testing.T) { name: "valid IPI install config", installConfig: validIPIInstallConfig(), validationMethod: validateProvisioning, + }, { + name: "valid IPI zoning install config", + installConfig: validIPIZoningInstallConfig(), + validationMethod: validateProvisioning, }, { name: "invalid IPI - no network", installConfig: func() *types.InstallConfig { diff --git a/pkg/types/vsphere/platform.go b/pkg/types/vsphere/platform.go index b00eca7b5f4..b3c419db85e 100644 --- a/pkg/types/vsphere/platform.go +++ b/pkg/types/vsphere/platform.go @@ -72,4 +72,77 @@ type Platform struct { // specified, it will be set according to the default storage policy // of vsphere. DiskType DiskType `json:"diskType,omitempty"` + + // VCenters slice is the configuration for the use of Regions and Zones. Also + // deploying openshift cluster virtual machines in multiple datacenters and clusters. + // +optional + VCenters []VCenter `json:"vcenters,omitempty"` +} + +// VCenter stores the vCenter connection fields and the Region slice +// that is used to create virtual machines in various datacenters and clusters +type VCenter struct { + // Server is the domain name or IP address of the vCenter. + // +required + Server string `json:"server"` + + // User is the name of the user to use to connect to the vCenter. + // +required + User string `json:"user"` + + // Password is the password for the user to use to connect to the vCenter. + // +required + Password string `json:"password"` + + // Port is the vCenter API TCP port that the vCenter SDK clients connect to. + // +optional + Port uint `json:"port,omitempty"` + + // Regions slice is the configuration of the vCenter datacenter and Zones that + // will be used in deployment of virtual machines, cloud provider and tags. + // +required + Regions []Region `json:"regions"` +} + +// Region stores the name of the region, the datacenter that defines the region and the Zones +// that are a part of a particular region. +type Region struct { + + // Name is the region name that will be used for cloud provider and tag + // +required + Name string `json:"name"` + + // Datacenter is the name of the datacenter to use in the vCenter. + // +required + Datacenter string `json:"datacenter"` + + // Zones slice is the configuration of the vCenter Resource Pool (optionally), + // Cluster, Network and Datastore to deploy virtual machines. + // +required + Zones []Zone `json:"zones"` +} + +// Zone stores the name of the zone, the cluster associated with the zone. +// ResourcePool, Network and Datastore is provided for virtual machine deployment. +type Zone struct { + // Name is the zone name that will be used for the cloud provider and tag + // +required + Name string `json:"name"` + + // ResourcePool is the name of the already configured Resource Pool to use + // when deploying virtual machines. + // +optional + ResourcePool string `json:"resourcePool,omitempty"` + + // Cluster is the name of the cluster virtual machines will be cloned into. + // +required + Cluster string `json:"cluster"` + + // Network specifies the name of the network to be used by the cluster. + // +required + Network string `json:"network"` + + // Datastore is the default datastore to use for provisioning volumes. + // +required + Datastore string `json:"datastore"` } diff --git a/pkg/types/vsphere/validation/platform.go b/pkg/types/vsphere/validation/platform.go index 265773d28b3..b527810c10e 100644 --- a/pkg/types/vsphere/validation/platform.go +++ b/pkg/types/vsphere/validation/platform.go @@ -35,6 +35,11 @@ func ValidatePlatform(p *vsphere.Platform, fldPath *field.Path) field.ErrorList } } + // validate zoning + if len(p.VCenters) != 0 { + allErrs = append(allErrs, validateZoning(p, fldPath)...) + } + // If all VIPs are empty, skip IP validation. All VIPs are required to be defined together. if strings.Join([]string{p.APIVIP, p.IngressVIP}, "") != "" { allErrs = append(allErrs, validateVIPs(p, fldPath)...) @@ -69,6 +74,74 @@ func ValidateForProvisioning(p *vsphere.Platform, fldPath *field.Path) field.Err return allErrs } +func validateZoning(p *vsphere.Platform, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + vCentersFieldPath := fldPath.Child("vcenters") + regionsFieldPath := vCentersFieldPath.Child("regions") + zonesFieldPath := regionsFieldPath.Child("zones") + + // Check if the VCenters slice has entries + if len(p.VCenters) != 0 { + // We currently do not support more than one vCenter + if len(p.VCenters) > 1 { + allErrs = append(allErrs, field.Required(vCentersFieldPath, "must specify a single VCenters entry.")) + } + + for _, v := range p.VCenters { + if len(v.Server) != 0 { + if err := validate.Host(v.Server); err != nil { + allErrs = append(allErrs, field.Invalid(vCentersFieldPath.Child("server"), v.Server, "must be the domain name or IP address of the vCenter")) + } + } + if v.Port != 0 { + allErrs = append(allErrs, field.Required(vCentersFieldPath.Child("port"), "is currently not supported")) + } + if len(v.Server) == 0 { + allErrs = append(allErrs, field.Required(vCentersFieldPath.Child("server"), "must specify the name of the vCenter")) + } + if len(v.User) == 0 { + allErrs = append(allErrs, field.Required(vCentersFieldPath.Child("username"), "must specify the username")) + } + if len(v.Password) == 0 { + allErrs = append(allErrs, field.Required(vCentersFieldPath.Child("password"), "must specify the password")) + } + if len(v.Regions) == 0 { + allErrs = append(allErrs, field.Required(regionsFieldPath, "must specify the regions")) + } else { + for _, r := range v.Regions { + if len(r.Datacenter) == 0 { + allErrs = append(allErrs, field.Required(regionsFieldPath.Child("datacenter"), "must specify the datacenter")) + } + if len(r.Name) == 0 { + allErrs = append(allErrs, field.Required(regionsFieldPath.Child("name"), "must specify the region name")) + } + if len(r.Zones) == 0 { + allErrs = append(allErrs, field.Required(zonesFieldPath, "must specify the zones")) + } else { + for _, z := range r.Zones { + if len(z.Datastore) == 0 { + allErrs = append(allErrs, field.Required(zonesFieldPath.Child("datastore"), "must specify the datastore")) + } + if len(z.Network) == 0 { + allErrs = append(allErrs, field.Required(zonesFieldPath.Child("network"), "must specify a network")) + } + if len(z.Cluster) == 0 { + allErrs = append(allErrs, field.Required(zonesFieldPath.Child("cluster"), "must specify a cluster")) + } + if len(z.Name) == 0 { + allErrs = append(allErrs, field.Required(zonesFieldPath.Child("name"), "must specify a zone name")) + } + } + } + } + } + } + } + + return allErrs +} + // validateVIPs checks that all required VIPs are provided and are valid IP addresses. func validateVIPs(p *vsphere.Platform, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{}