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 }