github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/errors"
    12  	"github.com/juju/utils"
    13  	"github.com/juju/utils/set"
    14  
    15  	"github.com/juju/juju/provider/gce/google"
    16  	"github.com/juju/juju/storage"
    17  )
    18  
    19  const (
    20  	storageProviderType = storage.ProviderType("gce")
    21  )
    22  
    23  // StorageProviderTypes implements storage.ProviderRegistry.
    24  func (env *environ) StorageProviderTypes() ([]storage.ProviderType, error) {
    25  	return []storage.ProviderType{storageProviderType}, nil
    26  }
    27  
    28  // StorageProvider implements storage.ProviderRegistry.
    29  func (env *environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
    30  	if t == storageProviderType {
    31  		return &storageProvider{env}, nil
    32  	}
    33  	return nil, errors.NotFoundf("storage provider %q", t)
    34  }
    35  
    36  type storageProvider struct {
    37  	env *environ
    38  }
    39  
    40  var _ storage.Provider = (*storageProvider)(nil)
    41  
    42  func (g *storageProvider) ValidateConfig(cfg *storage.Config) error {
    43  	return nil
    44  }
    45  
    46  func (g *storageProvider) Supports(k storage.StorageKind) bool {
    47  	return k == storage.StorageKindBlock
    48  }
    49  
    50  func (g *storageProvider) Scope() storage.Scope {
    51  	return storage.ScopeEnviron
    52  }
    53  
    54  func (g *storageProvider) Dynamic() bool {
    55  	return true
    56  }
    57  
    58  func (g *storageProvider) DefaultPools() []*storage.Config {
    59  	// TODO(perrito666) Add explicit pools.
    60  	return nil
    61  }
    62  
    63  func (g *storageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
    64  	return nil, errors.NotSupportedf("filesystems")
    65  }
    66  
    67  type volumeSource struct {
    68  	gce       gceConnection
    69  	envName   string // non-unique, informational only
    70  	modelUUID string
    71  }
    72  
    73  func (g *storageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) {
    74  	environConfig := g.env.Config()
    75  	source := &volumeSource{
    76  		gce:       g.env.gce,
    77  		envName:   environConfig.Name(),
    78  		modelUUID: environConfig.UUID(),
    79  	}
    80  	return source, nil
    81  }
    82  
    83  type instanceCache map[string]google.Instance
    84  
    85  func (c instanceCache) update(gceClient gceConnection, ids ...string) error {
    86  	if len(ids) == 1 {
    87  		if _, ok := c[ids[0]]; ok {
    88  			return nil
    89  		}
    90  	}
    91  	idMap := make(map[string]int, len(ids))
    92  	for _, id := range ids {
    93  		idMap[id] = 0
    94  	}
    95  	instances, err := gceClient.Instances("", google.StatusRunning)
    96  	if err != nil {
    97  		return errors.Annotate(err, "querying instance details")
    98  	}
    99  	for _, instance := range instances {
   100  		if _, ok := idMap[instance.ID]; !ok {
   101  			continue
   102  		}
   103  		c[instance.ID] = instance
   104  	}
   105  	return nil
   106  }
   107  
   108  func (c instanceCache) get(id string) (google.Instance, error) {
   109  	inst, ok := c[id]
   110  	if !ok {
   111  		return google.Instance{}, errors.Errorf("cannot attach to non-running instance %v", id)
   112  	}
   113  	return inst, nil
   114  }
   115  
   116  func (v *volumeSource) CreateVolumes(params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) {
   117  	results := make([]storage.CreateVolumesResult, len(params))
   118  	instanceIds := set.NewStrings()
   119  	for i, p := range params {
   120  		if err := v.ValidateVolumeParams(p); err != nil {
   121  			results[i].Error = err
   122  			continue
   123  		}
   124  		instanceIds.Add(string(p.Attachment.InstanceId))
   125  	}
   126  
   127  	instances := make(instanceCache)
   128  	if instanceIds.Size() > 1 {
   129  		if err := instances.update(v.gce, instanceIds.Values()...); err != nil {
   130  			logger.Debugf("querying running instances: %v", err)
   131  			// We ignore the error, because we don't want an invalid
   132  			// InstanceId reference from one VolumeParams to prevent
   133  			// the creation of another volume.
   134  		}
   135  	}
   136  
   137  	for i, p := range params {
   138  		if results[i].Error != nil {
   139  			continue
   140  		}
   141  		volume, attachment, err := v.createOneVolume(p, instances)
   142  		if err != nil {
   143  			results[i].Error = err
   144  			logger.Errorf("could not create one volume (or attach it): %v", err)
   145  			continue
   146  		}
   147  		results[i].Volume = volume
   148  		results[i].VolumeAttachment = attachment
   149  	}
   150  	return results, nil
   151  }
   152  
   153  // mibToGib converts mebibytes to gibibytes.
   154  // GCE expects GiB, we work in MiB; round up
   155  // to nearest GiB.
   156  func mibToGib(m uint64) uint64 {
   157  	return (m + 1023) / 1024
   158  }
   159  
   160  func nameVolume(zone string) (string, error) {
   161  	volumeUUID, err := utils.NewUUID()
   162  	if err != nil {
   163  		return "", errors.Annotate(err, "cannot generate uuid to name the volume")
   164  	}
   165  	// type-zone-uuid
   166  	volumeName := fmt.Sprintf("%s--%s", zone, volumeUUID.String())
   167  	return volumeName, nil
   168  }
   169  
   170  func (v *volumeSource) createOneVolume(p storage.VolumeParams, instances instanceCache) (volume *storage.Volume, volumeAttachment *storage.VolumeAttachment, err error) {
   171  	var volumeName, zone string
   172  	defer func() {
   173  		if err == nil || volumeName == "" {
   174  			return
   175  		}
   176  		if err := v.gce.RemoveDisk(zone, volumeName); err != nil {
   177  			logger.Errorf("error cleaning up volume %v: %v", volumeName, err)
   178  		}
   179  	}()
   180  
   181  	instId := string(p.Attachment.InstanceId)
   182  	if err := instances.update(v.gce, instId); err != nil {
   183  		return nil, nil, errors.Annotatef(err, "cannot add %q to instance cache", instId)
   184  	}
   185  	inst, err := instances.get(instId)
   186  	if err != nil {
   187  		// Can't create the volume without the instance,
   188  		// because we need to know what its AZ is.
   189  		return nil, nil, errors.Annotatef(err, "cannot obtain %q from instance cache", instId)
   190  	}
   191  	persistentType, ok := p.Attributes["type"].(google.DiskType)
   192  	if !ok {
   193  		persistentType = google.DiskPersistentStandard
   194  	}
   195  
   196  	zone = inst.ZoneName
   197  	volumeName, err = nameVolume(zone)
   198  	if err != nil {
   199  		return nil, nil, errors.Annotate(err, "cannot create a new volume name")
   200  	}
   201  	// TODO(perrito666) the volumeName is arbitrary and it was crafted this
   202  	// way to help solve the need to have zone all over the place.
   203  	disk := google.DiskSpec{
   204  		SizeHintGB:         mibToGib(p.Size),
   205  		Name:               volumeName,
   206  		PersistentDiskType: persistentType,
   207  		Description:        v.modelUUID,
   208  	}
   209  
   210  	gceDisks, err := v.gce.CreateDisks(zone, []google.DiskSpec{disk})
   211  	if err != nil {
   212  		return nil, nil, errors.Annotate(err, "cannot create disk")
   213  	}
   214  	if len(gceDisks) != 1 {
   215  		return nil, nil, errors.New(fmt.Sprintf("unexpected number of disks created: %d", len(gceDisks)))
   216  	}
   217  	gceDisk := gceDisks[0]
   218  	// TODO(perrito666) Tag, there are no tags in gce, how do we fix it?
   219  
   220  	attachedDisk, err := v.attachOneVolume(gceDisk.Name, google.ModeRW, inst.ID)
   221  	if err != nil {
   222  		return nil, nil, errors.Annotatef(err, "attaching %q to %q", gceDisk.Name, instId)
   223  	}
   224  
   225  	volume = &storage.Volume{
   226  		p.Tag,
   227  		storage.VolumeInfo{
   228  			VolumeId:   gceDisk.Name,
   229  			Size:       gceDisk.Size,
   230  			Persistent: true,
   231  		},
   232  	}
   233  
   234  	volumeAttachment = &storage.VolumeAttachment{
   235  		p.Tag,
   236  		p.Attachment.Machine,
   237  		storage.VolumeAttachmentInfo{
   238  			DeviceLink: fmt.Sprintf(
   239  				"/dev/disk/by-id/google-%s",
   240  				attachedDisk.DeviceName,
   241  			),
   242  		},
   243  	}
   244  
   245  	return volume, volumeAttachment, nil
   246  }
   247  
   248  func (v *volumeSource) DestroyVolumes(volNames []string) ([]error, error) {
   249  	var wg sync.WaitGroup
   250  	wg.Add(len(volNames))
   251  	results := make([]error, len(volNames))
   252  	for i, volumeName := range volNames {
   253  		go func(i int, volumeName string) {
   254  			defer wg.Done()
   255  			results[i] = v.destroyOneVolume(volumeName)
   256  		}(i, volumeName)
   257  	}
   258  	wg.Wait()
   259  	return results, nil
   260  }
   261  
   262  func parseVolumeId(volName string) (string, string, error) {
   263  	idRest := strings.SplitN(volName, "--", 2)
   264  	if len(idRest) != 2 {
   265  		return "", "", errors.New(fmt.Sprintf("malformed volume id %q", volName))
   266  	}
   267  	zone := idRest[0]
   268  	volumeUUID := idRest[1]
   269  	return zone, volumeUUID, nil
   270  
   271  }
   272  
   273  func isValidVolume(volumeName string) bool {
   274  	_, _, err := parseVolumeId(volumeName)
   275  	return err == nil
   276  }
   277  
   278  func (v *volumeSource) destroyOneVolume(volName string) error {
   279  	zone, _, err := parseVolumeId(volName)
   280  	if err != nil {
   281  		return errors.Annotatef(err, "invalid volume id %q", volName)
   282  	}
   283  	if err := v.gce.RemoveDisk(zone, volName); err != nil {
   284  		return errors.Annotatef(err, "cannot destroy volume %q", volName)
   285  	}
   286  	return nil
   287  }
   288  
   289  func (v *volumeSource) ListVolumes() ([]string, error) {
   290  	azs, err := v.gce.AvailabilityZones("")
   291  	if err != nil {
   292  		return nil, errors.Annotate(err, "cannot determine availability zones")
   293  	}
   294  	var volumes []string
   295  	for _, zone := range azs {
   296  		disks, err := v.gce.Disks(zone.Name())
   297  		if err != nil {
   298  			// maybe use available and status also.
   299  			logger.Errorf("cannot get disks for %q zone", zone.Name())
   300  			continue
   301  		}
   302  		for _, disk := range disks {
   303  			// Blank disk description means an older disk or a disk
   304  			// not created by storage, we should not touch it.
   305  			if disk.Description != v.modelUUID && disk.Description != "" {
   306  				continue
   307  			}
   308  			// We don't want to lay hands on disks we did not create.
   309  			if isValidVolume(disk.Name) {
   310  				volumes = append(volumes, disk.Name)
   311  			}
   312  		}
   313  	}
   314  	return volumes, nil
   315  }
   316  func (v *volumeSource) DescribeVolumes(volNames []string) ([]storage.DescribeVolumesResult, error) {
   317  	results := make([]storage.DescribeVolumesResult, len(volNames))
   318  	for i, vol := range volNames {
   319  		res, err := v.describeOneVolume(vol)
   320  		if err != nil {
   321  			return nil, errors.Annotate(err, "cannot describe volumes")
   322  		}
   323  		results[i] = res
   324  	}
   325  	return results, nil
   326  }
   327  
   328  func (v *volumeSource) describeOneVolume(volName string) (storage.DescribeVolumesResult, error) {
   329  	zone, _, err := parseVolumeId(volName)
   330  	if err != nil {
   331  		return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot describe %q", volName)
   332  	}
   333  	disk, err := v.gce.Disk(zone, volName)
   334  	if err != nil {
   335  		return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot get volume %q", volName)
   336  	}
   337  	desc := storage.DescribeVolumesResult{
   338  		&storage.VolumeInfo{
   339  			Size:     disk.Size,
   340  			VolumeId: disk.Name,
   341  		},
   342  		nil,
   343  	}
   344  	return desc, nil
   345  }
   346  
   347  // TODO(perrito666) These rules are yet to be defined.
   348  func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   349  	return nil
   350  }
   351  
   352  func (v *volumeSource) AttachVolumes(attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   353  	results := make([]storage.AttachVolumesResult, len(attachParams))
   354  	for i, attachment := range attachParams {
   355  		volumeName := attachment.VolumeId
   356  		mode := google.ModeRW
   357  		if attachment.ReadOnly {
   358  			mode = google.ModeRW
   359  		}
   360  		instanceId := attachment.InstanceId
   361  		attached, err := v.attachOneVolume(volumeName, mode, string(instanceId))
   362  		if err != nil {
   363  			logger.Errorf("could not attach %q to %q: %v", volumeName, instanceId, err)
   364  			results[i].Error = err
   365  			continue
   366  		}
   367  		results[i].VolumeAttachment = &storage.VolumeAttachment{
   368  			attachment.Volume,
   369  			attachment.Machine,
   370  			storage.VolumeAttachmentInfo{
   371  				DeviceName: attached.DeviceName,
   372  			},
   373  		}
   374  	}
   375  	return results, nil
   376  }
   377  
   378  func (v *volumeSource) attachOneVolume(volumeName string, mode google.DiskMode, instanceId string) (*google.AttachedDisk, error) {
   379  	zone, _, err := parseVolumeId(volumeName)
   380  	if err != nil {
   381  		return nil, errors.Annotate(err, "invalid volume name")
   382  	}
   383  	instanceDisks, err := v.gce.InstanceDisks(zone, instanceId)
   384  	if err != nil {
   385  		return nil, errors.Annotate(err, "cannot verify if the disk is already in the instance")
   386  	}
   387  	// Is it already attached?
   388  	for _, disk := range instanceDisks {
   389  		if disk.VolumeName == volumeName {
   390  			return disk, nil
   391  		}
   392  	}
   393  
   394  	attachment, err := v.gce.AttachDisk(zone, volumeName, instanceId, mode)
   395  	if err != nil {
   396  		return nil, errors.Annotate(err, "cannot attach volume")
   397  	}
   398  	return attachment, nil
   399  }
   400  
   401  func (v *volumeSource) DetachVolumes(attachParams []storage.VolumeAttachmentParams) ([]error, error) {
   402  	result := make([]error, len(attachParams))
   403  	for i, volumeAttachment := range attachParams {
   404  		result[i] = v.detachOneVolume(volumeAttachment)
   405  	}
   406  	return result, nil
   407  }
   408  
   409  func (v *volumeSource) detachOneVolume(attachParam storage.VolumeAttachmentParams) error {
   410  	instId := attachParam.InstanceId
   411  	volumeName := attachParam.VolumeId
   412  	zone, _, err := parseVolumeId(volumeName)
   413  	if err != nil {
   414  		return errors.Annotatef(err, "%q is not a valid volume id", volumeName)
   415  	}
   416  	return v.gce.DetachDisk(zone, string(instId), volumeName)
   417  }