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