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

     1  package gcp
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"sort"
     7  	"strings"
     8  
     9  	monitoring "cloud.google.com/go/monitoring/apiv3/v2"
    10  	"github.com/pkg/errors"
    11  	"google.golang.org/api/googleapi"
    12  	"google.golang.org/api/option"
    13  	serviceusage "google.golang.org/api/serviceusage/v1beta1"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  
    17  	gcpconfig "github.com/openshift/installer/pkg/asset/installconfig/gcp"
    18  	"github.com/openshift/installer/pkg/quota"
    19  )
    20  
    21  // Load load the quota information for a project and provided services. It provides information
    22  // about the usage and limit for each resource quota.
    23  // roles/servicemanagement.quotaViewer role allows users to fetch the required details.
    24  func Load(ctx context.Context, project string, services ...string) ([]quota.Quota, error) {
    25  	ssn, err := gcpconfig.GetSession(ctx)
    26  	if err != nil {
    27  		return nil, errors.Wrap(err, "failed to get session")
    28  	}
    29  
    30  	options := []option.ClientOption{
    31  		option.WithCredentials(ssn.Credentials),
    32  	}
    33  	servicesSvc, err := serviceusage.NewService(ctx, options...)
    34  	if err != nil {
    35  		return nil, errors.Wrap(err, "failed to create services svc")
    36  	}
    37  	metricsSvc, err := monitoring.NewMetricClient(ctx, options...)
    38  	if err != nil {
    39  		return nil, errors.Wrap(err, "failed to create metrics svc")
    40  	}
    41  
    42  	limits, err := loadLimits(ctx, servicesSvc.Services, project, services...)
    43  	if err != nil {
    44  		return nil, errors.Wrap(err, "failed to load quota limits")
    45  	}
    46  	usages, err := loadUsage(ctx, metricsSvc, project)
    47  	if err != nil {
    48  		return nil, errors.Wrap(err, "failed to load quota usages")
    49  	}
    50  	return newQuotas(usages, limits), nil
    51  }
    52  
    53  // record stores the data from quota limits and usages.
    54  type record struct {
    55  	Service string
    56  	Name    string
    57  
    58  	// this can either be a region or zones or const "global"
    59  	Location string
    60  
    61  	Value int64
    62  }
    63  
    64  // newQuotas combines the usage and quota limit to create a list of Quotas.
    65  // newQuotas matches the limits to the corrsponding usage to create summary of the quota.
    66  // Usages are usually reported for location as global or either per region or per zone, while the limits
    67  // are usually set for location as global or per region and therfore the quota consolidate the zone's usages to
    68  // it's region as sum.
    69  // When there is no matching usage found for a limit, the usage is treated as zero.
    70  func newQuotas(usages []record, limits []record) []quota.Quota {
    71  	sort.Slice(usages, func(i, j int) bool {
    72  		return usages[i].Service < usages[j].Service && usages[i].Name < usages[j].Name && usages[i].Location < usages[j].Location
    73  	})
    74  	sort.Slice(limits, func(i, j int) bool {
    75  		return limits[i].Service < limits[j].Service && limits[i].Name < limits[j].Name && limits[i].Location < limits[j].Location
    76  	})
    77  
    78  	findUsage := func(l record) (record, bool) {
    79  		for _, u := range usages {
    80  			if !strings.EqualFold(l.Service, u.Service) {
    81  				continue
    82  			}
    83  			if !strings.EqualFold(l.Name, u.Name) {
    84  				continue
    85  			}
    86  			if !strings.EqualFold(l.Location, u.Location) {
    87  				continue
    88  			}
    89  			return u, true
    90  		}
    91  		return record{}, false
    92  	}
    93  
    94  	quotas := make([]quota.Quota, 0, len(limits))
    95  	for _, l := range limits {
    96  		q := quota.Quota{
    97  			Service: l.Service,
    98  			Name:    l.Name,
    99  			Region:  l.Location,
   100  
   101  			Limit: l.Value,
   102  		}
   103  		u, ok := findUsage(l)
   104  		if !ok {
   105  			q.InUse = int64(0)
   106  		} else {
   107  			q.InUse = u.Value
   108  		}
   109  		quotas = append(quotas, q)
   110  	}
   111  	return quotas
   112  }
   113  
   114  // IsUnauthorized checks if the error is un authorized.
   115  func IsUnauthorized(err error) bool {
   116  	if err == nil {
   117  		return false
   118  	}
   119  	var gErr *googleapi.Error
   120  	if errors.As(err, &gErr) {
   121  		return gErr.Code == http.StatusUnauthorized || gErr.Code == http.StatusForbidden
   122  	}
   123  
   124  	if grpcCode := status.Code(errors.Cause(err)); grpcCode != codes.OK {
   125  		return grpcCode == codes.PermissionDenied
   126  	}
   127  	return false
   128  }