2014-06-17 17:50:42 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 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.
|
|
|
|
*/
|
|
|
|
|
2014-08-19 22:34:35 +00:00
|
|
|
package gce_cloud
|
2014-06-17 17:50:42 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2014-09-03 21:12:20 +00:00
|
|
|
"io"
|
2014-06-17 17:50:42 +00:00
|
|
|
"io/ioutil"
|
2014-06-18 04:58:41 +00:00
|
|
|
"net"
|
2014-06-17 17:50:42 +00:00
|
|
|
"net/http"
|
2014-06-30 04:00:22 +00:00
|
|
|
"os/exec"
|
2014-08-05 17:58:43 +00:00
|
|
|
"path"
|
2014-06-17 17:50:42 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2015-01-05 21:16:18 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
|
|
|
|
2014-06-17 17:50:42 +00:00
|
|
|
compute "code.google.com/p/google-api-go-client/compute/v1"
|
2014-11-13 20:35:03 +00:00
|
|
|
container "code.google.com/p/google-api-go-client/container/v1beta1"
|
2014-09-23 05:20:48 +00:00
|
|
|
"github.com/golang/glog"
|
2015-01-15 07:17:28 +00:00
|
|
|
"golang.org/x/oauth2"
|
|
|
|
"golang.org/x/oauth2/google"
|
2014-06-17 17:50:42 +00:00
|
|
|
)
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// GCECloud is an implementation of Interface, TCPLoadBalancer and Instances for Google Compute Engine.
|
2014-06-17 17:50:42 +00:00
|
|
|
type GCECloud struct {
|
2014-11-13 20:35:03 +00:00
|
|
|
service *compute.Service
|
|
|
|
containerService *container.Service
|
|
|
|
projectID string
|
|
|
|
zone string
|
|
|
|
instanceID string
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
|
2014-08-19 23:40:47 +00:00
|
|
|
func init() {
|
2014-09-03 21:12:20 +00:00
|
|
|
cloudprovider.RegisterCloudProvider("gce", func(config io.Reader) (cloudprovider.Interface, error) { return newGCECloud() })
|
2014-08-19 23:40:47 +00:00
|
|
|
}
|
|
|
|
|
2014-08-05 17:58:43 +00:00
|
|
|
func getMetadata(url string) (string, error) {
|
2014-06-17 17:50:42 +00:00
|
|
|
client := http.Client{}
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
|
|
if err != nil {
|
2014-08-05 17:58:43 +00:00
|
|
|
return "", err
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
req.Header.Add("X-Google-Metadata-Request", "True")
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
2014-08-05 17:58:43 +00:00
|
|
|
return "", err
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
data, err := ioutil.ReadAll(res.Body)
|
2014-08-05 17:58:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(data), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getProjectAndZone() (string, string, error) {
|
|
|
|
url := "http://metadata/computeMetadata/v1/instance/zone"
|
|
|
|
result, err := getMetadata(url)
|
2014-06-17 17:50:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
2014-08-05 17:58:43 +00:00
|
|
|
parts := strings.Split(result, "/")
|
2014-06-17 17:50:42 +00:00
|
|
|
if len(parts) != 4 {
|
2014-11-20 10:00:36 +00:00
|
|
|
return "", "", fmt.Errorf("unexpected response: %s", result)
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
return parts[1], parts[3], nil
|
|
|
|
}
|
|
|
|
|
2014-08-05 17:58:43 +00:00
|
|
|
func getInstanceID() (string, error) {
|
|
|
|
url := "http://metadata/computeMetadata/v1/instance/hostname"
|
|
|
|
result, err := getMetadata(url)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
parts := strings.Split(result, ".")
|
|
|
|
if len(parts) == 0 {
|
2014-11-20 10:00:36 +00:00
|
|
|
return "", fmt.Errorf("unexpected response: %s", result)
|
2014-08-05 17:58:43 +00:00
|
|
|
}
|
|
|
|
return parts[0], nil
|
|
|
|
}
|
|
|
|
|
2014-08-19 23:40:47 +00:00
|
|
|
// newGCECloud creates a new instance of GCECloud.
|
|
|
|
func newGCECloud() (*GCECloud, error) {
|
2014-06-17 17:50:42 +00:00
|
|
|
projectID, zone, err := getProjectAndZone()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-08-05 17:58:43 +00:00
|
|
|
// TODO: if we want to use this on a machine that doesn't have the http://metadata server
|
|
|
|
// e.g. on a user's machine (not VM) somewhere, we need to have an alternative for
|
|
|
|
// instance id lookup.
|
|
|
|
instanceID, err := getInstanceID()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-01-15 07:17:28 +00:00
|
|
|
client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource(""))
|
2014-06-17 17:50:42 +00:00
|
|
|
svc, err := compute.New(client)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-11-13 20:35:03 +00:00
|
|
|
containerSvc, err := container.New(client)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-06-17 17:50:42 +00:00
|
|
|
return &GCECloud{
|
2014-11-13 20:35:03 +00:00
|
|
|
service: svc,
|
|
|
|
containerService: containerSvc,
|
|
|
|
projectID: projectID,
|
|
|
|
zone: zone,
|
|
|
|
instanceID: instanceID,
|
2014-06-17 17:50:42 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2014-11-13 20:35:03 +00:00
|
|
|
func (gce *GCECloud) Clusters() (cloudprovider.Clusters, bool) {
|
|
|
|
return gce, true
|
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// TCPLoadBalancer returns an implementation of TCPLoadBalancer for Google Compute Engine.
|
2014-08-19 22:34:35 +00:00
|
|
|
func (gce *GCECloud) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) {
|
2014-06-18 05:28:44 +00:00
|
|
|
return gce, true
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// Instances returns an implementation of Instances for Google Compute Engine.
|
2014-08-19 22:34:35 +00:00
|
|
|
func (gce *GCECloud) Instances() (cloudprovider.Instances, bool) {
|
2014-06-18 05:28:44 +00:00
|
|
|
return gce, true
|
2014-06-18 04:58:41 +00:00
|
|
|
}
|
|
|
|
|
2014-07-28 22:42:08 +00:00
|
|
|
// Zones returns an implementation of Zones for Google Compute Engine.
|
2014-08-19 22:34:35 +00:00
|
|
|
func (gce *GCECloud) Zones() (cloudprovider.Zones, bool) {
|
2014-07-28 22:42:08 +00:00
|
|
|
return gce, true
|
|
|
|
}
|
|
|
|
|
2014-06-17 17:50:42 +00:00
|
|
|
func makeHostLink(projectID, zone, host string) string {
|
2014-09-24 16:41:30 +00:00
|
|
|
host = canonicalizeInstanceName(host)
|
2014-06-17 17:50:42 +00:00
|
|
|
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances/%s",
|
|
|
|
projectID, zone, host)
|
|
|
|
}
|
|
|
|
|
2014-12-18 23:46:10 +00:00
|
|
|
// Session Affinity Type string
|
|
|
|
type GCEAffinityType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
// AffinityTypeNone - no session affinity.
|
|
|
|
GCEAffinityTypeNone GCEAffinityType = "None"
|
|
|
|
// AffinityTypeClientIP is the Client IP based.
|
|
|
|
GCEAffinityTypeClientIP GCEAffinityType = "CLIENT_IP"
|
|
|
|
// AffinityTypeClientIP is the Client IP based.
|
|
|
|
GCEAffinityTypeClientIPProto GCEAffinityType = "CLIENT_IP_PROTO"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (gce *GCECloud) makeTargetPool(name, region string, hosts []string, affinityType GCEAffinityType) (string, error) {
|
2014-06-17 17:50:42 +00:00
|
|
|
var instances []string
|
|
|
|
for _, host := range hosts {
|
|
|
|
instances = append(instances, makeHostLink(gce.projectID, gce.zone, host))
|
|
|
|
}
|
|
|
|
pool := &compute.TargetPool{
|
2014-12-18 23:46:10 +00:00
|
|
|
Name: name,
|
|
|
|
Instances: instances,
|
|
|
|
SessionAffinity: string(affinityType),
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
_, err := gce.service.TargetPools.Insert(gce.projectID, region, pool).Do()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
link := fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/regions/%s/targetPools/%s", gce.projectID, region, name)
|
|
|
|
return link, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gce *GCECloud) waitForRegionOp(op *compute.Operation, region string) error {
|
|
|
|
pollOp := op
|
|
|
|
for pollOp.Status != "DONE" {
|
|
|
|
var err error
|
|
|
|
time.Sleep(time.Second * 10)
|
|
|
|
pollOp, err = gce.service.RegionOperations.Get(gce.projectID, region, op.Name).Do()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// TCPLoadBalancerExists is an implementation of TCPLoadBalancer.TCPLoadBalancerExists.
|
2014-06-17 17:50:42 +00:00
|
|
|
func (gce *GCECloud) TCPLoadBalancerExists(name, region string) (bool, error) {
|
|
|
|
_, err := gce.service.ForwardingRules.Get(gce.projectID, region, name).Do()
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2014-12-18 23:46:10 +00:00
|
|
|
//translate from what K8s supports to what the cloud provider supports for session affinity.
|
|
|
|
func translateAffinityType(affinityType api.AffinityType) GCEAffinityType {
|
|
|
|
switch affinityType {
|
|
|
|
case api.AffinityTypeClientIP:
|
|
|
|
return GCEAffinityTypeClientIP
|
|
|
|
case api.AffinityTypeNone:
|
|
|
|
return GCEAffinityTypeNone
|
|
|
|
default:
|
|
|
|
glog.Errorf("unexpected affinity type: %v", affinityType)
|
|
|
|
return GCEAffinityTypeNone
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// CreateTCPLoadBalancer is an implementation of TCPLoadBalancer.CreateTCPLoadBalancer.
|
2014-12-18 23:46:10 +00:00
|
|
|
func (gce *GCECloud) CreateTCPLoadBalancer(name, region string, externalIP net.IP, port int, hosts []string, affinityType api.AffinityType) (net.IP, error) {
|
|
|
|
pool, err := gce.makeTargetPool(name, region, hosts, translateAffinityType(affinityType))
|
2014-06-17 17:50:42 +00:00
|
|
|
if err != nil {
|
2014-11-12 04:08:33 +00:00
|
|
|
return nil, err
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
req := &compute.ForwardingRule{
|
|
|
|
Name: name,
|
|
|
|
IPProtocol: "TCP",
|
|
|
|
PortRange: strconv.Itoa(port),
|
|
|
|
Target: pool,
|
|
|
|
}
|
2014-11-12 04:08:33 +00:00
|
|
|
if len(externalIP) > 0 {
|
|
|
|
req.IPAddress = externalIP.String()
|
|
|
|
}
|
|
|
|
op, err := gce.service.ForwardingRules.Insert(gce.projectID, region, req).Do()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = gce.waitForRegionOp(op, region)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fwd, err := gce.service.ForwardingRules.Get(gce.projectID, region, name).Do()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return net.ParseIP(fwd.IPAddress), nil
|
2014-06-17 17:50:42 +00:00
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// UpdateTCPLoadBalancer is an implementation of TCPLoadBalancer.UpdateTCPLoadBalancer.
|
2014-06-17 17:50:42 +00:00
|
|
|
func (gce *GCECloud) UpdateTCPLoadBalancer(name, region string, hosts []string) error {
|
|
|
|
var refs []*compute.InstanceReference
|
|
|
|
for _, host := range hosts {
|
|
|
|
refs = append(refs, &compute.InstanceReference{host})
|
|
|
|
}
|
|
|
|
req := &compute.TargetPoolsAddInstanceRequest{
|
|
|
|
Instances: refs,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := gce.service.TargetPools.AddInstance(gce.projectID, region, name, req).Do()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// DeleteTCPLoadBalancer is an implementation of TCPLoadBalancer.DeleteTCPLoadBalancer.
|
2014-06-17 17:50:42 +00:00
|
|
|
func (gce *GCECloud) DeleteTCPLoadBalancer(name, region string) error {
|
|
|
|
_, err := gce.service.ForwardingRules.Delete(gce.projectID, region, name).Do()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = gce.service.TargetPools.Delete(gce.projectID, region, name).Do()
|
|
|
|
return err
|
|
|
|
}
|
2014-06-18 04:58:41 +00:00
|
|
|
|
2014-09-24 16:41:30 +00:00
|
|
|
// Take a GCE instance 'hostname' and break it down to something that can be fed
|
|
|
|
// to the GCE API client library. Basically this means reducing 'kubernetes-
|
|
|
|
// minion-2.c.my-proj.internal' to 'kubernetes-minion-2' if necessary.
|
|
|
|
func canonicalizeInstanceName(name string) string {
|
|
|
|
ix := strings.Index(name, ".")
|
|
|
|
if ix != -1 {
|
|
|
|
name = name[:ix]
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// IPAddress is an implementation of Instances.IPAddress.
|
2014-06-18 04:58:41 +00:00
|
|
|
func (gce *GCECloud) IPAddress(instance string) (net.IP, error) {
|
2014-09-24 16:41:30 +00:00
|
|
|
instance = canonicalizeInstanceName(instance)
|
|
|
|
res, err := gce.service.Instances.Get(gce.projectID, gce.zone, instance).Do()
|
2014-09-23 05:20:48 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Failed to retrieve TargetInstance resource for instance:%s", instance)
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-06-18 04:58:41 +00:00
|
|
|
ip := net.ParseIP(res.NetworkInterfaces[0].AccessConfigs[0].NatIP)
|
|
|
|
if ip == nil {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("invalid network IP: %s", res.NetworkInterfaces[0].AccessConfigs[0].NatIP)
|
2014-06-18 04:58:41 +00:00
|
|
|
}
|
|
|
|
return ip, nil
|
|
|
|
}
|
2014-06-25 05:27:33 +00:00
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// fqdnSuffix is hacky function to compute the delta between hostame and hostname -f.
|
2014-06-30 04:00:22 +00:00
|
|
|
func fqdnSuffix() (string, error) {
|
|
|
|
fullHostname, err := exec.Command("hostname", "-f").Output()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
hostname, err := exec.Command("hostname").Output()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(string(fullHostname)[len(string(hostname)):]), nil
|
|
|
|
}
|
|
|
|
|
2014-07-10 11:46:56 +00:00
|
|
|
// List is an implementation of Instances.List.
|
2014-06-25 05:27:33 +00:00
|
|
|
func (gce *GCECloud) List(filter string) ([]string, error) {
|
2014-06-30 04:00:22 +00:00
|
|
|
// GCE gives names without their fqdn suffix, so get that here for appending.
|
|
|
|
// This is needed because the kubelet looks for its jobs in /registry/hosts/<fqdn>/pods
|
|
|
|
// We should really just replace this convention, with a negotiated naming protocol for kubelet's
|
|
|
|
// to register with the master.
|
|
|
|
suffix, err := fqdnSuffix()
|
|
|
|
if err != nil {
|
|
|
|
return []string{}, err
|
|
|
|
}
|
|
|
|
if len(suffix) > 0 {
|
|
|
|
suffix = "." + suffix
|
|
|
|
}
|
2014-06-25 05:27:33 +00:00
|
|
|
listCall := gce.service.Instances.List(gce.projectID, gce.zone)
|
|
|
|
if len(filter) > 0 {
|
|
|
|
listCall = listCall.Filter("name eq " + filter)
|
|
|
|
}
|
|
|
|
res, err := listCall.Do()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var instances []string
|
|
|
|
for _, instance := range res.Items {
|
2014-06-30 04:00:22 +00:00
|
|
|
instances = append(instances, instance.Name+suffix)
|
2014-06-25 05:27:33 +00:00
|
|
|
}
|
|
|
|
return instances, nil
|
|
|
|
}
|
2014-07-28 22:42:08 +00:00
|
|
|
|
2015-01-05 21:16:18 +00:00
|
|
|
// cpu is in cores, memory is in GiB
|
|
|
|
func makeResources(cpu float64, memory float64) *api.NodeResources {
|
2014-09-26 23:28:30 +00:00
|
|
|
return &api.NodeResources{
|
|
|
|
Capacity: api.ResourceList{
|
2015-01-05 21:16:18 +00:00
|
|
|
api.ResourceCPU: *resource.NewMilliQuantity(int64(cpu*1000), resource.DecimalSI),
|
|
|
|
api.ResourceMemory: *resource.NewQuantity(int64(memory*1024*1024*1024), resource.BinarySI),
|
2014-09-26 23:28:30 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func canonicalizeMachineType(machineType string) string {
|
|
|
|
ix := strings.LastIndex(machineType, "/")
|
|
|
|
return machineType[ix+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gce *GCECloud) GetNodeResources(name string) (*api.NodeResources, error) {
|
|
|
|
instance := canonicalizeInstanceName(name)
|
|
|
|
instanceCall := gce.service.Instances.Get(gce.projectID, gce.zone, instance)
|
|
|
|
res, err := instanceCall.Do()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-01-05 21:16:18 +00:00
|
|
|
// TODO: actually read machine size instead of this awful hack.
|
2014-09-26 23:28:30 +00:00
|
|
|
switch canonicalizeMachineType(res.MachineType) {
|
|
|
|
case "f1-micro":
|
|
|
|
return makeResources(1, 0.6), nil
|
|
|
|
case "g1-small":
|
|
|
|
return makeResources(1, 1.70), nil
|
|
|
|
case "n1-standard-1":
|
|
|
|
return makeResources(1, 3.75), nil
|
|
|
|
case "n1-standard-2":
|
|
|
|
return makeResources(2, 7.5), nil
|
|
|
|
case "n1-standard-4":
|
|
|
|
return makeResources(4, 15), nil
|
|
|
|
case "n1-standard-8":
|
|
|
|
return makeResources(8, 30), nil
|
|
|
|
case "n1-standard-16":
|
|
|
|
return makeResources(16, 30), nil
|
|
|
|
default:
|
|
|
|
glog.Errorf("unknown machine: %s", res.MachineType)
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-19 22:34:35 +00:00
|
|
|
func (gce *GCECloud) GetZone() (cloudprovider.Zone, error) {
|
2014-08-04 16:58:10 +00:00
|
|
|
region, err := getGceRegion(gce.zone)
|
|
|
|
if err != nil {
|
2014-08-19 22:34:35 +00:00
|
|
|
return cloudprovider.Zone{}, err
|
2014-08-04 16:58:10 +00:00
|
|
|
}
|
2014-08-19 22:34:35 +00:00
|
|
|
return cloudprovider.Zone{
|
2014-08-04 16:58:10 +00:00
|
|
|
FailureDomain: gce.zone,
|
|
|
|
Region: region,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2014-08-05 17:58:43 +00:00
|
|
|
func (gce *GCECloud) AttachDisk(diskName string, readOnly bool) error {
|
|
|
|
disk, err := gce.getDisk(diskName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
readWrite := "READ_WRITE"
|
|
|
|
if readOnly {
|
|
|
|
readWrite = "READ_ONLY"
|
|
|
|
}
|
|
|
|
attachedDisk := gce.convertDiskToAttachedDisk(disk, readWrite)
|
|
|
|
_, err = gce.service.Instances.AttachDisk(gce.projectID, gce.zone, gce.instanceID, attachedDisk).Do()
|
2014-12-11 21:00:26 +00:00
|
|
|
if err != nil {
|
|
|
|
// Check if the disk is already attached to this instance. We do this only
|
|
|
|
// in the error case, since it is expected to be exceptional.
|
|
|
|
instance, err := gce.service.Instances.Get(gce.projectID, gce.zone, gce.instanceID).Do()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, disk := range instance.Disks {
|
|
|
|
if disk.InitializeParams.DiskName == diskName {
|
|
|
|
// Disk is already attached, we're good to go.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2014-08-05 17:58:43 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gce *GCECloud) DetachDisk(devicePath string) error {
|
|
|
|
_, err := gce.service.Instances.DetachDisk(gce.projectID, gce.zone, gce.instanceID, devicePath).Do()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gce *GCECloud) getDisk(diskName string) (*compute.Disk, error) {
|
|
|
|
return gce.service.Disks.Get(gce.projectID, gce.zone, diskName).Do()
|
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// getGceRegion returns region of the gce zone. Zone names
|
|
|
|
// are of the form: ${region-name}-${ix}.
|
2014-08-04 16:58:10 +00:00
|
|
|
// For example "us-central1-b" has a region of "us-central1".
|
|
|
|
// So we look for the last '-' and trim to just before that.
|
|
|
|
func getGceRegion(zone string) (string, error) {
|
|
|
|
ix := strings.LastIndex(zone, "-")
|
|
|
|
if ix == -1 {
|
|
|
|
return "", fmt.Errorf("unexpected zone: %s", zone)
|
|
|
|
}
|
|
|
|
return zone[:ix], nil
|
2014-07-28 22:42:08 +00:00
|
|
|
}
|
2014-08-05 17:58:43 +00:00
|
|
|
|
|
|
|
// Converts a Disk resource to an AttachedDisk resource.
|
|
|
|
func (gce *GCECloud) convertDiskToAttachedDisk(disk *compute.Disk, readWrite string) *compute.AttachedDisk {
|
|
|
|
return &compute.AttachedDisk{
|
|
|
|
DeviceName: disk.Name,
|
|
|
|
Kind: disk.Kind,
|
|
|
|
Mode: readWrite,
|
|
|
|
Source: "https://" + path.Join("www.googleapis.com/compute/v1/projects/", gce.projectID, "zones", gce.zone, "disks", disk.Name),
|
|
|
|
Type: "PERSISTENT",
|
|
|
|
}
|
|
|
|
}
|
2014-11-13 20:35:03 +00:00
|
|
|
|
|
|
|
func (gce *GCECloud) ListClusters() ([]string, error) {
|
|
|
|
list, err := gce.containerService.Projects.Clusters.List(gce.projectID).Do()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
result := []string{}
|
|
|
|
for _, cluster := range list.Clusters {
|
|
|
|
result = append(result, cluster.Name)
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gce *GCECloud) Master(clusterName string) (string, error) {
|
|
|
|
return "k8s-" + clusterName + "-master.internal", nil
|
|
|
|
}
|