github.com/openshift/installer@v1.4.17/pkg/asset/quota/quota.go (about)

     1  package quota
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/pkg/errors"
    10  	"github.com/sirupsen/logrus"
    11  
    12  	"github.com/openshift/installer/pkg/asset"
    13  	"github.com/openshift/installer/pkg/asset/installconfig"
    14  	configgcp "github.com/openshift/installer/pkg/asset/installconfig/gcp"
    15  	openstackvalidation "github.com/openshift/installer/pkg/asset/installconfig/openstack/validation"
    16  	configpowervs "github.com/openshift/installer/pkg/asset/installconfig/powervs"
    17  	"github.com/openshift/installer/pkg/asset/machines"
    18  	"github.com/openshift/installer/pkg/asset/quota/aws"
    19  	"github.com/openshift/installer/pkg/asset/quota/gcp"
    20  	"github.com/openshift/installer/pkg/asset/quota/openstack"
    21  	"github.com/openshift/installer/pkg/diagnostics"
    22  	"github.com/openshift/installer/pkg/quota"
    23  	quotaaws "github.com/openshift/installer/pkg/quota/aws"
    24  	quotagcp "github.com/openshift/installer/pkg/quota/gcp"
    25  	typesaws "github.com/openshift/installer/pkg/types/aws"
    26  	"github.com/openshift/installer/pkg/types/azure"
    27  	"github.com/openshift/installer/pkg/types/baremetal"
    28  	"github.com/openshift/installer/pkg/types/external"
    29  	typesgcp "github.com/openshift/installer/pkg/types/gcp"
    30  	"github.com/openshift/installer/pkg/types/ibmcloud"
    31  	"github.com/openshift/installer/pkg/types/none"
    32  	"github.com/openshift/installer/pkg/types/nutanix"
    33  	typesopenstack "github.com/openshift/installer/pkg/types/openstack"
    34  	"github.com/openshift/installer/pkg/types/ovirt"
    35  	"github.com/openshift/installer/pkg/types/powervs"
    36  	"github.com/openshift/installer/pkg/types/vsphere"
    37  )
    38  
    39  // PlatformQuotaCheck is an asset that validates the install-config platform for
    40  // any resource requirements based on the quotas available.
    41  type PlatformQuotaCheck struct {
    42  }
    43  
    44  var _ asset.Asset = (*PlatformQuotaCheck)(nil)
    45  
    46  // Dependencies returns the dependencies for PlatformQuotaCheck
    47  func (a *PlatformQuotaCheck) Dependencies() []asset.Asset {
    48  	return []asset.Asset{
    49  		&installconfig.InstallConfig{},
    50  		&machines.Master{},
    51  		&machines.Worker{},
    52  	}
    53  }
    54  
    55  // Generate queries for input from the user.
    56  func (a *PlatformQuotaCheck) Generate(ctx context.Context, dependencies asset.Parents) error {
    57  	ic := &installconfig.InstallConfig{}
    58  	mastersAsset := &machines.Master{}
    59  	workersAsset := &machines.Worker{}
    60  	dependencies.Get(ic, mastersAsset, workersAsset)
    61  
    62  	masters, err := mastersAsset.Machines()
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	workers, err := workersAsset.MachineSets()
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	platform := ic.Config.Platform.Name()
    73  	switch platform {
    74  	case typesaws.Name:
    75  		if !quotaaws.SupportedRegions.Has(ic.AWS.Region) {
    76  			logrus.Debugf("%s does not support API for checking quotas, therefore skipping.", ic.AWS.Region)
    77  			return nil
    78  		}
    79  		services := []string{"ec2", "vpc"}
    80  		session, err := ic.AWS.Session(ctx)
    81  		if err != nil {
    82  			return errors.Wrap(err, "failed to load AWS session")
    83  		}
    84  		q, err := quotaaws.Load(ctx, session, ic.AWS.Region, services...)
    85  		if quotaaws.IsUnauthorized(err) {
    86  			logrus.Debugf("Missing permissions to fetch Quotas and therefore will skip checking them: %v, make sure you have `servicequotas:ListAWSDefaultServiceQuotas` permission available to the user.", err)
    87  			logrus.Info("Skipping quota checks")
    88  			return nil
    89  		}
    90  		if err != nil {
    91  			return errors.Wrapf(err, "failed to load Quota for services: %s", strings.Join(services, ", "))
    92  		}
    93  		instanceTypes, err := aws.InstanceTypes(ctx, session, ic.AWS.Region)
    94  		if quotaaws.IsUnauthorized(err) {
    95  			logrus.Warnf("Missing permissions to fetch instance types and therefore will skip checking Quotas: %v, make sure you have `ec2:DescribeInstanceTypes` permission available to the user.", err)
    96  			return nil
    97  		}
    98  		if err != nil {
    99  			return errors.Wrapf(err, "failed to load instance types for %s", ic.AWS.Region)
   100  		}
   101  		reports, err := quota.Check(q, aws.Constraints(ic.Config, masters, workers, instanceTypes))
   102  		if err != nil {
   103  			return summarizeFailingReport(reports)
   104  		}
   105  		summarizeReport(reports)
   106  	case typesgcp.Name:
   107  		services := []string{"compute.googleapis.com", "iam.googleapis.com"}
   108  		q, err := quotagcp.Load(ctx, ic.Config.Platform.GCP.ProjectID, services...)
   109  		if quotagcp.IsUnauthorized(err) {
   110  			logrus.Warnf("Missing permissions to fetch Quotas and therefore will skip checking them: %v, make sure you have `roles/servicemanagement.quotaViewer` assigned to the user.", err)
   111  			return nil
   112  		}
   113  		if err != nil {
   114  			return errors.Wrapf(err, "failed to load Quota for services: %s", strings.Join(services, ", "))
   115  		}
   116  		session, err := configgcp.GetSession(ctx)
   117  		if err != nil {
   118  			return errors.Wrap(err, "failed to load GCP session")
   119  		}
   120  		client, err := gcp.NewClient(ctx, session, ic.Config.Platform.GCP.ProjectID)
   121  		if err != nil {
   122  			return errors.Wrap(err, "failed to create client for quota constraints")
   123  		}
   124  		reports, err := quota.Check(q, gcp.Constraints(client, ic.Config, masters, workers))
   125  		if err != nil {
   126  			return summarizeFailingReport(reports)
   127  		}
   128  		summarizeReport(reports)
   129  	case typesopenstack.Name:
   130  		if skip := os.Getenv("OPENSHIFT_INSTALL_SKIP_PREFLIGHT_VALIDATIONS"); skip == "1" {
   131  			logrus.Warnf("OVERRIDE: pre-flight validation disabled.")
   132  			return nil
   133  		}
   134  		ci, err := openstackvalidation.GetCloudInfo(ctx, ic.Config)
   135  		if err != nil {
   136  			return errors.Wrap(err, "failed to get cloud info")
   137  		}
   138  		if ci == nil {
   139  			logrus.Warnf("Empty OpenStack cloud info and therefore will skip checking quota validation.")
   140  			return nil
   141  		}
   142  		reports, err := quota.Check(ci.Quotas, openstack.Constraints(ci, masters, workers))
   143  		if err != nil {
   144  			return summarizeFailingReport(reports)
   145  		}
   146  		summarizeReport(reports)
   147  	case powervs.Name:
   148  		// We need to prompt for missing variables because NewPISession requires them!
   149  		bxCli, err := configpowervs.NewBxClient(true)
   150  		if err != nil {
   151  			return errors.Wrap(err, "failed to create bluemix client")
   152  		}
   153  
   154  		err = bxCli.NewPISession()
   155  		if err != nil {
   156  			return errors.Wrap(err, "failed to create a new PISession")
   157  		}
   158  	case azure.Name, baremetal.Name, ibmcloud.Name, external.Name, none.Name, ovirt.Name, vsphere.Name, nutanix.Name:
   159  		// no special provisioning requirements to check
   160  	default:
   161  		err = fmt.Errorf("unknown platform type %q", platform)
   162  	}
   163  	return err
   164  }
   165  
   166  // Name returns the human-friendly name of the asset.
   167  func (a *PlatformQuotaCheck) Name() string {
   168  	return "Platform Quota Check"
   169  }
   170  
   171  // summarizeFailingReport summarizes a report when there are failing constraints.
   172  func summarizeFailingReport(reports []quota.ConstraintReport) error {
   173  	var notavailable []string
   174  	var unknown []string
   175  	var regionMessage string
   176  	for _, report := range reports {
   177  		switch report.Result {
   178  		case quota.NotAvailable:
   179  			if report.For.Region != "" {
   180  				regionMessage = " in " + report.For.Region
   181  			} else {
   182  				regionMessage = ""
   183  			}
   184  			notavailable = append(notavailable, fmt.Sprintf("%s is not available%s because %s", report.For.Name, regionMessage, report.Message))
   185  		case quota.Unknown:
   186  			unknown = append(unknown, report.For.Name)
   187  		default:
   188  			continue
   189  		}
   190  	}
   191  
   192  	if len(notavailable) == 0 && len(unknown) > 0 {
   193  		// all quotas are missing information so warn and skip
   194  		logrus.Warnf("Failed to find information on quotas %s", strings.Join(unknown, ", "))
   195  		return nil
   196  	}
   197  
   198  	msg := strings.Join(notavailable, ", ")
   199  	if len(unknown) > 0 {
   200  		msg = fmt.Sprintf("%s, and could not find information on %s", msg, strings.Join(unknown, ", "))
   201  	}
   202  	return &diagnostics.Err{Reason: "MissingQuota", Message: msg}
   203  }
   204  
   205  // summarizeReport summarizes a report when there are availble.
   206  func summarizeReport(reports []quota.ConstraintReport) {
   207  	var low []string
   208  	var regionMessage string
   209  	for _, report := range reports {
   210  		switch report.Result {
   211  		case quota.AvailableButLow:
   212  			if report.For.Region != "" {
   213  				regionMessage = " (" + report.For.Region + ")"
   214  			} else {
   215  				regionMessage = ""
   216  			}
   217  			low = append(low, fmt.Sprintf("%s%s", report.For.Name, regionMessage))
   218  		default:
   219  			continue
   220  		}
   221  	}
   222  	if len(low) > 0 {
   223  		logrus.Warnf("Following quotas %s are available but will be completely used pretty soon.", strings.Join(low, ", "))
   224  	}
   225  }