github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/disks.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gce
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/juju/collections/set"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils"
    14  
    15  	"github.com/juju/juju/environs/context"
    16  	"github.com/juju/juju/environs/tags"
    17  	"github.com/juju/juju/provider/gce/google"
    18  	"github.com/juju/juju/storage"
    19  )
    20  
    21  const (
    22  	storageProviderType = storage.ProviderType("gce")
    23  )
    24  
    25  // StorageProviderTypes implements storage.ProviderRegistry.
    26  func (env *environ) StorageProviderTypes() ([]storage.ProviderType, error) {
    27  	return []storage.ProviderType{storageProviderType}, nil
    28  }
    29  
    30  // StorageProvider implements storage.ProviderRegistry.
    31  func (env *environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
    32  	if t == storageProviderType {
    33  		return &storageProvider{env}, nil
    34  	}
    35  	return nil, errors.NotFoundf("storage provider %q", t)
    36  }
    37  
    38  type storageProvider struct {
    39  	env *environ
    40  }
    41  
    42  var _ storage.Provider = (*storageProvider)(nil)
    43  
    44  func (g *storageProvider) ValidateConfig(cfg *storage.Config) error {
    45  	return nil
    46  }
    47  
    48  func (g *storageProvider) Supports(k storage.StorageKind) bool {
    49  	return k == storage.StorageKindBlock
    50  }
    51  
    52  func (g *storageProvider) Scope() storage.Scope {
    53  	return storage.ScopeEnviron
    54  }
    55  
    56  func (g *storageProvider) Dynamic() bool {
    57  	return true
    58  }
    59  
    60  func (e *storageProvider) Releasable() bool {
    61  	return true
    62  }
    63  
    64  func (g *storageProvider) DefaultPools() []*storage.Config {
    65  	// TODO(perrito666) Add explicit pools.
    66  	return nil
    67  }
    68  
    69  func (g *storageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
    70  	return nil, errors.NotSupportedf("filesystems")
    71  }
    72  
    73  type volumeSource struct {
    74  	gce       gceConnection
    75  	envName   string // non-unique, informational only
    76  	modelUUID string
    77  }
    78  
    79  func (g *storageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) {
    80  	environConfig := g.env.Config()
    81  	source := &volumeSource{
    82  		gce:       g.env.gce,
    83  		envName:   environConfig.Name(),
    84  		modelUUID: environConfig.UUID(),
    85  	}
    86  	return source, nil
    87  }
    88  
    89  type instanceCache map[string]google.Instance
    90  
    91  func (c instanceCache) update(gceClient gceConnection, ctx context.ProviderCallContext, ids ...string) error {
    92  	if len(ids) == 1 {
    93  		if _, ok := c[ids[0]]; ok {
    94  			return nil
    95  		}
    96  	}
    97  	idMap := make(map[string]int, len(ids))
    98  	for _, id := range ids {
    99  		idMap[id] = 0
   100  	}
   101  	instances, err := gceClient.Instances("", google.StatusRunning)
   102  	if err != nil {
   103  		return google.HandleCredentialError(errors.Annotate(err, "querying instance details"), ctx)
   104  	}
   105  	for _, instance := range instances {
   106  		if _, ok := idMap[instance.ID]; !ok {
   107  			continue
   108  		}
   109  		c[instance.ID] = instance
   110  	}
   111  	return nil
   112  }
   113  
   114  func (c instanceCache) get(id string) (google.Instance, error) {
   115  	inst, ok := c[id]
   116  	if !ok {
   117  		return google.Instance{}, errors.Errorf("cannot attach to non-running instance %v", id)
   118  	}
   119  	return inst, nil
   120  }
   121  
   122  func (v *volumeSource) CreateVolumes(ctx context.ProviderCallContext, params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) {
   123  	results := make([]storage.CreateVolumesResult, len(params))
   124  	instanceIds := set.NewStrings()
   125  	for i, p := range params {
   126  		if err := v.ValidateVolumeParams(p); err != nil {
   127  			results[i].Error = err
   128  			continue
   129  		}
   130  		instanceIds.Add(string(p.Attachment.InstanceId))
   131  	}
   132  
   133  	instances := make(instanceCache)
   134  	if instanceIds.Size() > 1 {
   135  		if err := instances.update(v.gce, ctx, instanceIds.Values()...); err != nil {
   136  			logger.Debugf("querying running instances: %v", err)
   137  			// We ignore the error, because we don't want an invalid
   138  			// InstanceId reference from one VolumeParams to prevent
   139  			// the creation of another volume.
   140  			// ... Unless the error is due to an invalid credential, in which case, continuing with this call
   141  			// is pointless and creates an unnecessary churn: we know all calls will fail with the same error.
   142  			if google.HasDenialStatusCode(err) {
   143  				return results, err
   144  			}
   145  		}
   146  	}
   147  
   148  	for i, p := range params {
   149  		if results[i].Error != nil {
   150  			continue
   151  		}
   152  		volume, attachment, err := v.createOneVolume(ctx, p, instances)
   153  		if err != nil {
   154  			results[i].Error = err
   155  			logger.Errorf("could not create one volume (or attach it): %v", err)
   156  			// ... Unless the error is due to an invalid credential, in which case, continuing with this call
   157  			// is pointless and creates an unnecessary churn: we know all calls will fail with the same error.
   158  			if google.HasDenialStatusCode(err) {
   159  				return results, err
   160  			}
   161  			continue
   162  		}
   163  		results[i].Volume = volume
   164  		results[i].VolumeAttachment = attachment
   165  	}
   166  	return results, nil
   167  }
   168  
   169  // mibToGib converts mebibytes to gibibytes.
   170  // GCE expects GiB, we work in MiB; round up
   171  // to nearest GiB.
   172  func mibToGib(m uint64) uint64 {
   173  	return (m + 1023) / 1024
   174  }
   175  
   176  func nameVolume(zone string) (string, error) {
   177  	volumeUUID, err := utils.NewUUID()
   178  	if err != nil {
   179  		return "", errors.Annotate(err, "cannot generate uuid to name the volume")
   180  	}
   181  	// type-zone-uuid
   182  	volumeName := fmt.Sprintf("%s--%s", zone, volumeUUID.String())
   183  	return volumeName, nil
   184  }
   185  
   186  func (v *volumeSource) createOneVolume(ctx context.ProviderCallContext, p storage.VolumeParams, instances instanceCache) (volume *storage.Volume, volumeAttachment *storage.VolumeAttachment, err error) {
   187  	var volumeName, zone string
   188  	defer func() {
   189  		if err == nil || volumeName == "" {
   190  			return
   191  		}
   192  		if err := v.gce.RemoveDisk(zone, volumeName); err != nil {
   193  			logger.Errorf("error cleaning up volume %v: %v", volumeName, google.HandleCredentialError(err, ctx))
   194  		}
   195  	}()
   196  
   197  	instId := string(p.Attachment.InstanceId)
   198  	if err := instances.update(v.gce, ctx, instId); err != nil {
   199  		return nil, nil, errors.Annotatef(err, "cannot add %q to instance cache", instId)
   200  	}
   201  	inst, err := instances.get(instId)
   202  	if err != nil {
   203  		// Can't create the volume without the instance,
   204  		// because we need to know what its AZ is.
   205  		return nil, nil, errors.Annotatef(err, "cannot obtain %q from instance cache", instId)
   206  	}
   207  	persistentType, ok := p.Attributes["type"].(google.DiskType)
   208  	if !ok {
   209  		persistentType = google.DiskPersistentStandard
   210  	}
   211  
   212  	zone = inst.ZoneName
   213  	volumeName, err = nameVolume(zone)
   214  	if err != nil {
   215  		return nil, nil, errors.Annotate(err, "cannot create a new volume name")
   216  	}
   217  	// TODO(perrito666) the volumeName is arbitrary and it was crafted this
   218  	// way to help solve the need to have zone all over the place.
   219  	disk := google.DiskSpec{
   220  		SizeHintGB:         mibToGib(p.Size),
   221  		Name:               volumeName,
   222  		PersistentDiskType: persistentType,
   223  		Labels:             resourceTagsToDiskLabels(p.ResourceTags),
   224  	}
   225  
   226  	gceDisks, err := v.gce.CreateDisks(zone, []google.DiskSpec{disk})
   227  	if err != nil {
   228  		return nil, nil, google.HandleCredentialError(errors.Annotate(err, "cannot create disk"), ctx)
   229  	}
   230  	if len(gceDisks) != 1 {
   231  		return nil, nil, errors.New(fmt.Sprintf("unexpected number of disks created: %d", len(gceDisks)))
   232  	}
   233  	gceDisk := gceDisks[0]
   234  
   235  	attachedDisk, err := v.attachOneVolume(ctx, gceDisk.Name, google.ModeRW, inst.ID)
   236  	if err != nil {
   237  		return nil, nil, errors.Annotatef(err, "attaching %q to %q", gceDisk.Name, instId)
   238  	}
   239  
   240  	volume = &storage.Volume{
   241  		p.Tag,
   242  		storage.VolumeInfo{
   243  			VolumeId:   gceDisk.Name,
   244  			Size:       gceDisk.Size,
   245  			Persistent: true,
   246  		},
   247  	}
   248  
   249  	volumeAttachment = &storage.VolumeAttachment{
   250  		p.Tag,
   251  		p.Attachment.Machine,
   252  		storage.VolumeAttachmentInfo{
   253  			DeviceLink: fmt.Sprintf(
   254  				"/dev/disk/by-id/google-%s",
   255  				attachedDisk.DeviceName,
   256  			),
   257  		},
   258  	}
   259  
   260  	return volume, volumeAttachment, nil
   261  }
   262  
   263  func (v *volumeSource) DestroyVolumes(ctx context.ProviderCallContext, volNames []string) ([]error, error) {
   264  	return v.foreachVolume(ctx, volNames, v.destroyOneVolume), nil
   265  }
   266  
   267  func (v *volumeSource) ReleaseVolumes(ctx context.ProviderCallContext, volNames []string) ([]error, error) {
   268  	return v.foreachVolume(ctx, volNames, v.releaseOneVolume), nil
   269  }
   270  
   271  func (v *volumeSource) foreachVolume(ctx context.ProviderCallContext, volNames []string, f func(context.ProviderCallContext, string) error) []error {
   272  	var wg sync.WaitGroup
   273  	wg.Add(len(volNames))
   274  	results := make([]error, len(volNames))
   275  	for i, volumeName := range volNames {
   276  		go func(i int, volumeName string) {
   277  			defer wg.Done()
   278  			results[i] = f(ctx, volumeName)
   279  		}(i, volumeName)
   280  	}
   281  	wg.Wait()
   282  	return results
   283  }
   284  
   285  func parseVolumeId(volName string) (string, string, error) {
   286  	idRest := strings.SplitN(volName, "--", 2)
   287  	if len(idRest) != 2 {
   288  		return "", "", errors.New(fmt.Sprintf("malformed volume id %q", volName))
   289  	}
   290  	zone := idRest[0]
   291  	volumeUUID := idRest[1]
   292  	return zone, volumeUUID, nil
   293  }
   294  
   295  func isValidVolume(volumeName string) bool {
   296  	_, _, err := parseVolumeId(volumeName)
   297  	return err == nil
   298  }
   299  
   300  func (v *volumeSource) destroyOneVolume(ctx context.ProviderCallContext, volName string) error {
   301  	zone, _, err := parseVolumeId(volName)
   302  	if err != nil {
   303  		return errors.Annotatef(err, "invalid volume id %q", volName)
   304  	}
   305  	if err := v.gce.RemoveDisk(zone, volName); err != nil {
   306  		return google.HandleCredentialError(errors.Annotatef(err, "cannot destroy volume %q", volName), ctx)
   307  	}
   308  	return nil
   309  }
   310  
   311  func (v *volumeSource) releaseOneVolume(ctx context.ProviderCallContext, volName string) error {
   312  	zone, _, err := parseVolumeId(volName)
   313  	if err != nil {
   314  		return errors.Annotatef(err, "invalid volume id %q", volName)
   315  	}
   316  	disk, err := v.gce.Disk(zone, volName)
   317  	if err != nil {
   318  		return google.HandleCredentialError(errors.Trace(err), ctx)
   319  	}
   320  	switch disk.Status {
   321  	case google.StatusReady, google.StatusFailed:
   322  	default:
   323  		return errors.Errorf(
   324  			"cannot release volume %q with status %q",
   325  			volName, disk.Status,
   326  		)
   327  	}
   328  	if len(disk.AttachedInstances) > 0 {
   329  		return errors.Errorf(
   330  			"cannot release volume %q, attached to instances %q",
   331  			volName, disk.AttachedInstances,
   332  		)
   333  	}
   334  	delete(disk.Labels, tags.JujuController)
   335  	delete(disk.Labels, tags.JujuModel)
   336  	if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil {
   337  		return google.HandleCredentialError(errors.Annotatef(err, "cannot remove labels from volume %q", volName), ctx)
   338  	}
   339  	return nil
   340  }
   341  
   342  func (v *volumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) {
   343  	var volumes []string
   344  	disks, err := v.gce.Disks()
   345  	if err != nil {
   346  		return nil, google.HandleCredentialError(errors.Trace(err), ctx)
   347  	}
   348  	for _, disk := range disks {
   349  		if !isValidVolume(disk.Name) {
   350  			continue
   351  		}
   352  		if disk.Labels[tags.JujuModel] != v.modelUUID {
   353  			continue
   354  		}
   355  		volumes = append(volumes, disk.Name)
   356  	}
   357  	return volumes, nil
   358  }
   359  
   360  // ImportVolume is specified on the storage.VolumeImporter interface.
   361  func (v *volumeSource) ImportVolume(ctx context.ProviderCallContext, volName string, tags map[string]string) (storage.VolumeInfo, error) {
   362  	zone, _, err := parseVolumeId(volName)
   363  	if err != nil {
   364  		return storage.VolumeInfo{}, errors.Annotatef(err, "cannot get volume %q", volName)
   365  	}
   366  	disk, err := v.gce.Disk(zone, volName)
   367  	if err != nil {
   368  		return storage.VolumeInfo{}, google.HandleCredentialError(errors.Annotatef(err, "cannot get volume %q", volName), ctx)
   369  	}
   370  	if disk.Status != google.StatusReady {
   371  		return storage.VolumeInfo{}, errors.Errorf(
   372  			"cannot import volume %q with status %q",
   373  			volName, disk.Status,
   374  		)
   375  	}
   376  	if disk.Labels == nil {
   377  		disk.Labels = make(map[string]string)
   378  	}
   379  	for k, v := range resourceTagsToDiskLabels(tags) {
   380  		disk.Labels[k] = v
   381  	}
   382  	if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil {
   383  		return storage.VolumeInfo{}, google.HandleCredentialError(errors.Annotatef(err, "cannot update labels on volume %q", volName), ctx)
   384  	}
   385  	return storage.VolumeInfo{
   386  		VolumeId:   disk.Name,
   387  		Size:       disk.Size,
   388  		Persistent: true,
   389  	}, nil
   390  }
   391  
   392  func (v *volumeSource) DescribeVolumes(ctx context.ProviderCallContext, volNames []string) ([]storage.DescribeVolumesResult, error) {
   393  	results := make([]storage.DescribeVolumesResult, len(volNames))
   394  	for i, vol := range volNames {
   395  		res, err := v.describeOneVolume(ctx, vol)
   396  		if err != nil {
   397  			return nil, errors.Annotate(err, "cannot describe volumes")
   398  		}
   399  		results[i] = res
   400  	}
   401  	return results, nil
   402  }
   403  
   404  func (v *volumeSource) describeOneVolume(ctx context.ProviderCallContext, volName string) (storage.DescribeVolumesResult, error) {
   405  	zone, _, err := parseVolumeId(volName)
   406  	if err != nil {
   407  		return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot describe %q", volName)
   408  	}
   409  	disk, err := v.gce.Disk(zone, volName)
   410  	if err != nil {
   411  		return storage.DescribeVolumesResult{}, google.HandleCredentialError(errors.Annotatef(err, "cannot get volume %q", volName), ctx)
   412  	}
   413  	desc := storage.DescribeVolumesResult{
   414  		&storage.VolumeInfo{
   415  			Size:     disk.Size,
   416  			VolumeId: disk.Name,
   417  		},
   418  		nil,
   419  	}
   420  	return desc, nil
   421  }
   422  
   423  // TODO(perrito666) These rules are yet to be defined.
   424  func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   425  	return nil
   426  }
   427  
   428  func (v *volumeSource) AttachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   429  	results := make([]storage.AttachVolumesResult, len(attachParams))
   430  	for i, attachment := range attachParams {
   431  		volumeName := attachment.VolumeId
   432  		mode := google.ModeRW
   433  		if attachment.ReadOnly {
   434  			mode = google.ModeRW
   435  		}
   436  		instanceId := attachment.InstanceId
   437  		attached, err := v.attachOneVolume(ctx, volumeName, mode, string(instanceId))
   438  		if err != nil {
   439  			logger.Errorf("could not attach %q to %q: %v", volumeName, instanceId, err)
   440  			results[i].Error = err
   441  			// ... Unless the error is due to an invalid credential, in which case, continuing with this call
   442  			// is pointless and creates an unnecessary churn: we know all calls will fail with the same error.
   443  			if google.HasDenialStatusCode(err) {
   444  				return results, err
   445  			}
   446  			continue
   447  		}
   448  		results[i].VolumeAttachment = &storage.VolumeAttachment{
   449  			attachment.Volume,
   450  			attachment.Machine,
   451  			storage.VolumeAttachmentInfo{
   452  				DeviceLink: fmt.Sprintf(
   453  					"/dev/disk/by-id/google-%s",
   454  					attached.DeviceName,
   455  				),
   456  			},
   457  		}
   458  	}
   459  	return results, nil
   460  }
   461  
   462  func (v *volumeSource) attachOneVolume(ctx context.ProviderCallContext, volumeName string, mode google.DiskMode, instanceId string) (*google.AttachedDisk, error) {
   463  	zone, _, err := parseVolumeId(volumeName)
   464  	if err != nil {
   465  		return nil, errors.Annotate(err, "invalid volume name")
   466  	}
   467  	instanceDisks, err := v.gce.InstanceDisks(zone, instanceId)
   468  	if err != nil {
   469  		return nil, google.HandleCredentialError(errors.Annotate(err, "cannot verify if the disk is already in the instance"), ctx)
   470  	}
   471  	// Is it already attached?
   472  	for _, disk := range instanceDisks {
   473  		if disk.VolumeName == volumeName {
   474  			return disk, nil
   475  		}
   476  	}
   477  
   478  	attachment, err := v.gce.AttachDisk(zone, volumeName, instanceId, mode)
   479  	if err != nil {
   480  		return nil, google.HandleCredentialError(errors.Annotate(err, "cannot attach volume"), ctx)
   481  	}
   482  	return attachment, nil
   483  }
   484  
   485  func (v *volumeSource) DetachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]error, error) {
   486  	result := make([]error, len(attachParams))
   487  	for i, volumeAttachment := range attachParams {
   488  		err := v.detachOneVolume(ctx, volumeAttachment)
   489  		if google.HasDenialStatusCode(err) {
   490  			// no need to continue as we'll keep getting the same invalid credential error.
   491  			return result, err
   492  		}
   493  		result[i] = err
   494  	}
   495  	return result, nil
   496  }
   497  
   498  func (v *volumeSource) detachOneVolume(ctx context.ProviderCallContext, attachParam storage.VolumeAttachmentParams) error {
   499  	instId := attachParam.InstanceId
   500  	volumeName := attachParam.VolumeId
   501  	zone, _, err := parseVolumeId(volumeName)
   502  	if err != nil {
   503  		return errors.Annotatef(err, "%q is not a valid volume id", volumeName)
   504  	}
   505  	return google.HandleCredentialError(v.gce.DetachDisk(zone, string(instId), volumeName), ctx)
   506  }
   507  
   508  // resourceTagsToDiskLabels translates a set of
   509  // resource tags, provided by Juju, to disk labels.
   510  func resourceTagsToDiskLabels(in map[string]string) map[string]string {
   511  	out := make(map[string]string)
   512  	for k, v := range in {
   513  		// Only the controller and model UUID tags are carried
   514  		// over, as they're known not to conflict with GCE's
   515  		// rules regarding label values.
   516  		switch k {
   517  		case tags.JujuController, tags.JujuModel:
   518  			out[k] = v
   519  		}
   520  	}
   521  	return out
   522  }