github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils"
    14  	"google.golang.org/api/compute/v1"
    15  	"google.golang.org/api/googleapi"
    16  )
    17  
    18  // These are attempt strategies used in waitOperation.
    19  var (
    20  	// TODO(ericsnow) Tune the timeouts and delays.
    21  
    22  	attemptsLong = utils.AttemptStrategy{
    23  		Total: 5 * time.Minute,
    24  		Delay: 2 * time.Second,
    25  	}
    26  	attemptsShort = utils.AttemptStrategy{
    27  		Total: 1 * time.Minute,
    28  		Delay: 1 * time.Second,
    29  	}
    30  )
    31  
    32  func convertRawAPIError(err error) error {
    33  	if err2, ok := err.(*googleapi.Error); ok {
    34  		if err2.Code == http.StatusNotFound {
    35  			return errors.NewNotFound(err, "")
    36  		}
    37  	}
    38  	return err
    39  }
    40  
    41  type rawConn struct {
    42  	*compute.Service
    43  }
    44  
    45  func (rc *rawConn) GetProject(projectID string) (*compute.Project, error) {
    46  	call := rc.Projects.Get(projectID)
    47  	proj, err := call.Do()
    48  	return proj, errors.Trace(err)
    49  }
    50  
    51  func (rc *rawConn) GetInstance(projectID, zone, id string) (*compute.Instance, error) {
    52  	call := rc.Instances.Get(projectID, zone, id)
    53  	inst, err := call.Do()
    54  	return inst, errors.Trace(err)
    55  }
    56  
    57  func (rc *rawConn) ListInstances(projectID, prefix string, statuses ...string) ([]*compute.Instance, error) {
    58  	call := rc.Instances.AggregatedList(projectID)
    59  	call = call.Filter("name eq " + prefix + ".*")
    60  
    61  	var results []*compute.Instance
    62  	for {
    63  		rawResult, err := call.Do()
    64  		if err != nil {
    65  			return nil, errors.Trace(err)
    66  		}
    67  
    68  		for _, instList := range rawResult.Items {
    69  			for _, inst := range instList.Instances {
    70  				if !checkInstStatus(inst, statuses) {
    71  					continue
    72  				}
    73  				results = append(results, inst)
    74  			}
    75  		}
    76  		if rawResult.NextPageToken == "" {
    77  			break
    78  		}
    79  		call = call.PageToken(rawResult.NextPageToken)
    80  	}
    81  	return results, nil
    82  }
    83  
    84  func checkInstStatus(inst *compute.Instance, statuses []string) bool {
    85  	if len(statuses) == 0 {
    86  		return true
    87  	}
    88  	for _, status := range statuses {
    89  		if inst.Status == status {
    90  			return true
    91  		}
    92  	}
    93  	return false
    94  }
    95  
    96  func (rc *rawConn) AddInstance(projectID, zoneName string, spec *compute.Instance) error {
    97  	call := rc.Instances.Insert(projectID, zoneName, spec)
    98  	operation, err := call.Do()
    99  	if err != nil {
   100  		// We are guaranteed the insert failed at the point.
   101  		return errors.Annotate(err, "sending new instance request")
   102  	}
   103  
   104  	err = rc.waitOperation(projectID, operation, attemptsLong)
   105  	return errors.Trace(err)
   106  }
   107  
   108  func (rc *rawConn) RemoveInstance(projectID, zone, id string) error {
   109  	call := rc.Instances.Delete(projectID, zone, id)
   110  	operation, err := call.Do()
   111  	if err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  
   115  	err = rc.waitOperation(projectID, operation, attemptsLong)
   116  	return errors.Trace(err)
   117  }
   118  
   119  func (rc *rawConn) GetFirewall(projectID, name string) (*compute.Firewall, error) {
   120  	call := rc.Firewalls.List(projectID)
   121  	call = call.Filter("name eq " + name)
   122  	firewallList, err := call.Do()
   123  	if err != nil {
   124  		return nil, errors.Annotate(err, "while getting firewall from GCE")
   125  	}
   126  
   127  	if len(firewallList.Items) == 0 {
   128  		return nil, errors.NotFoundf("firewall %q", name)
   129  	}
   130  	return firewallList.Items[0], nil
   131  }
   132  
   133  func (rc *rawConn) AddFirewall(projectID string, firewall *compute.Firewall) error {
   134  	call := rc.Firewalls.Insert(projectID, firewall)
   135  	operation, err := call.Do()
   136  	if err != nil {
   137  		return errors.Trace(err)
   138  	}
   139  
   140  	err = rc.waitOperation(projectID, operation, attemptsLong)
   141  	return errors.Trace(err)
   142  }
   143  
   144  func (rc *rawConn) UpdateFirewall(projectID, name string, firewall *compute.Firewall) error {
   145  	call := rc.Firewalls.Update(projectID, name, firewall)
   146  	operation, err := call.Do()
   147  	if err != nil {
   148  		return errors.Trace(err)
   149  	}
   150  
   151  	err = rc.waitOperation(projectID, operation, attemptsLong)
   152  	return errors.Trace(err)
   153  }
   154  
   155  func (rc *rawConn) RemoveFirewall(projectID, name string) error {
   156  	call := rc.Firewalls.Delete(projectID, name)
   157  	operation, err := call.Do()
   158  	if err != nil {
   159  		return errors.Trace(convertRawAPIError(err))
   160  	}
   161  
   162  	err = rc.waitOperation(projectID, operation, attemptsLong)
   163  	return errors.Trace(convertRawAPIError(err))
   164  }
   165  
   166  func (rc *rawConn) ListAvailabilityZones(projectID, region string) ([]*compute.Zone, error) {
   167  	call := rc.Zones.List(projectID)
   168  	if region != "" {
   169  		call = call.Filter("name eq " + region + "-.*")
   170  	}
   171  
   172  	var results []*compute.Zone
   173  	for {
   174  		zoneList, err := call.Do()
   175  		if err != nil {
   176  			return nil, errors.Trace(err)
   177  		}
   178  
   179  		for _, zone := range zoneList.Items {
   180  			results = append(results, zone)
   181  		}
   182  		if zoneList.NextPageToken == "" {
   183  			break
   184  		}
   185  		call = call.PageToken(zoneList.NextPageToken)
   186  	}
   187  	return results, nil
   188  }
   189  
   190  type waitError struct {
   191  	op    *compute.Operation
   192  	cause error
   193  }
   194  
   195  func (err waitError) Error() string {
   196  	if err.cause != nil {
   197  		return fmt.Sprintf("GCE operation %q failed: %v", err.op.Name, err.cause)
   198  	}
   199  	return fmt.Sprintf("GCE operation %q failed", err.op.Name)
   200  }
   201  
   202  func isWaitError(err error) bool {
   203  	_, ok := err.(*waitError)
   204  	return ok
   205  }
   206  
   207  type opDoer interface {
   208  	Do() (*compute.Operation, error)
   209  }
   210  
   211  // checkOperation requests a new copy of the given operation from the
   212  // GCE API and returns it. The new copy will have the operation's
   213  // current status.
   214  func (rc *rawConn) checkOperation(projectID string, op *compute.Operation) (*compute.Operation, error) {
   215  	var call opDoer
   216  	if op.Zone != "" {
   217  		zoneName := path.Base(op.Zone)
   218  		call = rc.ZoneOperations.Get(projectID, zoneName, op.Name)
   219  	} else if op.Region != "" {
   220  		region := path.Base(op.Region)
   221  		call = rc.RegionOperations.Get(projectID, region, op.Name)
   222  	} else {
   223  		call = rc.GlobalOperations.Get(projectID, op.Name)
   224  	}
   225  
   226  	operation, err := doOpCall(call)
   227  	if err != nil {
   228  		return nil, errors.Annotatef(err, "request for GCE operation %q failed", op.Name)
   229  	}
   230  	return operation, nil
   231  }
   232  
   233  var doOpCall = func(call opDoer) (*compute.Operation, error) {
   234  	return call.Do()
   235  }
   236  
   237  // waitOperation waits for the provided operation to reach the "done"
   238  // status. It follows the given attempt strategy (e.g. wait time between
   239  // attempts) and may time out.
   240  func (rc *rawConn) waitOperation(projectID string, op *compute.Operation, attempts utils.AttemptStrategy) error {
   241  	started := time.Now()
   242  	logger.Infof("GCE operation %q, waiting...", op.Name)
   243  	for a := attempts.Start(); a.Next(); {
   244  		if op.Status == StatusDone {
   245  			break
   246  		}
   247  
   248  		var err error
   249  		op, err = rc.checkOperation(projectID, op)
   250  		if err != nil {
   251  			return errors.Trace(err)
   252  		}
   253  	}
   254  	if op.Status != StatusDone {
   255  		err := errors.Errorf("timed out after %d seconds", time.Now().Sub(started)/time.Second)
   256  		return waitError{op, err}
   257  	}
   258  	if op.Error != nil {
   259  		for _, err := range op.Error.Errors {
   260  			logger.Errorf("GCE operation error: (%s) %s", err.Code, err.Message)
   261  		}
   262  		return waitError{op, nil}
   263  	}
   264  
   265  	logger.Infof("GCE operation %q finished", op.Name)
   266  	return nil
   267  }