github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/gce/google/raw.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package google
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"path"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/utils"
    15  	"google.golang.org/api/compute/v1"
    16  	"google.golang.org/api/googleapi"
    17  )
    18  
    19  const diskTypesBase = "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/%s"
    20  
    21  // These are attempt strategies used in waitOperation.
    22  var (
    23  	// TODO(ericsnow) Tune the timeouts and delays.
    24  
    25  	attemptsLong = utils.AttemptStrategy{
    26  		Total: 5 * time.Minute,
    27  		Delay: 2 * time.Second,
    28  	}
    29  )
    30  
    31  func convertRawAPIError(err error) error {
    32  	if err2, ok := err.(*googleapi.Error); ok {
    33  		if err2.Code == http.StatusNotFound {
    34  			return errors.NewNotFound(err, "")
    35  		}
    36  	}
    37  	return err
    38  }
    39  
    40  type rawConn struct {
    41  	*compute.Service
    42  }
    43  
    44  func (rc *rawConn) GetProject(projectID string) (*compute.Project, error) {
    45  	call := rc.Projects.Get(projectID)
    46  	proj, err := call.Do()
    47  	return proj, errors.Trace(err)
    48  }
    49  
    50  func (rc *rawConn) GetInstance(projectID, zone, id string) (*compute.Instance, error) {
    51  	call := rc.Instances.Get(projectID, zone, id)
    52  	inst, err := call.Do()
    53  	return inst, errors.Trace(err)
    54  }
    55  
    56  func (rc *rawConn) ListInstances(projectID, prefix string, statuses ...string) ([]*compute.Instance, error) {
    57  	call := rc.Instances.AggregatedList(projectID)
    58  	call = call.Filter("name eq " + prefix + ".*")
    59  
    60  	var results []*compute.Instance
    61  	for {
    62  		rawResult, err := call.Do()
    63  		if err != nil {
    64  			return nil, errors.Trace(err)
    65  		}
    66  
    67  		for _, instList := range rawResult.Items {
    68  			for _, inst := range instList.Instances {
    69  				if !checkInstStatus(inst, statuses) {
    70  					continue
    71  				}
    72  				results = append(results, inst)
    73  			}
    74  		}
    75  		if rawResult.NextPageToken == "" {
    76  			break
    77  		}
    78  		call = call.PageToken(rawResult.NextPageToken)
    79  	}
    80  	return results, nil
    81  }
    82  
    83  func checkInstStatus(inst *compute.Instance, statuses []string) bool {
    84  	if len(statuses) == 0 {
    85  		return true
    86  	}
    87  	for _, status := range statuses {
    88  		if inst.Status == status {
    89  			return true
    90  		}
    91  	}
    92  	return false
    93  }
    94  
    95  func (rc *rawConn) AddInstance(projectID, zoneName string, spec *compute.Instance) error {
    96  	call := rc.Instances.Insert(projectID, zoneName, spec)
    97  	operation, err := call.Do()
    98  	if err != nil {
    99  		// We are guaranteed the insert failed at the point.
   100  		return errors.Annotate(err, "sending new instance request")
   101  	}
   102  
   103  	err = rc.waitOperation(projectID, operation, attemptsLong)
   104  	return errors.Trace(err)
   105  }
   106  
   107  func (rc *rawConn) RemoveInstance(projectID, zone, id string) error {
   108  	call := rc.Instances.Delete(projectID, zone, id)
   109  	operation, err := call.Do()
   110  	if err != nil {
   111  		return errors.Trace(err)
   112  	}
   113  
   114  	err = rc.waitOperation(projectID, operation, attemptsLong)
   115  	return errors.Trace(err)
   116  }
   117  
   118  func (rc *rawConn) GetFirewall(projectID, name string) (*compute.Firewall, error) {
   119  	call := rc.Firewalls.List(projectID)
   120  	call = call.Filter("name eq " + name)
   121  	firewallList, err := call.Do()
   122  	if err != nil {
   123  		return nil, errors.Annotate(err, "while getting firewall from GCE")
   124  	}
   125  
   126  	if len(firewallList.Items) == 0 {
   127  		return nil, errors.NotFoundf("firewall %q", name)
   128  	}
   129  	return firewallList.Items[0], nil
   130  }
   131  
   132  func (rc *rawConn) AddFirewall(projectID string, firewall *compute.Firewall) error {
   133  	call := rc.Firewalls.Insert(projectID, firewall)
   134  	operation, err := call.Do()
   135  	if err != nil {
   136  		return errors.Trace(err)
   137  	}
   138  
   139  	err = rc.waitOperation(projectID, operation, attemptsLong)
   140  	return errors.Trace(err)
   141  }
   142  
   143  func (rc *rawConn) UpdateFirewall(projectID, name string, firewall *compute.Firewall) error {
   144  	call := rc.Firewalls.Update(projectID, name, firewall)
   145  	operation, err := call.Do()
   146  	if err != nil {
   147  		return errors.Trace(err)
   148  	}
   149  
   150  	err = rc.waitOperation(projectID, operation, attemptsLong)
   151  	return errors.Trace(err)
   152  }
   153  
   154  func (rc *rawConn) RemoveFirewall(projectID, name string) error {
   155  	call := rc.Firewalls.Delete(projectID, name)
   156  	operation, err := call.Do()
   157  	if err != nil {
   158  		return errors.Trace(convertRawAPIError(err))
   159  	}
   160  
   161  	err = rc.waitOperation(projectID, operation, attemptsLong)
   162  	return errors.Trace(convertRawAPIError(err))
   163  }
   164  
   165  func (rc *rawConn) ListAvailabilityZones(projectID, region string) ([]*compute.Zone, error) {
   166  	call := rc.Zones.List(projectID)
   167  	if region != "" {
   168  		call = call.Filter("name eq " + region + "-.*")
   169  	}
   170  
   171  	var results []*compute.Zone
   172  	for {
   173  		zoneList, err := call.Do()
   174  		if err != nil {
   175  			return nil, errors.Trace(err)
   176  		}
   177  
   178  		for _, zone := range zoneList.Items {
   179  			results = append(results, zone)
   180  		}
   181  		if zoneList.NextPageToken == "" {
   182  			break
   183  		}
   184  		call = call.PageToken(zoneList.NextPageToken)
   185  	}
   186  	return results, nil
   187  }
   188  
   189  func formatDiskType(project, zone string, spec *compute.Disk) {
   190  	// empty will default in pd-standard
   191  	if spec.Type == "" {
   192  		return
   193  	}
   194  	// see https://cloud.google.com/compute/docs/reference/latest/disks#resource
   195  	if strings.HasPrefix(spec.Type, "http") || strings.HasPrefix(spec.Type, "projects") || strings.HasPrefix(spec.Type, "global") {
   196  		return
   197  	}
   198  	spec.Type = fmt.Sprintf(diskTypesBase, project, zone, spec.Type)
   199  }
   200  
   201  func (rc *rawConn) CreateDisk(project, zone string, spec *compute.Disk) error {
   202  	ds := rc.Service.Disks
   203  	formatDiskType(project, zone, spec)
   204  	call := ds.Insert(project, zone, spec)
   205  	op, err := call.Do()
   206  	if err != nil {
   207  		return errors.Annotate(err, "could not create a new disk")
   208  	}
   209  	return errors.Trace(rc.waitOperation(project, op, attemptsLong))
   210  }
   211  
   212  func (rc *rawConn) ListDisks(project, zone string) ([]*compute.Disk, error) {
   213  	ds := rc.Service.Disks
   214  	call := ds.List(project, zone)
   215  	var results []*compute.Disk
   216  	for {
   217  		diskList, err := call.Do()
   218  		if err != nil {
   219  			return nil, errors.Trace(err)
   220  		}
   221  		for _, disk := range diskList.Items {
   222  			results = append(results, disk)
   223  		}
   224  		if diskList.NextPageToken == "" {
   225  			break
   226  		}
   227  		call = call.PageToken(diskList.NextPageToken)
   228  	}
   229  	return results, nil
   230  }
   231  
   232  func (rc *rawConn) RemoveDisk(project, zone, id string) error {
   233  	ds := rc.Disks
   234  	call := ds.Delete(project, zone, id)
   235  	op, err := call.Do()
   236  	if err != nil {
   237  		return errors.Annotatef(err, "could not delete disk %q", id)
   238  	}
   239  	return errors.Trace(rc.waitOperation(project, op, attemptsLong))
   240  }
   241  
   242  func (rc *rawConn) GetDisk(project, zone, id string) (*compute.Disk, error) {
   243  	ds := rc.Disks
   244  	call := ds.Get(project, zone, id)
   245  	disk, err := call.Do()
   246  	if err != nil {
   247  		return nil, errors.Annotatef(err, "cannot get disk %q at zone %q in project %q", id, zone, project)
   248  	}
   249  	return disk, nil
   250  }
   251  
   252  func (rc *rawConn) AttachDisk(project, zone, instanceId string, disk *compute.AttachedDisk) error {
   253  	call := rc.Instances.AttachDisk(project, zone, instanceId, disk)
   254  	_, err := call.Do() // Perhaps return something from the Op
   255  	if err != nil {
   256  		return errors.Annotatef(err, "cannot attach volume into %q", instanceId)
   257  	}
   258  	return nil
   259  }
   260  
   261  func (rc *rawConn) DetachDisk(project, zone, instanceId, diskDeviceName string) error {
   262  	call := rc.Instances.DetachDisk(project, zone, instanceId, diskDeviceName)
   263  	_, err := call.Do()
   264  	if err != nil {
   265  		return errors.Annotatef(err, "cannot detach volume from %q", instanceId)
   266  	}
   267  	return nil
   268  }
   269  
   270  func (rc *rawConn) InstanceDisks(project, zone, instanceId string) ([]*compute.AttachedDisk, error) {
   271  	instance, err := rc.GetInstance(project, zone, instanceId)
   272  	if err != nil {
   273  		return nil, errors.Annotatef(err, "cannot get instance %q to list its disks", instanceId)
   274  	}
   275  	return instance.Disks, nil
   276  }
   277  
   278  type waitError struct {
   279  	op    *compute.Operation
   280  	cause error
   281  }
   282  
   283  func (err waitError) Error() string {
   284  	if err.cause != nil {
   285  		return fmt.Sprintf("GCE operation %q failed: %v", err.op.Name, err.cause)
   286  	}
   287  	return fmt.Sprintf("GCE operation %q failed", err.op.Name)
   288  }
   289  
   290  func isWaitError(err error) bool {
   291  	_, ok := err.(*waitError)
   292  	return ok
   293  }
   294  
   295  type opDoer interface {
   296  	Do() (*compute.Operation, error)
   297  }
   298  
   299  // checkOperation requests a new copy of the given operation from the
   300  // GCE API and returns it. The new copy will have the operation's
   301  // current status.
   302  func (rc *rawConn) checkOperation(projectID string, op *compute.Operation) (*compute.Operation, error) {
   303  	var call opDoer
   304  	if op.Zone != "" {
   305  		zoneName := path.Base(op.Zone)
   306  		call = rc.ZoneOperations.Get(projectID, zoneName, op.Name)
   307  	} else if op.Region != "" {
   308  		region := path.Base(op.Region)
   309  		call = rc.RegionOperations.Get(projectID, region, op.Name)
   310  	} else {
   311  		call = rc.GlobalOperations.Get(projectID, op.Name)
   312  	}
   313  
   314  	operation, err := doOpCall(call)
   315  	if err != nil {
   316  		return nil, errors.Annotatef(err, "request for GCE operation %q failed", op.Name)
   317  	}
   318  	return operation, nil
   319  }
   320  
   321  var doOpCall = func(call opDoer) (*compute.Operation, error) {
   322  	return call.Do()
   323  }
   324  
   325  // waitOperation waits for the provided operation to reach the "done"
   326  // status. It follows the given attempt strategy (e.g. wait time between
   327  // attempts) and may time out.
   328  func (rc *rawConn) waitOperation(projectID string, op *compute.Operation, attempts utils.AttemptStrategy) error {
   329  	started := time.Now()
   330  	logger.Infof("GCE operation %q, waiting...", op.Name)
   331  	for a := attempts.Start(); a.Next(); {
   332  		if op.Status == StatusDone {
   333  			break
   334  		}
   335  
   336  		var err error
   337  		op, err = rc.checkOperation(projectID, op)
   338  		if err != nil {
   339  			return errors.Trace(err)
   340  		}
   341  	}
   342  	if op.Status != StatusDone {
   343  		err := errors.Errorf("timed out after %d seconds", time.Now().Sub(started)/time.Second)
   344  		return waitError{op, err}
   345  	}
   346  	if op.Error != nil {
   347  		for _, err := range op.Error.Errors {
   348  			logger.Errorf("GCE operation error: (%s) %s", err.Code, err.Message)
   349  		}
   350  		return waitError{op, nil}
   351  	}
   352  
   353  	logger.Infof("GCE operation %q finished", op.Name)
   354  	return nil
   355  }