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  }