2016-02-22 00:13:08 +00:00
/ *
2016-07-12 03:42:47 +00:00
Copyright 2014 The Kubernetes Authors .
2016-02-22 00:13:08 +00:00
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 gce
import (
"fmt"
"io"
2017-05-21 00:11:38 +00:00
"net/http"
2016-02-22 00:13:08 +00:00
"regexp"
2017-09-29 17:12:14 +00:00
"strconv"
2016-02-22 00:13:08 +00:00
"strings"
2017-07-16 19:30:35 +00:00
"sync"
2016-02-22 00:13:08 +00:00
"time"
2017-07-16 19:30:35 +00:00
gcfg "gopkg.in/gcfg.v1"
2017-04-01 14:42:02 +00:00
2017-07-16 19:30:35 +00:00
"cloud.google.com/go/compute/metadata"
2016-11-10 22:57:28 +00:00
2017-09-29 17:12:14 +00:00
"k8s.io/api/core/v1"
2017-07-16 19:30:35 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-04-01 14:42:02 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2017-09-29 17:12:14 +00:00
"k8s.io/apiserver/pkg/server/options/encryptionconfig"
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/record"
2017-04-01 14:42:02 +00:00
"k8s.io/client-go/util/flowcontrol"
2016-02-22 00:13:08 +00:00
"k8s.io/kubernetes/pkg/cloudprovider"
2017-05-21 00:11:38 +00:00
"k8s.io/kubernetes/pkg/controller"
2016-02-22 00:13:08 +00:00
"github.com/golang/glog"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
2017-07-16 19:30:35 +00:00
cloudkms "google.golang.org/api/cloudkms/v1"
computealpha "google.golang.org/api/compute/v0.alpha"
2017-05-21 00:11:38 +00:00
computebeta "google.golang.org/api/compute/v0.beta"
2016-02-22 00:13:08 +00:00
compute "google.golang.org/api/compute/v1"
container "google.golang.org/api/container/v1"
)
const (
ProviderName = "gce"
k8sNodeRouteTag = "k8s-node-route"
// AffinityTypeNone - no session affinity.
gceAffinityTypeNone = "NONE"
// AffinityTypeClientIP - affinity based on Client IP.
gceAffinityTypeClientIP = "CLIENT_IP"
// AffinityTypeClientIPProto - affinity based on Client IP and port.
gceAffinityTypeClientIPProto = "CLIENT_IP_PROTO"
2017-04-01 14:42:02 +00:00
operationPollInterval = 3 * time . Second
// Creating Route in very large clusters, may take more than half an hour.
operationPollTimeoutDuration = time . Hour
2016-03-19 23:00:11 +00:00
// Each page can have 500 results, but we cap how many pages
// are iterated through to prevent infinite loops if the API
// were to continuously return a nextPageToken.
maxPages = 25
2016-05-10 13:30:56 +00:00
2016-07-12 03:42:47 +00:00
maxTargetPoolCreateInstances = 200
2016-08-10 18:53:55 +00:00
// HTTP Load Balancer parameters
// Configure 2 second period for external health checks.
gceHcCheckIntervalSeconds = int64 ( 2 )
gceHcTimeoutSeconds = int64 ( 1 )
// Start sending requests as soon as a pod is found on the node.
gceHcHealthyThreshold = int64 ( 1 )
// Defaults to 5 * 2 = 10 seconds before the LB will steer traffic away
gceHcUnhealthyThreshold = int64 ( 5 )
2017-07-16 19:30:35 +00:00
2017-09-29 17:12:14 +00:00
gceComputeAPIEndpoint = "https://www.googleapis.com/compute/v1/"
gceComputeAPIEndpointAlpha = "https://www.googleapis.com/compute/alpha/"
2016-02-22 00:13:08 +00:00
)
2017-09-29 17:12:14 +00:00
// gceObject is an abstraction of all GCE API object in go client
type gceObject interface {
MarshalJSON ( ) ( [ ] byte , error )
}
2016-02-22 00:13:08 +00:00
// GCECloud is an implementation of Interface, LoadBalancer and Instances for Google Compute Engine.
type GCECloud struct {
2017-07-16 19:30:35 +00:00
// ClusterID contains functionality for getting (and initializing) the ingress-uid. Call GCECloud.Initialize()
// for the cloudprovider to start watching the configmap.
ClusterID ClusterID
2016-04-17 20:19:22 +00:00
service * compute . Service
2017-05-21 00:11:38 +00:00
serviceBeta * computebeta . Service
2017-07-16 19:30:35 +00:00
serviceAlpha * computealpha . Service
2016-04-17 20:19:22 +00:00
containerService * container . Service
2017-07-16 19:30:35 +00:00
cloudkmsService * cloudkms . Service
2017-09-29 17:12:14 +00:00
client clientset . Interface
2017-07-16 19:30:35 +00:00
clientBuilder controller . ControllerClientBuilder
2017-09-29 17:12:14 +00:00
eventBroadcaster record . EventBroadcaster
eventRecorder record . EventRecorder
2016-04-17 20:19:22 +00:00
projectID string
region string
localZone string // The zone in which we are running
2016-08-10 18:53:55 +00:00
managedZones [ ] string // List of zones we are spanning (for multi-AZ clusters, primarily when running on master)
2016-04-17 20:19:22 +00:00
networkURL string
2017-07-16 19:30:35 +00:00
subnetworkURL string
2017-09-29 17:12:14 +00:00
secondaryRangeName string
2017-07-16 19:30:35 +00:00
networkProjectID string
onXPN bool
nodeTags [ ] string // List of tags to use on firewall rules for load balancers
lastComputedNodeTags [ ] string // List of node tags calculated in GetHostTags()
lastKnownNodeNames sets . String // List of hostnames used to calculate lastComputedHostTags in GetHostTags(names)
computeNodeTagLock sync . Mutex // Lock for computing and setting node tags
nodeInstancePrefix string // If non-"", an advisory prefix for all nodes in the cluster
2016-04-17 20:19:22 +00:00
useMetadataServer bool
operationPollRateLimiter flowcontrol . RateLimiter
2017-07-16 19:30:35 +00:00
manager ServiceManager
// sharedResourceLock is used to serialize GCE operations that may mutate shared state to
// prevent inconsistencies. For example, load balancers manipulation methods will take the
// lock to prevent shared resources from being prematurely deleted while the operation is
// in progress.
sharedResourceLock sync . Mutex
2017-09-29 17:12:14 +00:00
// AlphaFeatureGate gates gce alpha features in GCECloud instance.
// Related wrapper functions that interacts with gce alpha api should examine whether
// the corresponding api is enabled.
// If not enabled, it should return error.
AlphaFeatureGate * AlphaFeatureGate
2017-07-16 19:30:35 +00:00
}
type ServiceManager interface {
// Creates a new persistent disk on GCE with the given disk spec.
2017-09-29 17:12:14 +00:00
CreateDisk (
name string ,
sizeGb int64 ,
tagsStr string ,
diskType string ,
zone string ) ( gceObject , error )
// Creates a new regional persistent disk on GCE with the given disk spec.
CreateRegionalDisk (
name string ,
sizeGb int64 ,
tagsStr string ,
diskType string ,
zones sets . String ) ( gceObject , error )
// Deletes the persistent disk from GCE with the given diskName.
DeleteDisk ( zone string , disk string ) ( gceObject , error )
// Deletes the regional persistent disk from GCE with the given diskName.
DeleteRegionalDisk ( diskName string ) ( gceObject , error )
// Attach a persistent disk on GCE with the given disk spec to the specified instance.
AttachDisk (
disk * GCEDisk ,
readWrite string ,
instanceZone string ,
instanceName string ) ( gceObject , error )
// Detach a persistent disk on GCE with the given disk spec from the specified instance.
DetachDisk (
instanceZone string ,
instanceName string ,
devicePath string ) ( gceObject , error )
2017-07-16 19:30:35 +00:00
// Gets the persistent disk from GCE with the given diskName.
2017-09-29 17:12:14 +00:00
GetDisk ( zone string , diskName string ) ( * GCEDisk , error )
2017-07-16 19:30:35 +00:00
2017-09-29 17:12:14 +00:00
// Gets the regional persistent disk from GCE with the given diskName.
GetRegionalDisk ( diskName string ) ( * GCEDisk , error )
2017-07-16 19:30:35 +00:00
// Waits until GCE reports the given operation in the given zone as done.
2017-09-29 17:12:14 +00:00
WaitForZoneOp ( op gceObject , zone string , mc * metricContext ) error
// Waits until GCE reports the given operation in the given region is done.
WaitForRegionalOp ( op gceObject , mc * metricContext ) error
2017-07-16 19:30:35 +00:00
}
type GCEServiceManager struct {
gce * GCECloud
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
type ConfigGlobal struct {
TokenURL string ` gcfg:"token-url" `
TokenBody string ` gcfg:"token-body" `
// ProjectID and NetworkProjectID can either be the numeric or string-based
// unique identifier that starts with [a-z].
ProjectID string ` gcfg:"project-id" `
// NetworkProjectID refers to the project which owns the network being used.
NetworkProjectID string ` gcfg:"network-project-id" `
NetworkName string ` gcfg:"network-name" `
SubnetworkName string ` gcfg:"subnetwork-name" `
// SecondaryRangeName is the name of the secondary range to allocate IP
// aliases. The secondary range must be present on the subnetwork the
// cluster is attached to.
SecondaryRangeName string ` gcfg:"secondary-range-name" `
NodeTags [ ] string ` gcfg:"node-tags" `
NodeInstancePrefix string ` gcfg:"node-instance-prefix" `
Multizone bool ` gcfg:"multizone" `
// ApiEndpoint is the GCE compute API endpoint to use. If this is blank,
// then the default endpoint is used.
ApiEndpoint string ` gcfg:"api-endpoint" `
// LocalZone specifies the GCE zone that gce cloud client instance is
// located in (i.e. where the controller will be running). If this is
// blank, then the local zone will be discovered via the metadata server.
LocalZone string ` gcfg:"local-zone" `
// Possible values: List of api names separated by comma. Default to none.
// For example: MyFeatureFlag
AlphaFeatures [ ] string ` gcfg:"alpha-features" `
}
// ConfigFile is the struct used to parse the /etc/gce.conf configuration file.
type ConfigFile struct {
Global ConfigGlobal ` gcfg:"global" `
}
// CloudConfig includes all the necessary configuration for creating GCECloud
type CloudConfig struct {
ApiEndpoint string
ProjectID string
NetworkProjectID string
Region string
Zone string
ManagedZones [ ] string
NetworkName string
NetworkURL string
SubnetworkName string
SubnetworkURL string
SecondaryRangeName string
NodeTags [ ] string
NodeInstancePrefix string
TokenSource oauth2 . TokenSource
UseMetadataServer bool
AlphaFeatureGate * AlphaFeatureGate
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
// kmsPluginRegisterOnce prevents the cloudprovider from registering its KMS plugin
// more than once in the KMS plugin registry.
var kmsPluginRegisterOnce sync . Once
2016-02-22 00:13:08 +00:00
func init ( ) {
2017-04-01 14:42:02 +00:00
cloudprovider . RegisterCloudProvider (
ProviderName ,
func ( config io . Reader ) ( cloudprovider . Interface , error ) {
return newGCECloud ( config )
} )
2016-02-22 00:13:08 +00:00
}
// Raw access to the underlying GCE service, probably should only be used for e2e tests
func ( g * GCECloud ) GetComputeService ( ) * compute . Service {
return g . service
}
2017-07-16 19:30:35 +00:00
// Raw access to the cloudkmsService of GCE cloud. Required for encryption of etcd using Google KMS.
func ( g * GCECloud ) GetKMSService ( ) * cloudkms . Service {
return g . cloudkmsService
}
2016-02-22 00:13:08 +00:00
// newGCECloud creates a new instance of GCECloud.
2017-09-29 17:12:14 +00:00
func newGCECloud ( config io . Reader ) ( gceCloud * GCECloud , err error ) {
var cloudConfig * CloudConfig
var configFile * ConfigFile
if config != nil {
configFile , err = readConfig ( config )
if err != nil {
return nil , err
}
glog . Infof ( "Using GCE provider config %+v" , configFile )
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
cloudConfig , err = generateCloudConfig ( configFile )
2016-02-22 00:13:08 +00:00
if err != nil {
return nil , err
}
2017-09-29 17:12:14 +00:00
return CreateGCECloud ( cloudConfig )
2016-02-22 00:13:08 +00:00
2017-09-29 17:12:14 +00:00
}
func readConfig ( reader io . Reader ) ( * ConfigFile , error ) {
cfg := & ConfigFile { }
if err := gcfg . FatalOnly ( gcfg . ReadInto ( cfg , reader ) ) ; err != nil {
glog . Errorf ( "Couldn't read config: %v" , err )
2016-02-22 00:13:08 +00:00
return nil , err
}
2017-09-29 17:12:14 +00:00
return cfg , nil
}
2016-02-22 00:13:08 +00:00
2017-09-29 17:12:14 +00:00
func generateCloudConfig ( configFile * ConfigFile ) ( cloudConfig * CloudConfig , err error ) {
cloudConfig = & CloudConfig { }
// By default, fetch token from GCE metadata server
cloudConfig . TokenSource = google . ComputeTokenSource ( "" )
cloudConfig . UseMetadataServer = true
2016-02-22 00:13:08 +00:00
2017-09-29 17:12:14 +00:00
if configFile != nil {
if configFile . Global . ApiEndpoint != "" {
cloudConfig . ApiEndpoint = configFile . Global . ApiEndpoint
2016-02-22 00:13:08 +00:00
}
2017-07-31 20:35:10 +00:00
2017-09-29 17:12:14 +00:00
if configFile . Global . TokenURL != "" {
// if tokenURL is nil, set tokenSource to nil. This will force the OAuth client to fall
// back to use DefaultTokenSource. This allows running gceCloud remotely.
if configFile . Global . TokenURL == "nil" {
cloudConfig . TokenSource = nil
} else {
cloudConfig . TokenSource = NewAltTokenSource ( configFile . Global . TokenURL , configFile . Global . TokenBody )
}
2017-07-16 19:30:35 +00:00
}
2017-09-29 17:12:14 +00:00
cloudConfig . NodeTags = configFile . Global . NodeTags
cloudConfig . NodeInstancePrefix = configFile . Global . NodeInstancePrefix
alphaFeatureGate , err := NewAlphaFeatureGate ( configFile . Global . AlphaFeatures )
if err != nil {
glog . Errorf ( "Encountered error for creating alpha feature gate: %v" , err )
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
cloudConfig . AlphaFeatureGate = alphaFeatureGate
}
2017-07-16 19:30:35 +00:00
2017-09-29 17:12:14 +00:00
// retrieve projectID and zone
if configFile == nil || configFile . Global . ProjectID == "" || configFile . Global . LocalZone == "" {
cloudConfig . ProjectID , cloudConfig . Zone , err = getProjectAndZone ( )
if err != nil {
return nil , err
2017-07-16 19:30:35 +00:00
}
2017-09-29 17:12:14 +00:00
}
2017-07-16 19:30:35 +00:00
2017-09-29 17:12:14 +00:00
if configFile != nil {
if configFile . Global . ProjectID != "" {
cloudConfig . ProjectID = configFile . Global . ProjectID
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
if configFile . Global . LocalZone != "" {
cloudConfig . Zone = configFile . Global . LocalZone
}
if configFile . Global . NetworkProjectID != "" {
cloudConfig . NetworkProjectID = configFile . Global . NetworkProjectID
}
}
// retrieve region
cloudConfig . Region , err = GetGCERegion ( cloudConfig . Zone )
if err != nil {
return nil , err
}
2017-07-31 20:35:10 +00:00
2017-09-29 17:12:14 +00:00
// generate managedZones
cloudConfig . ManagedZones = [ ] string { cloudConfig . Zone }
if configFile != nil && configFile . Global . Multizone {
cloudConfig . ManagedZones = nil // Use all zones in region
}
// Determine if network parameter is URL or Name
if configFile != nil && configFile . Global . NetworkName != "" {
if strings . Contains ( configFile . Global . NetworkName , "/" ) {
cloudConfig . NetworkURL = configFile . Global . NetworkName
} else {
cloudConfig . NetworkName = configFile . Global . NetworkName
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
} else {
cloudConfig . NetworkName , err = getNetworkNameViaMetadata ( )
if err != nil {
return nil , err
2016-02-22 00:13:08 +00:00
}
}
2017-09-29 17:12:14 +00:00
// Determine if subnetwork parameter is URL or Name
// If cluster is on a GCP network of mode=custom, then `SubnetName` must be specified in config file.
if configFile != nil && configFile . Global . SubnetworkName != "" {
if strings . Contains ( configFile . Global . SubnetworkName , "/" ) {
cloudConfig . SubnetworkURL = configFile . Global . SubnetworkName
} else {
cloudConfig . SubnetworkName = configFile . Global . SubnetworkName
}
}
2016-02-22 00:13:08 +00:00
2017-09-29 17:12:14 +00:00
if configFile != nil {
cloudConfig . SecondaryRangeName = configFile . Global . SecondaryRangeName
2017-07-31 20:35:10 +00:00
}
2017-09-29 17:12:14 +00:00
return cloudConfig , err
2017-07-31 20:35:10 +00:00
}
2017-09-29 17:12:14 +00:00
// CreateGCECloud creates a GCECloud object using the specified parameters.
2016-02-22 00:13:08 +00:00
// If no networkUrl is specified, loads networkName via rest call.
// If no tokenSource is specified, uses oauth2.DefaultTokenSource.
// If managedZones is nil / empty all zones in the region will be managed.
2017-09-29 17:12:14 +00:00
func CreateGCECloud ( config * CloudConfig ) ( * GCECloud , error ) {
// Use ProjectID for NetworkProjectID, if it wasn't explicitly set.
if config . NetworkProjectID == "" {
config . NetworkProjectID = config . ProjectID
}
2017-04-01 14:42:02 +00:00
2017-09-29 17:12:14 +00:00
client , err := newOauthClient ( config . TokenSource )
2017-05-21 00:11:38 +00:00
if err != nil {
return nil , err
2016-02-22 00:13:08 +00:00
}
2017-05-21 00:11:38 +00:00
service , err := compute . New ( client )
if err != nil {
2016-09-21 23:00:42 +00:00
return nil , err
}
2017-09-29 17:12:14 +00:00
client , err = newOauthClient ( config . TokenSource )
2017-07-16 19:30:35 +00:00
if err != nil {
return nil , err
}
2017-05-21 00:11:38 +00:00
serviceBeta , err := computebeta . New ( client )
2016-02-22 00:13:08 +00:00
if err != nil {
return nil , err
}
2017-09-29 17:12:14 +00:00
client , err = newOauthClient ( config . TokenSource )
2017-07-16 19:30:35 +00:00
if err != nil {
return nil , err
}
serviceAlpha , err := computealpha . New ( client )
if err != nil {
return nil , err
}
// Expect override api endpoint to always be v1 api and follows the same pattern as prod.
// Generate alpha and beta api endpoints based on override v1 api endpoint.
// For example,
// staging API endpoint: https://www.googleapis.com/compute/staging_v1/
2017-09-29 17:12:14 +00:00
if config . ApiEndpoint != "" {
service . BasePath = fmt . Sprintf ( "%sprojects/" , config . ApiEndpoint )
serviceBeta . BasePath = fmt . Sprintf ( "%sprojects/" , strings . Replace ( config . ApiEndpoint , "v1" , "beta" , - 1 ) )
serviceAlpha . BasePath = fmt . Sprintf ( "%sprojects/" , strings . Replace ( config . ApiEndpoint , "v1" , "alpha" , - 1 ) )
2017-07-16 19:30:35 +00:00
}
2017-05-21 00:11:38 +00:00
containerService , err := container . New ( client )
2016-02-22 00:13:08 +00:00
if err != nil {
return nil , err
}
2017-07-16 19:30:35 +00:00
cloudkmsService , err := cloudkms . New ( client )
if err != nil {
return nil , err
}
2017-09-29 17:12:14 +00:00
// ProjectID and.NetworkProjectID may be project number or name.
projID , netProjID := tryConvertToProjectNames ( config . ProjectID , config . NetworkProjectID , service )
onXPN := projID != netProjID
var networkURL string
var subnetURL string
if config . NetworkName == "" && config . NetworkURL == "" {
// TODO: Stop using this call and return an error.
// This function returns the first network in a list of networks for a project. The project
// should be set via configuration instead of randomly taking the first.
networkName , err := getNetworkNameViaAPICall ( service , config . NetworkProjectID )
2016-02-22 00:13:08 +00:00
if err != nil {
return nil , err
}
2017-09-29 17:12:14 +00:00
networkURL = gceNetworkURL ( config . ApiEndpoint , netProjID , networkName )
} else if config . NetworkURL != "" {
networkURL = config . NetworkURL
} else {
networkURL = gceNetworkURL ( config . ApiEndpoint , netProjID , config . NetworkName )
2017-07-16 19:30:35 +00:00
}
2017-09-29 17:12:14 +00:00
if config . SubnetworkURL != "" {
subnetURL = config . SubnetworkURL
} else if config . SubnetworkName != "" {
subnetURL = gceSubnetworkURL ( config . ApiEndpoint , netProjID , config . Region , config . SubnetworkName )
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
if len ( config . ManagedZones ) == 0 {
config . ManagedZones , err = getZonesForRegion ( service , config . ProjectID , config . Region )
2016-02-22 00:13:08 +00:00
if err != nil {
return nil , err
}
}
2017-09-29 17:12:14 +00:00
if len ( config . ManagedZones ) != 1 {
glog . Infof ( "managing multiple zones: %v" , config . ManagedZones )
2016-02-22 00:13:08 +00:00
}
2016-04-17 20:19:22 +00:00
operationPollRateLimiter := flowcontrol . NewTokenBucketRateLimiter ( 10 , 100 ) // 10 qps, 100 bucket size.
2017-07-16 19:30:35 +00:00
gce := & GCECloud {
2017-05-21 00:11:38 +00:00
service : service ,
2017-07-31 20:35:10 +00:00
serviceAlpha : serviceAlpha ,
2017-05-21 00:11:38 +00:00
serviceBeta : serviceBeta ,
containerService : containerService ,
2017-07-16 19:30:35 +00:00
cloudkmsService : cloudkmsService ,
2017-09-29 17:12:14 +00:00
projectID : projID ,
networkProjectID : netProjID ,
2017-07-16 19:30:35 +00:00
onXPN : onXPN ,
2017-09-29 17:12:14 +00:00
region : config . Region ,
localZone : config . Zone ,
managedZones : config . ManagedZones ,
2016-04-17 20:19:22 +00:00
networkURL : networkURL ,
2017-09-29 17:12:14 +00:00
subnetworkURL : subnetURL ,
secondaryRangeName : config . SecondaryRangeName ,
nodeTags : config . NodeTags ,
nodeInstancePrefix : config . NodeInstancePrefix ,
useMetadataServer : config . UseMetadataServer ,
2016-04-17 20:19:22 +00:00
operationPollRateLimiter : operationPollRateLimiter ,
2017-09-29 17:12:14 +00:00
AlphaFeatureGate : config . AlphaFeatureGate ,
2017-07-16 19:30:35 +00:00
}
gce . manager = & GCEServiceManager { gce }
2017-09-29 17:12:14 +00:00
// Registering the KMS plugin only the first time.
kmsPluginRegisterOnce . Do ( func ( ) {
// Register the Google Cloud KMS based service in the KMS plugin registry.
encryptionconfig . KMSPluginRegistry . Register ( KMSServiceName , func ( config io . Reader ) ( envelope . Service , error ) {
return gce . getGCPCloudKMSService ( config )
} )
} )
2017-07-16 19:30:35 +00:00
return gce , nil
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
func tryConvertToProjectNames ( configProject , configNetworkProject string , service * compute . Service ) ( projID , netProjID string ) {
projID = configProject
if isProjectNumber ( projID ) {
projName , err := getProjectID ( service , projID )
if err != nil {
glog . Warningf ( "Failed to retrieve project %v while trying to retrieve its name. err %v" , projID , err )
} else {
projID = projName
}
}
netProjID = projID
if configNetworkProject != configProject {
netProjID = configNetworkProject
}
if isProjectNumber ( netProjID ) {
netProjName , err := getProjectID ( service , netProjID )
if err != nil {
glog . Warningf ( "Failed to retrieve network project %v while trying to retrieve its name. err %v" , netProjID , err )
} else {
netProjID = netProjName
}
}
return projID , netProjID
}
2017-07-16 19:30:35 +00:00
// Initialize takes in a clientBuilder and spawns a goroutine for watching the clusterid configmap.
// This must be called before utilizing the funcs of gce.ClusterID
func ( gce * GCECloud ) Initialize ( clientBuilder controller . ControllerClientBuilder ) {
gce . clientBuilder = clientBuilder
2017-09-29 17:12:14 +00:00
gce . client = clientBuilder . ClientOrDie ( "cloud-provider" )
if gce . OnXPN ( ) {
gce . eventBroadcaster = record . NewBroadcaster ( )
gce . eventBroadcaster . StartRecordingToSink ( & v1core . EventSinkImpl { Interface : v1core . New ( gce . client . Core ( ) . RESTClient ( ) ) . Events ( "" ) } )
gce . eventRecorder = gce . eventBroadcaster . NewRecorder ( scheme . Scheme , v1 . EventSource { Component : "gce-cloudprovider" } )
}
2017-07-16 19:30:35 +00:00
go gce . watchClusterID ( )
}
2017-05-21 00:11:38 +00:00
2016-02-22 00:13:08 +00:00
// LoadBalancer returns an implementation of LoadBalancer for Google Compute Engine.
func ( gce * GCECloud ) LoadBalancer ( ) ( cloudprovider . LoadBalancer , bool ) {
return gce , true
}
// Instances returns an implementation of Instances for Google Compute Engine.
func ( gce * GCECloud ) Instances ( ) ( cloudprovider . Instances , bool ) {
return gce , true
}
// Zones returns an implementation of Zones for Google Compute Engine.
func ( gce * GCECloud ) Zones ( ) ( cloudprovider . Zones , bool ) {
return gce , true
}
2017-04-01 14:42:02 +00:00
func ( gce * GCECloud ) Clusters ( ) ( cloudprovider . Clusters , bool ) {
2016-02-22 00:13:08 +00:00
return gce , true
}
2017-04-01 14:42:02 +00:00
// Routes returns an implementation of Routes for Google Compute Engine.
func ( gce * GCECloud ) Routes ( ) ( cloudprovider . Routes , bool ) {
return gce , true
2016-02-22 00:13:08 +00:00
}
2017-04-01 14:42:02 +00:00
// ProviderName returns the cloud provider ID.
func ( gce * GCECloud ) ProviderName ( ) string {
return ProviderName
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
// ProjectID returns the ProjectID corresponding to the project this cloud is in.
func ( g * GCECloud ) ProjectID ( ) string {
return g . projectID
}
// NetworkProjectID returns the ProjectID corresponding to the project this cluster's network is in.
func ( g * GCECloud ) NetworkProjectID ( ) string {
return g . networkProjectID
}
2017-07-16 19:30:35 +00:00
// Region returns the region
func ( gce * GCECloud ) Region ( ) string {
return gce . region
}
// OnXPN returns true if the cluster is running on a cross project network (XPN)
func ( gce * GCECloud ) OnXPN ( ) bool {
return gce . onXPN
}
// NetworkURL returns the network url
func ( gce * GCECloud ) NetworkURL ( ) string {
return gce . networkURL
}
// SubnetworkURL returns the subnetwork url
func ( gce * GCECloud ) SubnetworkURL ( ) string {
return gce . subnetworkURL
}
2017-04-01 14:42:02 +00:00
// Known-useless DNS search path.
var uselessDNSSearchRE = regexp . MustCompile ( ` ^[0-9]+.google.internal.$ ` )
2016-02-22 00:13:08 +00:00
2017-04-01 14:42:02 +00:00
// ScrubDNS filters DNS settings for pods.
func ( gce * GCECloud ) ScrubDNS ( nameservers , searches [ ] string ) ( nsOut , srchOut [ ] string ) {
// GCE has too many search paths by default. Filter the ones we know are useless.
for _ , s := range searches {
if ! uselessDNSSearchRE . MatchString ( s ) {
srchOut = append ( srchOut , s )
2016-02-22 00:13:08 +00:00
}
}
2017-04-01 14:42:02 +00:00
return nameservers , srchOut
2016-02-22 00:13:08 +00:00
}
2017-09-29 17:12:14 +00:00
// HasClusterID returns true if the cluster has a clusterID
func ( gce * GCECloud ) HasClusterID ( ) bool {
return true
}
// Project IDs cannot have a digit for the first characeter. If the id contains a digit,
// then it must be a project number.
func isProjectNumber ( idOrNumber string ) bool {
_ , err := strconv . ParseUint ( idOrNumber , 10 , 64 )
return err == nil
}
2017-04-01 14:42:02 +00:00
// GCECloud implements cloudprovider.Interface.
var _ cloudprovider . Interface = ( * GCECloud ) ( nil )
2016-02-22 00:13:08 +00:00
2017-07-16 19:30:35 +00:00
func gceNetworkURL ( apiEndpoint , project , network string ) string {
if apiEndpoint == "" {
apiEndpoint = gceComputeAPIEndpoint
}
return apiEndpoint + strings . Join ( [ ] string { "projects" , project , "global" , "networks" , network } , "/" )
}
func gceSubnetworkURL ( apiEndpoint , project , region , subnetwork string ) string {
if apiEndpoint == "" {
apiEndpoint = gceComputeAPIEndpoint
}
return apiEndpoint + strings . Join ( [ ] string { "projects" , project , "regions" , region , "subnetworks" , subnetwork } , "/" )
}
// getProjectIDInURL parses typical full resource URLS and shorter URLS
// https://www.googleapis.com/compute/v1/projects/myproject/global/networks/mycustom
// projects/myproject/global/networks/mycustom
// All return "myproject"
func getProjectIDInURL ( urlStr string ) ( string , error ) {
fields := strings . Split ( urlStr , "/" )
for i , v := range fields {
if v == "projects" && i < len ( fields ) - 1 {
return fields [ i + 1 ] , nil
}
}
return "" , fmt . Errorf ( "could not find project field in url: %v" , urlStr )
2016-02-22 00:13:08 +00:00
}
2017-04-01 14:42:02 +00:00
func getNetworkNameViaMetadata ( ) ( string , error ) {
result , err := metadata . Get ( "instance/network-interfaces/0/network" )
2016-02-22 00:13:08 +00:00
if err != nil {
2017-04-01 14:42:02 +00:00
return "" , err
2016-02-22 00:13:08 +00:00
}
2017-04-01 14:42:02 +00:00
parts := strings . Split ( result , "/" )
if len ( parts ) != 4 {
return "" , fmt . Errorf ( "unexpected response: %s" , result )
2016-04-17 20:19:22 +00:00
}
2017-04-01 14:42:02 +00:00
return parts [ 3 ] , nil
}
2016-04-17 20:19:22 +00:00
2017-04-01 14:42:02 +00:00
func getNetworkNameViaAPICall ( svc * compute . Service , projectID string ) ( string , error ) {
// TODO: use PageToken to list all not just the first 500
networkList , err := svc . Networks . List ( projectID ) . Do ( )
2016-02-22 00:13:08 +00:00
if err != nil {
2017-04-01 14:42:02 +00:00
return "" , err
2016-08-10 18:53:55 +00:00
}
2016-02-22 00:13:08 +00:00
2017-04-01 14:42:02 +00:00
if networkList == nil || len ( networkList . Items ) <= 0 {
2017-05-21 00:11:38 +00:00
return "" , fmt . Errorf ( "GCE Network List call returned no networks for project %q" , projectID )
2016-02-22 00:13:08 +00:00
}
2017-04-01 14:42:02 +00:00
return networkList . Items [ 0 ] . Name , nil
}
2016-03-19 23:00:11 +00:00
2017-09-29 17:12:14 +00:00
// getProjectID returns the project's string ID given a project number or string
func getProjectID ( svc * compute . Service , projectNumberOrID string ) ( string , error ) {
proj , err := svc . Projects . Get ( projectNumberOrID ) . Do ( )
if err != nil {
return "" , err
}
return proj . Name , nil
}
2017-04-01 14:42:02 +00:00
func getZonesForRegion ( svc * compute . Service , projectID , region string ) ( [ ] string , error ) {
// TODO: use PageToken to list all not just the first 500
listCall := svc . Zones . List ( projectID )
2016-02-22 00:13:08 +00:00
2017-04-01 14:42:02 +00:00
// Filtering by region doesn't seem to work
// (tested in https://cloud.google.com/compute/docs/reference/latest/zones/list)
// listCall = listCall.Filter("region eq " + region)
2016-02-22 00:13:08 +00:00
2017-04-01 14:42:02 +00:00
res , err := listCall . Do ( )
2016-02-22 00:13:08 +00:00
if err != nil {
2017-04-01 14:42:02 +00:00
return nil , fmt . Errorf ( "unexpected response listing zones: %v" , err )
2016-02-22 00:13:08 +00:00
}
2017-04-01 14:42:02 +00:00
zones := [ ] string { }
for _ , zone := range res . Items {
regionName := lastComponent ( zone . Region )
if regionName == region {
zones = append ( zones , zone . Name )
2016-02-22 00:13:08 +00:00
}
}
2017-04-01 14:42:02 +00:00
return zones , nil
2016-02-22 00:13:08 +00:00
}
2017-05-21 00:11:38 +00:00
func newOauthClient ( tokenSource oauth2 . TokenSource ) ( * http . Client , error ) {
if tokenSource == nil {
var err error
tokenSource , err = google . DefaultTokenSource (
oauth2 . NoContext ,
compute . CloudPlatformScope ,
compute . ComputeScope )
glog . Infof ( "Using DefaultTokenSource %#v" , tokenSource )
if err != nil {
return nil , err
}
} else {
glog . Infof ( "Using existing Token Source %#v" , tokenSource )
}
if err := wait . PollImmediate ( 5 * time . Second , 30 * time . Second , func ( ) ( bool , error ) {
if _ , err := tokenSource . Token ( ) ; err != nil {
glog . Errorf ( "error fetching initial token: %v" , err )
return false , nil
}
return true , nil
} ) ; err != nil {
return nil , err
}
2016-08-10 18:53:55 +00:00
2017-05-21 00:11:38 +00:00
return oauth2 . NewClient ( oauth2 . NoContext , tokenSource ) , nil
2016-02-22 00:13:08 +00:00
}
2017-07-16 19:30:35 +00:00
func ( manager * GCEServiceManager ) CreateDisk (
2017-09-29 17:12:14 +00:00
name string ,
sizeGb int64 ,
tagsStr string ,
diskType string ,
zone string ) ( gceObject , error ) {
diskTypeURI , err := manager . getDiskTypeURI (
manager . gce . region /* diskRegion */ , singleZone { zone } , diskType )
if err != nil {
return nil , err
}
2017-07-16 19:30:35 +00:00
2017-09-29 17:12:14 +00:00
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
diskToCreateAlpha := & computealpha . Disk {
Name : name ,
SizeGb : sizeGb ,
Description : tagsStr ,
Type : diskTypeURI ,
}
return manager . gce . serviceAlpha . Disks . Insert (
manager . gce . projectID , zone , diskToCreateAlpha ) . Do ( )
}
diskToCreateV1 := & compute . Disk {
Name : name ,
SizeGb : sizeGb ,
Description : tagsStr ,
Type : diskTypeURI ,
}
return manager . gce . service . Disks . Insert (
manager . gce . projectID , zone , diskToCreateV1 ) . Do ( )
}
func ( manager * GCEServiceManager ) CreateRegionalDisk (
name string ,
sizeGb int64 ,
tagsStr string ,
diskType string ,
replicaZones sets . String ) ( gceObject , error ) {
diskTypeURI , err := manager . getDiskTypeURI (
manager . gce . region /* diskRegion */ , multiZone { replicaZones } , diskType )
if err != nil {
return nil , err
}
fullyQualifiedReplicaZones := [ ] string { }
for _ , replicaZone := range replicaZones . UnsortedList ( ) {
fullyQualifiedReplicaZones = append (
fullyQualifiedReplicaZones , manager . getReplicaZoneURI ( replicaZone ) )
}
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
diskToCreateAlpha := & computealpha . Disk {
Name : name ,
SizeGb : sizeGb ,
Description : tagsStr ,
Type : diskTypeURI ,
ReplicaZones : fullyQualifiedReplicaZones ,
}
return manager . gce . serviceAlpha . RegionDisks . Insert (
manager . gce . projectID , manager . gce . region , diskToCreateAlpha ) . Do ( )
}
return nil , fmt . Errorf ( "The regional PD feature is only available via the GCE Alpha API. Enable \"GCEDiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature." )
}
func ( manager * GCEServiceManager ) AttachDisk (
disk * GCEDisk ,
readWrite string ,
instanceZone string ,
instanceName string ) ( gceObject , error ) {
source , err := manager . getDiskSourceURI ( disk )
if err != nil {
return nil , err
}
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
attachedDiskAlpha := & computealpha . AttachedDisk {
DeviceName : disk . Name ,
Kind : disk . Kind ,
Mode : readWrite ,
Source : source ,
Type : diskTypePersistent ,
}
return manager . gce . serviceAlpha . Instances . AttachDisk (
manager . gce . projectID , instanceZone , instanceName , attachedDiskAlpha ) . Do ( )
}
attachedDiskV1 := & compute . AttachedDisk {
DeviceName : disk . Name ,
Kind : disk . Kind ,
Mode : readWrite ,
Source : source ,
Type : disk . Type ,
}
return manager . gce . service . Instances . AttachDisk (
manager . gce . projectID , instanceZone , instanceName , attachedDiskV1 ) . Do ( )
}
func ( manager * GCEServiceManager ) DetachDisk (
instanceZone string ,
instanceName string ,
devicePath string ) ( gceObject , error ) {
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
manager . gce . serviceAlpha . Instances . DetachDisk (
manager . gce . projectID , instanceZone , instanceName , devicePath ) . Do ( )
}
return manager . gce . service . Instances . DetachDisk (
manager . gce . projectID , instanceZone , instanceName , devicePath ) . Do ( )
2017-07-16 19:30:35 +00:00
}
func ( manager * GCEServiceManager ) GetDisk (
zone string ,
2017-09-29 17:12:14 +00:00
diskName string ) ( * GCEDisk , error ) {
if zone == "" {
return nil , fmt . Errorf ( "Can not fetch disk %q. Zone is empty." , diskName )
}
if diskName == "" {
return nil , fmt . Errorf ( "Can not fetch disk. Zone is specified (%q). But disk name is empty." , zone )
}
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
diskAlpha , err := manager . gce . serviceAlpha . Disks . Get (
manager . gce . projectID , zone , diskName ) . Do ( )
if err != nil {
return nil , err
}
var zoneInfo zoneType
if len ( diskAlpha . ReplicaZones ) > 1 {
zones := sets . NewString ( )
for _ , zoneURI := range diskAlpha . ReplicaZones {
zones . Insert ( lastComponent ( zoneURI ) )
}
zoneInfo = multiZone { zones }
} else {
zoneInfo = singleZone { lastComponent ( diskAlpha . Zone ) }
if diskAlpha . Zone == "" {
zoneInfo = singleZone { lastComponent ( zone ) }
}
}
region := strings . TrimSpace ( lastComponent ( diskAlpha . Region ) )
if region == "" {
region , err = manager . getRegionFromZone ( zoneInfo )
if err != nil {
return nil , fmt . Errorf ( "failed to extract region from zone for %q/%q err=%v" , zone , diskName , err )
}
}
return & GCEDisk {
ZoneInfo : zoneInfo ,
Region : region ,
Name : diskAlpha . Name ,
Kind : diskAlpha . Kind ,
Type : diskAlpha . Type ,
} , nil
}
diskStable , err := manager . gce . service . Disks . Get (
manager . gce . projectID , zone , diskName ) . Do ( )
if err != nil {
return nil , err
}
zoneInfo := singleZone { strings . TrimSpace ( lastComponent ( diskStable . Zone ) ) }
if zoneInfo . zone == "" {
zoneInfo = singleZone { zone }
}
region , err := manager . getRegionFromZone ( zoneInfo )
if err != nil {
return nil , fmt . Errorf ( "failed to extract region from zone for %q/%q err=%v" , zone , diskName , err )
}
return & GCEDisk {
ZoneInfo : zoneInfo ,
Region : region ,
Name : diskStable . Name ,
Kind : diskStable . Kind ,
Type : diskStable . Type ,
} , nil
}
func ( manager * GCEServiceManager ) GetRegionalDisk (
diskName string ) ( * GCEDisk , error ) {
2017-07-16 19:30:35 +00:00
2017-09-29 17:12:14 +00:00
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
diskAlpha , err := manager . gce . serviceAlpha . RegionDisks . Get (
manager . gce . projectID , manager . gce . region , diskName ) . Do ( )
if err != nil {
return nil , err
}
zones := sets . NewString ( )
for _ , zoneURI := range diskAlpha . ReplicaZones {
zones . Insert ( lastComponent ( zoneURI ) )
}
return & GCEDisk {
ZoneInfo : multiZone { zones } ,
Region : lastComponent ( diskAlpha . Region ) ,
Name : diskAlpha . Name ,
Kind : diskAlpha . Kind ,
Type : diskAlpha . Type ,
} , nil
}
return nil , fmt . Errorf ( "The regional PD feature is only available via the GCE Alpha API. Enable \"GCEDiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature." )
2017-07-16 19:30:35 +00:00
}
func ( manager * GCEServiceManager ) DeleteDisk (
zone string ,
2017-09-29 17:12:14 +00:00
diskName string ) ( gceObject , error ) {
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
return manager . gce . serviceAlpha . Disks . Delete (
manager . gce . projectID , zone , diskName ) . Do ( )
}
return manager . gce . service . Disks . Delete (
manager . gce . projectID , zone , diskName ) . Do ( )
}
func ( manager * GCEServiceManager ) DeleteRegionalDisk (
diskName string ) ( gceObject , error ) {
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
return manager . gce . serviceAlpha . RegionDisks . Delete (
manager . gce . projectID , manager . gce . region , diskName ) . Do ( )
}
2017-07-16 19:30:35 +00:00
2017-09-29 17:12:14 +00:00
return nil , fmt . Errorf ( "DeleteRegionalDisk is a regional PD feature and is only available via the GCE Alpha API. Enable \"GCEDiskAlphaAPI\" in the list of \"alpha-features\" in \"gce.conf\" to use the feature." )
2017-07-16 19:30:35 +00:00
}
2017-09-29 17:12:14 +00:00
func ( manager * GCEServiceManager ) WaitForZoneOp (
op gceObject , zone string , mc * metricContext ) error {
2017-07-16 19:30:35 +00:00
return manager . gce . waitForZoneOp ( op , zone , mc )
}
2017-09-29 17:12:14 +00:00
func ( manager * GCEServiceManager ) WaitForRegionalOp (
op gceObject , mc * metricContext ) error {
return manager . gce . waitForRegionOp ( op , manager . gce . region , mc )
}
func ( manager * GCEServiceManager ) getDiskSourceURI ( disk * GCEDisk ) ( string , error ) {
getProjectsAPIEndpoint := manager . getProjectsAPIEndpoint ( )
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
getProjectsAPIEndpoint = manager . getProjectsAPIEndpointAlpha ( )
}
switch zoneInfo := disk . ZoneInfo . ( type ) {
case singleZone :
if zoneInfo . zone == "" || disk . Region == "" {
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "PD does not have zone/region information: %#v" , disk )
}
return getProjectsAPIEndpoint + fmt . Sprintf (
diskSourceURITemplateSingleZone ,
manager . gce . projectID ,
zoneInfo . zone ,
disk . Name ) , nil
case multiZone :
if zoneInfo . replicaZones == nil || zoneInfo . replicaZones . Len ( ) <= 0 {
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "PD is regional but does not have any replicaZones specified: %v" , disk )
}
return getProjectsAPIEndpoint + fmt . Sprintf (
diskSourceURITemplateRegional ,
manager . gce . projectID ,
disk . Region ,
disk . Name ) , nil
case nil :
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "PD did not have ZoneInfo: %v" , disk )
default :
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "disk.ZoneInfo has unexpected type %T" , zoneInfo )
}
}
func ( manager * GCEServiceManager ) getDiskTypeURI (
diskRegion string , diskZoneInfo zoneType , diskType string ) ( string , error ) {
getProjectsAPIEndpoint := manager . getProjectsAPIEndpoint ( )
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
getProjectsAPIEndpoint = manager . getProjectsAPIEndpointAlpha ( )
}
switch zoneInfo := diskZoneInfo . ( type ) {
case singleZone :
if zoneInfo . zone == "" {
return "" , fmt . Errorf ( "zone is empty: %v" , zoneInfo )
}
return getProjectsAPIEndpoint + fmt . Sprintf (
diskTypeURITemplateSingleZone ,
manager . gce . projectID ,
zoneInfo . zone ,
diskType ) , nil
case multiZone :
if zoneInfo . replicaZones == nil || zoneInfo . replicaZones . Len ( ) <= 0 {
return "" , fmt . Errorf ( "zoneInfo is regional but does not have any replicaZones specified: %v" , zoneInfo )
}
return getProjectsAPIEndpoint + fmt . Sprintf (
diskTypeURITemplateRegional ,
manager . gce . projectID ,
diskRegion ,
diskType ) , nil
case nil :
return "" , fmt . Errorf ( "zoneInfo nil" )
default :
return "" , fmt . Errorf ( "zoneInfo has unexpected type %T" , zoneInfo )
}
}
func ( manager * GCEServiceManager ) getReplicaZoneURI ( zone string ) string {
getProjectsAPIEndpoint := manager . getProjectsAPIEndpoint ( )
if manager . gce . AlphaFeatureGate . Enabled ( GCEDiskAlphaFeatureGate ) {
getProjectsAPIEndpoint = manager . getProjectsAPIEndpointAlpha ( )
}
return getProjectsAPIEndpoint + fmt . Sprintf (
replicaZoneURITemplateSingleZone ,
manager . gce . projectID ,
zone )
}
func ( manager * GCEServiceManager ) getProjectsAPIEndpoint ( ) string {
projectsApiEndpoint := gceComputeAPIEndpoint + "projects/"
if manager . gce . service != nil {
projectsApiEndpoint = manager . gce . service . BasePath
}
return projectsApiEndpoint
}
func ( manager * GCEServiceManager ) getProjectsAPIEndpointAlpha ( ) string {
projectsApiEndpoint := gceComputeAPIEndpointAlpha + "projects/"
if manager . gce . service != nil {
projectsApiEndpoint = manager . gce . serviceAlpha . BasePath
}
return projectsApiEndpoint
}
func ( manager * GCEServiceManager ) getRegionFromZone ( zoneInfo zoneType ) ( string , error ) {
var zone string
switch zoneInfo := zoneInfo . ( type ) {
case singleZone :
if zoneInfo . zone == "" {
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "PD is single zone, but zone is not specified: %#v" , zoneInfo )
}
zone = zoneInfo . zone
case multiZone :
if zoneInfo . replicaZones == nil || zoneInfo . replicaZones . Len ( ) <= 0 {
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "PD is regional but does not have any replicaZones specified: %v" , zoneInfo )
}
zone = zoneInfo . replicaZones . UnsortedList ( ) [ 0 ]
case nil :
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "zoneInfo is nil" )
default :
// Unexpected, but sanity-check
return "" , fmt . Errorf ( "zoneInfo has unexpected type %T" , zoneInfo )
}
region , err := GetGCERegion ( zone )
if err != nil {
glog . Warningf ( "failed to parse GCE region from zone %q: %v" , zone , err )
region = manager . gce . region
}
return region , nil
}