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