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