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"
|
|
|
|
"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-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"
|
|
|
|
"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
|
|
|
|
|
|
|
gceComputeAPIEndpoint = "https://www.googleapis.com/compute/v1/"
|
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
|
|
|
|
clientBuilder controller.ControllerClientBuilder
|
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
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceManager interface {
|
|
|
|
// Creates a new persistent disk on GCE with the given disk spec.
|
|
|
|
CreateDisk(project string, zone string, disk *compute.Disk) (*compute.Operation, error)
|
|
|
|
|
|
|
|
// Gets the persistent disk from GCE with the given diskName.
|
|
|
|
GetDisk(project string, zone string, diskName string) (*compute.Disk, error)
|
|
|
|
|
|
|
|
// Deletes the persistent disk from GCE with the given diskName.
|
|
|
|
DeleteDisk(project string, zone string, disk string) (*compute.Operation, error)
|
|
|
|
|
|
|
|
// Waits until GCE reports the given operation in the given zone as done.
|
|
|
|
WaitForZoneOp(op *compute.Operation, zone string, mc *metricContext) error
|
|
|
|
}
|
|
|
|
|
|
|
|
type GCEServiceManager struct {
|
|
|
|
gce *GCECloud
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Config struct {
|
|
|
|
Global struct {
|
2016-06-21 18:58:43 +00:00
|
|
|
TokenURL string `gcfg:"token-url"`
|
|
|
|
TokenBody string `gcfg:"token-body"`
|
|
|
|
ProjectID string `gcfg:"project-id"`
|
|
|
|
NetworkName string `gcfg:"network-name"`
|
2017-07-16 19:30:35 +00:00
|
|
|
SubnetworkName string `gcfg:"subnetwork-name"`
|
2016-06-21 18:58:43 +00:00
|
|
|
NodeTags []string `gcfg:"node-tags"`
|
|
|
|
NodeInstancePrefix string `gcfg:"node-instance-prefix"`
|
|
|
|
Multizone bool `gcfg:"multizone"`
|
2017-07-16 19:30:35 +00:00
|
|
|
// Specifying ApiEndpoint will override the default GCE compute API endpoint.
|
|
|
|
ApiEndpoint string `gcfg:"api-endpoint"`
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the ProjectID corresponding to the project this cloud is in.
|
|
|
|
func (g *GCECloud) GetProjectID() string {
|
|
|
|
return g.projectID
|
|
|
|
}
|
|
|
|
|
2016-02-22 00:13:08 +00:00
|
|
|
// newGCECloud creates a new instance of GCECloud.
|
|
|
|
func newGCECloud(config io.Reader) (*GCECloud, error) {
|
2017-07-16 19:30:35 +00:00
|
|
|
apiEndpoint := ""
|
2016-02-22 00:13:08 +00:00
|
|
|
projectID, zone, err := getProjectAndZone()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
region, err := GetGCERegion(zone)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
networkName, err := getNetworkNameViaMetadata()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-16 19:30:35 +00:00
|
|
|
networkURL := gceNetworkURL("", projectID, networkName)
|
|
|
|
subnetworkURL := ""
|
2016-02-22 00:13:08 +00:00
|
|
|
|
|
|
|
// By default, Kubernetes clusters only run against one zone
|
|
|
|
managedZones := []string{zone}
|
|
|
|
|
|
|
|
tokenSource := google.ComputeTokenSource("")
|
2016-05-09 18:53:58 +00:00
|
|
|
var nodeTags []string
|
2016-06-21 18:58:43 +00:00
|
|
|
var nodeInstancePrefix string
|
2016-02-22 00:13:08 +00:00
|
|
|
if config != nil {
|
|
|
|
var cfg Config
|
|
|
|
if err := gcfg.ReadInto(&cfg, config); err != nil {
|
|
|
|
glog.Errorf("Couldn't read config: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-05-09 18:53:58 +00:00
|
|
|
glog.Infof("Using GCE provider config %+v", cfg)
|
2017-07-16 19:30:35 +00:00
|
|
|
if cfg.Global.ApiEndpoint != "" {
|
|
|
|
apiEndpoint = cfg.Global.ApiEndpoint
|
|
|
|
}
|
2016-02-22 00:13:08 +00:00
|
|
|
if cfg.Global.ProjectID != "" {
|
|
|
|
projectID = cfg.Global.ProjectID
|
|
|
|
}
|
2017-07-16 19:30:35 +00:00
|
|
|
|
|
|
|
if cfg.Global.NetworkName != "" && strings.Contains(cfg.Global.NetworkName, "/") {
|
|
|
|
networkURL = cfg.Global.NetworkName
|
|
|
|
} else {
|
|
|
|
networkURL = gceNetworkURL(apiEndpoint, projectID, networkName)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.Global.SubnetworkName != "" && strings.Contains(cfg.Global.SubnetworkName, "/") {
|
|
|
|
subnetworkURL = cfg.Global.SubnetworkName
|
|
|
|
} else {
|
|
|
|
subnetworkURL = gceSubnetworkURL(apiEndpoint, cfg.Global.ProjectID, region, cfg.Global.SubnetworkName)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
if cfg.Global.TokenURL != "" {
|
2016-06-21 18:58:43 +00:00
|
|
|
tokenSource = NewAltTokenSource(cfg.Global.TokenURL, cfg.Global.TokenBody)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
2016-05-09 18:53:58 +00:00
|
|
|
nodeTags = cfg.Global.NodeTags
|
2016-06-21 18:58:43 +00:00
|
|
|
nodeInstancePrefix = cfg.Global.NodeInstancePrefix
|
2016-02-22 00:13:08 +00:00
|
|
|
if cfg.Global.Multizone {
|
|
|
|
managedZones = nil // Use all zones in region
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-16 19:30:35 +00:00
|
|
|
return CreateGCECloud(apiEndpoint, projectID, region, zone, managedZones, networkURL, subnetworkURL,
|
|
|
|
nodeTags, nodeInstancePrefix, tokenSource, true /* useMetadataServer */)
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a GCECloud object using the specified parameters.
|
|
|
|
// 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-07-16 19:30:35 +00:00
|
|
|
func CreateGCECloud(apiEndpoint, projectID, region, zone string, managedZones []string, networkURL, subnetworkURL string, nodeTags []string,
|
2017-04-01 14:42:02 +00:00
|
|
|
nodeInstancePrefix string, tokenSource oauth2.TokenSource, useMetadataServer bool) (*GCECloud, error) {
|
|
|
|
|
2017-05-21 00:11:38 +00:00
|
|
|
client, err := newOauthClient(tokenSource)
|
|
|
|
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-05-21 00:11:38 +00:00
|
|
|
client, err = newOauthClient(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-07-16 19:30:35 +00:00
|
|
|
client, err = newOauthClient(tokenSource)
|
|
|
|
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/
|
|
|
|
if apiEndpoint != "" {
|
|
|
|
service.BasePath = fmt.Sprintf("%sprojects/", apiEndpoint)
|
|
|
|
serviceBeta.BasePath = fmt.Sprintf("%sprojects/", strings.Replace(apiEndpoint, "v1", "beta", 0))
|
|
|
|
serviceAlpha.BasePath = fmt.Sprintf("%sprojects/", strings.Replace(apiEndpoint, "v1", "alpha", 0))
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-02-22 00:13:08 +00:00
|
|
|
if networkURL == "" {
|
2017-05-21 00:11:38 +00:00
|
|
|
networkName, err := getNetworkNameViaAPICall(service, projectID)
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-07-16 19:30:35 +00:00
|
|
|
networkURL = gceNetworkURL(apiEndpoint, projectID, networkName)
|
|
|
|
}
|
|
|
|
|
|
|
|
networkProjectID, err := getProjectIDInURL(networkURL)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
2017-07-16 19:30:35 +00:00
|
|
|
onXPN := networkProjectID != projectID
|
2016-02-22 00:13:08 +00:00
|
|
|
|
|
|
|
if len(managedZones) == 0 {
|
2017-05-21 00:11:38 +00:00
|
|
|
managedZones, err = getZonesForRegion(service, projectID, region)
|
2016-02-22 00:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(managedZones) != 1 {
|
|
|
|
glog.Infof("managing multiple zones: %v", managedZones)
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
serviceBeta: serviceBeta,
|
|
|
|
containerService: containerService,
|
2017-07-16 19:30:35 +00:00
|
|
|
cloudkmsService: cloudkmsService,
|
2016-04-17 20:19:22 +00:00
|
|
|
projectID: projectID,
|
2017-07-16 19:30:35 +00:00
|
|
|
networkProjectID: networkProjectID,
|
|
|
|
onXPN: onXPN,
|
2016-04-17 20:19:22 +00:00
|
|
|
region: region,
|
|
|
|
localZone: zone,
|
|
|
|
managedZones: managedZones,
|
|
|
|
networkURL: networkURL,
|
2017-07-16 19:30:35 +00:00
|
|
|
subnetworkURL: subnetworkURL,
|
2016-05-09 18:53:58 +00:00
|
|
|
nodeTags: nodeTags,
|
2016-06-21 18:58:43 +00:00
|
|
|
nodeInstancePrefix: nodeInstancePrefix,
|
2016-04-17 20:19:22 +00:00
|
|
|
useMetadataServer: useMetadataServer,
|
|
|
|
operationPollRateLimiter: operationPollRateLimiter,
|
2017-07-16 19:30:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gce.manager = &GCEServiceManager{gce}
|
|
|
|
return gce, nil
|
2016-02-22 00:13:08 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
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-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-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-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(
|
|
|
|
project string,
|
|
|
|
zone string,
|
|
|
|
disk *compute.Disk) (*compute.Operation, error) {
|
|
|
|
|
|
|
|
return manager.gce.service.Disks.Insert(project, zone, disk).Do()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (manager *GCEServiceManager) GetDisk(
|
|
|
|
project string,
|
|
|
|
zone string,
|
|
|
|
diskName string) (*compute.Disk, error) {
|
|
|
|
|
|
|
|
return manager.gce.service.Disks.Get(project, zone, diskName).Do()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (manager *GCEServiceManager) DeleteDisk(
|
|
|
|
project string,
|
|
|
|
zone string,
|
|
|
|
diskName string) (*compute.Operation, error) {
|
|
|
|
|
|
|
|
return manager.gce.service.Disks.Delete(project, zone, diskName).Do()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (manager *GCEServiceManager) WaitForZoneOp(op *compute.Operation, zone string, mc *metricContext) error {
|
|
|
|
return manager.gce.waitForZoneOp(op, zone, mc)
|
|
|
|
}
|