github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"golang.org/x/net/context"
    16  	"google.golang.org/api/compute/v1"
    17  	"google.golang.org/api/googleapi"
    18  )
    19  
    20  const diskTypesBase = "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/%s"
    21  
    22  // These are attempt strategies used in waitOperation.
    23  var (
    24  	// TODO(ericsnow) Tune the timeouts and delays.
    25  	// TODO(katco): 2016-08-09: lp:1611427
    26  	attemptsLong = utils.AttemptStrategy{
    27  		Total: 5 * time.Minute,
    28  		Delay: 2 * 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) GetFirewalls(projectID, namePrefix string) ([]*compute.Firewall, error) {
   120  	call := rc.Firewalls.List(projectID)
   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", namePrefix)
   128  	}
   129  	var result []*compute.Firewall
   130  	for _, fw := range firewallList.Items {
   131  		if strings.HasPrefix(fw.Name, namePrefix) {
   132  			result = append(result, fw)
   133  		}
   134  	}
   135  	return result, nil
   136  }
   137  
   138  func (rc *rawConn) AddFirewall(projectID string, firewall *compute.Firewall) error {
   139  	call := rc.Firewalls.Insert(projectID, firewall)
   140  	operation, err := call.Do()
   141  	if err != nil {
   142  		return errors.Trace(err)
   143  	}
   144  
   145  	err = rc.waitOperation(projectID, operation, attemptsLong)
   146  	return errors.Trace(err)
   147  }
   148  
   149  func (rc *rawConn) UpdateFirewall(projectID, name string, firewall *compute.Firewall) error {
   150  	call := rc.Firewalls.Update(projectID, name, firewall)
   151  	operation, err := call.Do()
   152  	if err != nil {
   153  		return errors.Trace(err)
   154  	}
   155  
   156  	err = rc.waitOperation(projectID, operation, attemptsLong)
   157  	return errors.Trace(err)
   158  }
   159  
   160  func (rc *rawConn) RemoveFirewall(projectID, name string) error {
   161  	call := rc.Firewalls.Delete(projectID, name)
   162  	operation, err := call.Do()
   163  	if err != nil {
   164  		return errors.Trace(convertRawAPIError(err))
   165  	}
   166  
   167  	err = rc.waitOperation(projectID, operation, attemptsLong)
   168  	return errors.Trace(convertRawAPIError(err))
   169  }
   170  
   171  func (rc *rawConn) ListAvailabilityZones(projectID, region string) ([]*compute.Zone, error) {
   172  	call := rc.Zones.List(projectID)
   173  	if region != "" {
   174  		call = call.Filter("name eq " + region + "-.*")
   175  	}
   176  
   177  	var results []*compute.Zone
   178  	for {
   179  		zoneList, err := call.Do()
   180  		if err != nil {
   181  			return nil, errors.Trace(err)
   182  		}
   183  
   184  		for _, zone := range zoneList.Items {
   185  			results = append(results, zone)
   186  		}
   187  		if zoneList.NextPageToken == "" {
   188  			break
   189  		}
   190  		call = call.PageToken(zoneList.NextPageToken)
   191  	}
   192  	return results, nil
   193  }
   194  
   195  func formatDiskType(project, zone string, spec *compute.Disk) {
   196  	// empty will default in pd-standard
   197  	if spec.Type == "" {
   198  		return
   199  	}
   200  	// see https://cloud.google.com/compute/docs/reference/latest/disks#resource
   201  	if strings.HasPrefix(spec.Type, "http") || strings.HasPrefix(spec.Type, "projects") || strings.HasPrefix(spec.Type, "global") {
   202  		return
   203  	}
   204  	spec.Type = fmt.Sprintf(diskTypesBase, project, zone, spec.Type)
   205  }
   206  
   207  func (rc *rawConn) CreateDisk(project, zone string, spec *compute.Disk) error {
   208  	ds := rc.Service.Disks
   209  	formatDiskType(project, zone, spec)
   210  	call := ds.Insert(project, zone, spec)
   211  	op, err := call.Do()
   212  	if err != nil {
   213  		return errors.Annotate(err, "could not create a new disk")
   214  	}
   215  	return errors.Trace(rc.waitOperation(project, op, attemptsLong))
   216  }
   217  
   218  func (rc *rawConn) ListDisks(project string) ([]*compute.Disk, error) {
   219  	ds := rc.Service.Disks
   220  	call := ds.AggregatedList(project)
   221  	var results []*compute.Disk
   222  	for {
   223  		diskList, err := call.Do()
   224  		if err != nil {
   225  			return nil, errors.Trace(err)
   226  		}
   227  		for _, list := range diskList.Items {
   228  			results = append(results, list.Disks...)
   229  		}
   230  		if diskList.NextPageToken == "" {
   231  			break
   232  		}
   233  		call = call.PageToken(diskList.NextPageToken)
   234  	}
   235  	return results, nil
   236  }
   237  
   238  func (rc *rawConn) RemoveDisk(project, zone, id string) error {
   239  	ds := rc.Disks
   240  	call := ds.Delete(project, zone, id)
   241  	op, err := call.Do()
   242  	if err != nil {
   243  		return errors.Annotatef(err, "could not delete disk %q", id)
   244  	}
   245  	return errors.Trace(rc.waitOperation(project, op, attemptsLong))
   246  }
   247  
   248  func (rc *rawConn) GetDisk(project, zone, id string) (*compute.Disk, error) {
   249  	ds := rc.Disks
   250  	call := ds.Get(project, zone, id)
   251  	disk, err := call.Do()
   252  	if err != nil {
   253  		return nil, errors.Annotatef(err, "cannot get disk %q at zone %q in project %q", id, zone, project)
   254  	}
   255  	return disk, nil
   256  }
   257  
   258  func (rc *rawConn) SetDiskLabels(project, zone, id, labelFingerprint string, labels map[string]string) error {
   259  	ds := rc.Service.Disks
   260  	call := ds.SetLabels(project, zone, id, &compute.ZoneSetLabelsRequest{
   261  		LabelFingerprint: labelFingerprint,
   262  		Labels:           labels,
   263  	})
   264  	_, err := call.Do()
   265  	return errors.Trace(err)
   266  }
   267  
   268  func (rc *rawConn) AttachDisk(project, zone, instanceId string, disk *compute.AttachedDisk) error {
   269  	call := rc.Instances.AttachDisk(project, zone, instanceId, disk)
   270  	_, err := call.Do() // Perhaps return something from the Op
   271  	if err != nil {
   272  		return errors.Annotatef(err, "cannot attach volume into %q", instanceId)
   273  	}
   274  	return nil
   275  }
   276  
   277  func (rc *rawConn) DetachDisk(project, zone, instanceId, diskDeviceName string) error {
   278  	call := rc.Instances.DetachDisk(project, zone, instanceId, diskDeviceName)
   279  	_, err := call.Do()
   280  	if err != nil {
   281  		return errors.Annotatef(err, "cannot detach volume from %q", instanceId)
   282  	}
   283  	return nil
   284  }
   285  
   286  func (rc *rawConn) InstanceDisks(project, zone, instanceId string) ([]*compute.AttachedDisk, error) {
   287  	instance, err := rc.GetInstance(project, zone, instanceId)
   288  	if err != nil {
   289  		return nil, errors.Annotatef(err, "cannot get instance %q to list its disks", instanceId)
   290  	}
   291  	return instance.Disks, nil
   292  }
   293  
   294  type waitError struct {
   295  	op    *compute.Operation
   296  	cause error
   297  }
   298  
   299  func (err waitError) Error() string {
   300  	if err.cause != nil {
   301  		return fmt.Sprintf("GCE operation %q failed: %v", err.op.Name, err.cause)
   302  	}
   303  	return fmt.Sprintf("GCE operation %q failed", err.op.Name)
   304  }
   305  
   306  func isWaitError(err error) bool {
   307  	_, ok := err.(*waitError)
   308  	return ok
   309  }
   310  
   311  type opDoer interface {
   312  	Do(...googleapi.CallOption) (*compute.Operation, error)
   313  }
   314  
   315  // checkOperation requests a new copy of the given operation from the
   316  // GCE API and returns it. The new copy will have the operation's
   317  // current status.
   318  func (rc *rawConn) checkOperation(projectID string, op *compute.Operation) (*compute.Operation, error) {
   319  	var call opDoer
   320  	if op.Zone != "" {
   321  		zoneName := path.Base(op.Zone)
   322  		call = rc.ZoneOperations.Get(projectID, zoneName, op.Name)
   323  	} else if op.Region != "" {
   324  		region := path.Base(op.Region)
   325  		call = rc.RegionOperations.Get(projectID, region, op.Name)
   326  	} else {
   327  		call = rc.GlobalOperations.Get(projectID, op.Name)
   328  	}
   329  
   330  	operation, err := doOpCall(call)
   331  	if err != nil {
   332  		return nil, errors.Annotatef(err, "request for GCE operation %q failed", op.Name)
   333  	}
   334  	return operation, nil
   335  }
   336  
   337  var doOpCall = func(call opDoer) (*compute.Operation, error) {
   338  	return call.Do()
   339  }
   340  
   341  // waitOperation waits for the provided operation to reach the "done"
   342  // status. It follows the given attempt strategy (e.g. wait time between
   343  // attempts) and may time out.
   344  //
   345  // TODO(katco): 2016-08-09: lp:1611427
   346  func (rc *rawConn) waitOperation(projectID string, op *compute.Operation, attempts utils.AttemptStrategy) error {
   347  	// TODO(perrito666) 2016-05-02 lp:1558657
   348  	started := time.Now()
   349  	logger.Infof("GCE operation %q, waiting...", op.Name)
   350  	for a := attempts.Start(); a.Next(); {
   351  		if op.Status == StatusDone {
   352  			break
   353  		}
   354  
   355  		var err error
   356  		op, err = rc.checkOperation(projectID, op)
   357  		if err != nil {
   358  			return errors.Trace(err)
   359  		}
   360  	}
   361  	if op.Status != StatusDone {
   362  		// lp:1558657
   363  		err := errors.Errorf("timed out after %d seconds", time.Now().Sub(started)/time.Second)
   364  		return waitError{op, err}
   365  	}
   366  	if op.Error != nil {
   367  		for _, err := range op.Error.Errors {
   368  			logger.Errorf("GCE operation error: (%s) %s", err.Code, err.Message)
   369  		}
   370  		return waitError{op, nil}
   371  	}
   372  
   373  	logger.Infof("GCE operation %q finished", op.Name)
   374  	return nil
   375  }
   376  
   377  // ListMachineTypes returns a list of machines available in the project and zone provided.
   378  func (rc *rawConn) ListMachineTypes(projectID, zone string) (*compute.MachineTypeList, error) {
   379  	op := rc.MachineTypes.List(projectID, zone)
   380  	machines, err := op.Do()
   381  	if err != nil {
   382  		return nil, errors.Annotatef(err, "listing machine types for project %q and zone %q", projectID, zone)
   383  	}
   384  	return machines, nil
   385  }
   386  
   387  func (rc *rawConn) SetMetadata(projectID, zone, instanceID string, metadata *compute.Metadata) error {
   388  	call := rc.Instances.SetMetadata(projectID, zone, instanceID, metadata)
   389  	op, err := call.Do()
   390  	if err != nil {
   391  		return errors.Trace(err)
   392  	}
   393  	err = rc.waitOperation(projectID, op, attemptsLong)
   394  	return errors.Trace(err)
   395  }
   396  
   397  func (rc *rawConn) ListSubnetworks(projectID, region string) ([]*compute.Subnetwork, error) {
   398  	ctx := context.Background()
   399  	call := rc.Subnetworks.List(projectID, region)
   400  	var results []*compute.Subnetwork
   401  	err := call.Pages(ctx, func(page *compute.SubnetworkList) error {
   402  		results = append(results, page.Items...)
   403  		return nil
   404  	})
   405  	if err != nil {
   406  		return nil, errors.Trace(err)
   407  	}
   408  	return results, nil
   409  }
   410  
   411  func (rc *rawConn) ListNetworks(projectID string) ([]*compute.Network, error) {
   412  	ctx := context.Background()
   413  	call := rc.Networks.List(projectID)
   414  	var results []*compute.Network
   415  	err := call.Pages(ctx, func(page *compute.NetworkList) error {
   416  		results = append(results, page.Items...)
   417  		return nil
   418  	})
   419  	if err != nil {
   420  		return nil, errors.Trace(err)
   421  	}
   422  	return results, nil
   423  }