github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/powervs/validation.go (about) 1 package powervs 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 "time" 9 10 "k8s.io/apimachinery/pkg/util/validation/field" 11 12 "github.com/openshift/installer/pkg/types" 13 powervstypes "github.com/openshift/installer/pkg/types/powervs" 14 ) 15 16 // Validate executes platform specific validation/ 17 func Validate(config *types.InstallConfig) error { 18 allErrs := field.ErrorList{} 19 20 if config.Platform.PowerVS == nil { 21 allErrs = append(allErrs, field.Required(field.NewPath("platform", "powervs"), "Power VS Validation requires a Power VS platform configuration.")) 22 } else { 23 if config.ControlPlane != nil { 24 fldPath := field.NewPath("controlPlane") 25 allErrs = append(allErrs, validateMachinePool(fldPath, config.ControlPlane)...) 26 } 27 for idx, compute := range config.Compute { 28 fldPath := field.NewPath("compute").Index(idx) 29 allErrs = append(allErrs, validateMachinePool(fldPath, &compute)...) 30 } 31 // Machine pool CIDR check 32 for i := range config.Networking.MachineNetwork { 33 // Each machine pool CIDR must have 24 significant bits (/24) 34 if bits, _ := config.Networking.MachineNetwork[i].CIDR.Mask.Size(); bits != 24 { 35 // If not, create an error displaying the CIDR in the install config vs the expectation (/24) 36 fldPath := field.NewPath("Networking") 37 allErrs = append(allErrs, field.Invalid(fldPath.Child("MachineNetwork").Child("CIDR"), (&config.Networking.MachineNetwork[i].CIDR).String(), "Machine Pool CIDR must be /24.")) 38 } 39 } 40 } 41 return allErrs.ToAggregate() 42 } 43 44 func validateMachinePool(fldPath *field.Path, machinePool *types.MachinePool) field.ErrorList { 45 allErrs := field.ErrorList{} 46 if machinePool.Architecture != "ppc64le" { 47 allErrs = append(allErrs, field.NotSupported(fldPath.Child("architecture"), machinePool.Architecture, []string{"ppc64le"})) 48 } 49 return allErrs 50 } 51 52 // ValidatePERAvailability ensures the target datacenter has PER enabled. 53 func ValidatePERAvailability(client API, ic *types.InstallConfig) error { 54 capabilities, err := client.GetDatacenterCapabilities(context.TODO(), ic.PowerVS.Zone) 55 if err != nil { 56 return err 57 } 58 const per = "power-edge-router" 59 perAvail, ok := capabilities[per] 60 if !ok { 61 return fmt.Errorf("%s capability unknown at: %s", per, ic.PowerVS.Zone) 62 } 63 if !perAvail { 64 return fmt.Errorf("%s is not available at: %s", per, ic.PowerVS.Zone) 65 } 66 67 return nil 68 } 69 70 // ValidatePreExistingDNS ensures no pre-existing DNS record exists in the CIS 71 // DNS zone or IBM DNS zone for cluster's Kubernetes API. 72 func ValidatePreExistingDNS(client API, ic *types.InstallConfig, metadata MetadataAPI) error { 73 allErrs := field.ErrorList{} 74 75 fldPath := field.NewPath("baseDomain") 76 if ic.Publish == types.ExternalPublishingStrategy { 77 allErrs = append(allErrs, validatePreExistingPublicDNS(fldPath, client, ic, metadata)...) 78 } else { 79 allErrs = append(allErrs, validatePreExistingPrivateDNS(fldPath, client, ic, metadata)...) 80 } 81 82 return allErrs.ToAggregate() 83 } 84 85 func validatePreExistingPublicDNS(fldPath *field.Path, client API, ic *types.InstallConfig, metadata MetadataAPI) field.ErrorList { 86 allErrs := field.ErrorList{} 87 // Get CIS CRN 88 crn, err := metadata.CISInstanceCRN(context.TODO()) 89 if err != nil { 90 return append(allErrs, field.InternalError(fldPath, err)) 91 } 92 93 // Get CIS zone ID by name 94 zoneID, err := client.GetDNSZoneIDByName(context.TODO(), ic.BaseDomain, types.ExternalPublishingStrategy) 95 if err != nil { 96 return append(allErrs, field.InternalError(fldPath, err)) 97 } 98 99 // Search for existing records 100 recordNames := [...]string{fmt.Sprintf("api.%s", ic.ClusterDomain()), fmt.Sprintf("api-int.%s", ic.ClusterDomain())} 101 for _, recordName := range recordNames { 102 records, err := client.GetDNSRecordsByName(context.TODO(), crn, zoneID, recordName, types.ExternalPublishingStrategy) 103 if err != nil { 104 allErrs = append(allErrs, field.InternalError(fldPath, err)) 105 } 106 107 // DNS record exists 108 if len(records) != 0 { 109 allErrs = append(allErrs, field.Duplicate(fldPath, fmt.Sprintf("record %s already exists in CIS zone (%s) and might be in use by another cluster, please remove it to continue", recordName, zoneID))) 110 } 111 } 112 return allErrs 113 } 114 115 func validatePreExistingPrivateDNS(fldPath *field.Path, client API, ic *types.InstallConfig, metadata MetadataAPI) field.ErrorList { 116 allErrs := field.ErrorList{} 117 // Get DNS CRN 118 crn, err := metadata.DNSInstanceCRN(context.TODO()) 119 if err != nil { 120 return append(allErrs, field.InternalError(fldPath, err)) 121 } 122 123 // Get CIS zone ID by name 124 zoneID, err := client.GetDNSZoneIDByName(context.TODO(), ic.BaseDomain, types.InternalPublishingStrategy) 125 if err != nil { 126 return append(allErrs, field.InternalError(fldPath, err)) 127 } 128 129 // Search for existing records 130 recordNames := [...]string{fmt.Sprintf("api-int.%s", ic.ClusterDomain())} 131 for _, recordName := range recordNames { 132 records, err := client.GetDNSRecordsByName(context.TODO(), crn, zoneID, recordName, types.InternalPublishingStrategy) 133 if err != nil { 134 allErrs = append(allErrs, field.InternalError(fldPath, err)) 135 } 136 137 // DNS record exists 138 if len(records) != 0 { 139 allErrs = append(allErrs, field.Duplicate(fldPath, fmt.Sprintf("record %s already exists in DNS zone (%s) and might be in use by another cluster, please remove it to continue", recordName, zoneID))) 140 } 141 } 142 return allErrs 143 } 144 145 // ValidateCustomVPCSetup ensures optional VPC settings, if specified, are all legit. 146 func ValidateCustomVPCSetup(client API, ic *types.InstallConfig) error { 147 allErrs := field.ErrorList{} 148 var vpcRegion = ic.PowerVS.VPCRegion 149 var vpcName = ic.PowerVS.VPCName 150 var err error 151 fldPath := field.NewPath("VPC") 152 153 if vpcRegion != "" { 154 if !powervstypes.ValidateVPCRegion(vpcRegion) { 155 allErrs = append(allErrs, field.NotFound(fldPath.Child("vpcRegion"), vpcRegion)) 156 } 157 } else { 158 vpcRegion, err = powervstypes.VPCRegionForPowerVSRegion(ic.PowerVS.Region) 159 if err != nil { 160 allErrs = append(allErrs, field.Invalid(fldPath.Child("region"), nil, ic.PowerVS.Region)) 161 } 162 } 163 164 if vpcName != "" { 165 allErrs = append(allErrs, findVPCInRegion(client, vpcName, vpcRegion, fldPath)...) 166 allErrs = append(allErrs, findSubnetInVPC(client, ic.PowerVS.VPCSubnets, vpcRegion, vpcName, fldPath)...) 167 } else if len(ic.PowerVS.VPCSubnets) != 0 { 168 allErrs = append(allErrs, field.Invalid(fldPath.Child("vpcSubnets"), nil, "invalid without vpcName")) 169 } 170 171 return allErrs.ToAggregate() 172 } 173 174 func findVPCInRegion(client API, name string, region string, path *field.Path) field.ErrorList { 175 allErrs := field.ErrorList{} 176 177 if name == "" { 178 return allErrs 179 } 180 181 vpcs, err := client.GetVPCs(context.TODO(), region) 182 if err != nil { 183 return append(allErrs, field.InternalError(path.Child("vpcRegion"), err)) 184 } 185 186 found := false 187 for _, vpc := range vpcs { 188 if *vpc.Name == name { 189 found = true 190 break 191 } 192 } 193 if !found { 194 allErrs = append(allErrs, field.NotFound(path.Child("vpcName"), name)) 195 } 196 197 return allErrs 198 } 199 200 func findSubnetInVPC(client API, subnets []string, region string, name string, path *field.Path) field.ErrorList { 201 allErrs := field.ErrorList{} 202 203 if len(subnets) == 0 { 204 return allErrs 205 } 206 207 subnet, err := client.GetSubnetByName(context.TODO(), subnets[0], region) 208 if err != nil { 209 allErrs = append(allErrs, field.InternalError(path.Child("vpcSubnets"), err)) 210 } else if *subnet.VPC.Name != name { 211 allErrs = append(allErrs, field.Invalid(path.Child("vpcSubnets"), nil, "not attached to VPC")) 212 } 213 214 return allErrs 215 } 216 217 // ValidateResourceGroup validates the resource group in our install config. 218 func ValidateResourceGroup(client API, ic *types.InstallConfig) error { 219 ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Minute) 220 defer cancel() 221 222 resourceGroups, err := client.ListResourceGroups(ctx) 223 if err != nil { 224 return fmt.Errorf("failed to list resourceGroups: %w", err) 225 } 226 227 switch ic.PowerVS.PowerVSResourceGroup { 228 case "": 229 return errors.New("platform:powervs:powervsresourcegroup is empty") 230 case "Default": 231 found := false 232 for _, resourceGroup := range resourceGroups.Resources { 233 if resourceGroup.Default != nil && *resourceGroup.Default { 234 found = true 235 ic.PowerVS.PowerVSResourceGroup = *resourceGroup.Name 236 break 237 } 238 } 239 if !found { 240 return errors.New("platform:powervs:powervsresourcegroup is default but no default exists") 241 } 242 default: 243 found := false 244 for _, resourceGroup := range resourceGroups.Resources { 245 if *resourceGroup.Name == ic.PowerVS.PowerVSResourceGroup { 246 found = true 247 break 248 } 249 } 250 if !found { 251 return errors.New("platform:powervs:powervsresourcegroup has an invalid name") 252 } 253 } 254 255 return nil 256 } 257 258 // ValidateSystemTypeForRegion checks if the specified sysType is available in the target region. 259 func ValidateSystemTypeForRegion(client API, ic *types.InstallConfig) error { 260 if ic.ControlPlane == nil || ic.ControlPlane.Platform.PowerVS == nil || ic.ControlPlane.Platform.PowerVS.SysType == "" { 261 return nil 262 } 263 availableOnes, err := powervstypes.AvailableSysTypes(ic.PowerVS.Region) 264 if err != nil { 265 return fmt.Errorf("failed to obtain available SysTypes for: %s", ic.PowerVS.Region) 266 } 267 requested := ic.ControlPlane.Platform.PowerVS.SysType 268 found := false 269 for i := range availableOnes { 270 if requested == availableOnes[i] { 271 found = true 272 break 273 } 274 } 275 if found { 276 return nil 277 } 278 return fmt.Errorf("%s is not available in: %s", requested, ic.PowerVS.Region) 279 } 280 281 // ValidateServiceInstance validates the optional service instance GUID in our install config. 282 func ValidateServiceInstance(client API, ic *types.InstallConfig) error { 283 ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Minute) 284 defer cancel() 285 286 serviceInstances, err := client.ListServiceInstances(ctx) 287 if err != nil { 288 return err 289 } 290 291 switch ic.PowerVS.ServiceInstanceGUID { 292 case "": 293 return nil 294 default: 295 found := false 296 for _, serviceInstance := range serviceInstances { 297 guid := strings.SplitN(serviceInstance, " ", 2)[1] 298 if guid == ic.PowerVS.ServiceInstanceGUID { 299 found = true 300 break 301 } 302 } 303 if !found { 304 return errors.New("platform:powervs:serviceInstanceGUID has an invalid guid") 305 } 306 } 307 308 return nil 309 }