cli_tools/common/utils/storage/resource_location_retriever.go (109 lines of code) (raw):

// Copyright 2019 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package storage import ( "fmt" "strings" daisy "github.com/GoogleCloudPlatform/compute-daisy" daisyCompute "github.com/GoogleCloudPlatform/compute-daisy/compute" "google.golang.org/api/compute/v1" "github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/domain" ) type multiRegion struct { regions []string isRegionToMultiRegionAllowed bool } var ( // for each GCS multi region, regions are sorted by GCE cost in ascending order as of the time of writing // this code. // GCE cost per region: https://cloud.google.com/compute/vm-instance-pricing // GCS multi-regions: https://cloud.google.com/storage/docs/locations#location-mr multiRegions = map[string]multiRegion{ "US": {[]string{"us-central1", "us-east1", "us-west1", "us-east4", "us-west4", "us-west2", "us-west3"}, true}, "EU": {[]string{"europe-west1", "europe-west4", "europe-north1", "europe-west2", "europe-west3", "europe-west6"}, true}, "ASIA": {[]string{"asia-east1", "asia-south1", "asia-southeast1", "asia-northeast1", "asia-northeast2", "asia-northeast3", "asia-southeast2", "asia-east2"}, true}, "EUR4": {[]string{"europe-west4", "europe-north1"}, false}, "NAM4": {[]string{"us-central1", "us-east1"}, false}, "ASIA1": {[]string{"asia-northeast1", "asia-northeast2"}, false}, } ) // ResourceLocationRetriever is responsible for retrieving GCE zone to run import in type ResourceLocationRetriever struct { Mgce domain.MetadataGCEInterface ComputeGCEService daisyCompute.Client } // NewResourceLocationRetriever creates a ResourceLocationRetriever func NewResourceLocationRetriever(aMgce domain.MetadataGCEInterface, cs daisyCompute.Client) *ResourceLocationRetriever { return &ResourceLocationRetriever{Mgce: aMgce, ComputeGCEService: cs} } // GetZone retrieves GCE zone to run import in based on imported source file location and available // zones in the project. // If storageRegion is provided and valid, first zone within that region will be used. // If no storageRegion is provided, GCE Zone from the running process // will be used. func (rlr *ResourceLocationRetriever) GetZone(storageLocation string, project string) (string, error) { zone := "" var err error if storageLocation != "" { // pick a zone from the region where data is stored zone, err = rlr.getZoneFromStorageLocation(storageLocation, project) if err == nil { return zone, err } } // determine zone based on the zone Cloud Build is running in if rlr.Mgce.OnGCE() { zone, err = rlr.Mgce.Zone() } if err != nil { return "", daisy.Errf("can't infer zone: %v", err) } if zone == "" { return "", daisy.Errf("zone is empty") } fmt.Printf("[image-import] Zone not provided, using %v\n", zone) return zone, nil } func (rlr *ResourceLocationRetriever) getZoneFromStorageLocation(location string, project string) (string, daisy.DError) { if project == "" { return "", daisy.Errf("project cannot be empty in order to find a zone from a location") } zones, err := rlr.ComputeGCEService.ListZones(project) if err != nil { return "", daisy.Errf("Failed to list zones: %v", err) } if rlr.isMultiRegion(location) { return rlr.getBestZoneForMultiRegion(location, zones) } return rlr.getZoneForRegion(location, zones) } func (rlr *ResourceLocationRetriever) getZoneForRegion(region string, zones []*compute.Zone) (string, daisy.DError) { for _, zone := range zones { if isZoneUp(zone) && strings.HasSuffix(strings.ToLower(zone.Region), strings.ToLower(region)) { return zone.Name, nil } } return "", daisy.Errf("no zone found for %v region", region) } func (rlr *ResourceLocationRetriever) getMultiRegionForRegion(region string) string { for multiRegionID, multiRegion := range multiRegions { if !multiRegion.isRegionToMultiRegionAllowed { continue } for _, aRegion := range multiRegion.regions { if strings.EqualFold(aRegion, region) { return multiRegionID } } } return "" } func (rlr *ResourceLocationRetriever) getBestZoneForMultiRegion(multiRegion string, zones []*compute.Zone) (string, daisy.DError) { for _, region := range multiRegions[multiRegion].regions { if zone, err := rlr.getZoneForRegion(region, zones); err == nil { return zone, nil } } return "", daisy.Errf("no zones found for %v multi region", multiRegion) } func (rlr *ResourceLocationRetriever) isMultiRegion(location string) bool { _, containsKey := multiRegions[strings.ToUpper(location)] return containsKey } func isZoneUp(zone *compute.Zone) bool { return zone != nil && zone.Status == "UP" } // GetLargestStorageLocation returns the largest storage location that includes provided argument. // If argument is a multi-region, the argument is returned. If argument is a region within a multi-region, // the multi-region is returned. If argument is a region not within a multi-region, argument is returned. func (rlr *ResourceLocationRetriever) GetLargestStorageLocation(storageLocation string) string { if rlr.isMultiRegion(storageLocation) { return storageLocation } if multiRegion := rlr.getMultiRegionForRegion(storageLocation); multiRegion != "" { return multiRegion } return storageLocation }