github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/provider/openstack/cinder.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"math"
     8  	"net/url"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils"
    13  	"gopkg.in/goose.v1/cinder"
    14  	"gopkg.in/goose.v1/nova"
    15  
    16  	"github.com/juju/juju/environs/config"
    17  	"github.com/juju/juju/storage"
    18  )
    19  
    20  const (
    21  	CinderProviderType = storage.ProviderType("cinder")
    22  	// autoAssignedMountPoint specifies the value to pass in when
    23  	// you'd like Cinder to automatically assign a mount point.
    24  	autoAssignedMountPoint = ""
    25  )
    26  
    27  type cinderProvider struct {
    28  	newStorageAdapter func(*config.Config) (openstackStorage, error)
    29  }
    30  
    31  var _ storage.Provider = (*cinderProvider)(nil)
    32  
    33  var cinderAttempt = utils.AttemptStrategy{
    34  	Total: 1 * time.Minute,
    35  	Delay: 5 * time.Second,
    36  }
    37  
    38  // VolumeSource implements storage.Provider.
    39  func (p *cinderProvider) VolumeSource(environConfig *config.Config, providerConfig *storage.Config) (storage.VolumeSource, error) {
    40  	if err := p.ValidateConfig(providerConfig); err != nil {
    41  		return nil, err
    42  	}
    43  	storageAdapter, err := p.newStorageAdapter(environConfig)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	source := &cinderVolumeSource{
    48  		storageAdapter: storageAdapter,
    49  		envName:        environConfig.Name(),
    50  	}
    51  	return source, nil
    52  }
    53  
    54  // FilesystemSource implements storage.Provider.
    55  func (p *cinderProvider) FilesystemSource(environConfig *config.Config, providerConfig *storage.Config) (storage.FilesystemSource, error) {
    56  	return nil, errors.NotSupportedf("filesystems")
    57  }
    58  
    59  // Supports implements storage.Provider.
    60  func (p *cinderProvider) Supports(kind storage.StorageKind) bool {
    61  	switch kind {
    62  	case storage.StorageKindBlock:
    63  		return true
    64  	}
    65  	return false
    66  }
    67  
    68  // Scope implements storage.Provider.
    69  func (s *cinderProvider) Scope() storage.Scope {
    70  	return storage.ScopeEnviron
    71  }
    72  
    73  // ValidateConfig implements storage.Provider.
    74  func (p *cinderProvider) ValidateConfig(cfg *storage.Config) error {
    75  	// TODO(axw) 2015-05-01 #1450737
    76  	// Reject attempts to create non-persistent volumes.
    77  	return nil
    78  }
    79  
    80  // Dynamic implements storage.Provider.
    81  func (p *cinderProvider) Dynamic() bool {
    82  	return true
    83  }
    84  
    85  type cinderVolumeSource struct {
    86  	storageAdapter openstackStorage
    87  	envName        string // non unique, informational only
    88  }
    89  
    90  var _ storage.VolumeSource = (*cinderVolumeSource)(nil)
    91  
    92  // CreateVolumes implements storage.VolumeSource.
    93  func (s *cinderVolumeSource) CreateVolumes(args []storage.VolumeParams) (_ []storage.Volume, _ []storage.VolumeAttachment, resultErr error) {
    94  	volumes := make([]storage.Volume, len(args))
    95  	for i, arg := range args {
    96  		volume, err := s.createVolume(arg)
    97  		if err != nil {
    98  			return nil, nil, err
    99  		}
   100  		volumes[i] = volume
   101  
   102  		// If the method exits with an error, be sure to delete any
   103  		// created volumes so that we're idempotent. We create several
   104  		// clousures instead of one to take advantage of the loop
   105  		// parameter.
   106  		defer func(arg storage.VolumeParams, volume storage.Volume) {
   107  			if resultErr == nil {
   108  				return
   109  			}
   110  			attachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.Attachment.InstanceId))
   111  			if err != nil {
   112  				logger.Warningf("could not list volumes while cleaning up: %v", err)
   113  				return
   114  			}
   115  			if err := detachVolume(
   116  				string(arg.Attachment.InstanceId),
   117  				volume.VolumeId,
   118  				attachments,
   119  				s.storageAdapter,
   120  			); err != nil {
   121  				logger.Warningf("could not detach volumes while cleaning up: %v", err)
   122  				return
   123  			}
   124  			if err := s.storageAdapter.DeleteVolume(volume.VolumeId); err != nil {
   125  				logger.Warningf("could not delete volumes while cleaning up: %v", err)
   126  			}
   127  		}(arg, volume)
   128  	}
   129  
   130  	attachmentParams := make([]storage.VolumeAttachmentParams, len(volumes))
   131  	for i, volume := range volumes {
   132  		attachmentParams[i] = *args[i].Attachment
   133  		attachmentParams[i].VolumeId = volume.VolumeId
   134  		attachmentParams[i].Volume = volume.Tag
   135  	}
   136  	attachments, err := s.AttachVolumes(attachmentParams)
   137  	if err != nil {
   138  		return nil, nil, errors.Trace(err)
   139  	}
   140  
   141  	return volumes, attachments, nil
   142  }
   143  
   144  func (s *cinderVolumeSource) createVolume(arg storage.VolumeParams) (storage.Volume, error) {
   145  	if b, ok := arg.Attributes[storage.Persistent]; ok && !b.(bool) {
   146  		return storage.Volume{}, errors.New("cannot create a non-persistent Cinder volume")
   147  	}
   148  	var metadata interface{}
   149  	if len(arg.ResourceTags) > 0 {
   150  		metadata = arg.ResourceTags
   151  	}
   152  	cinderVolume, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{
   153  		// The Cinder documentation incorrectly states the
   154  		// size parameter is in GB. It is actually GiB.
   155  		Size: int(math.Ceil(float64(arg.Size / 1024))),
   156  		Name: resourceName(arg.Tag, s.envName),
   157  		// TODO(axw) use the AZ of the initially attached machine.
   158  		AvailabilityZone: "",
   159  		Metadata:         metadata,
   160  	})
   161  	if err != nil {
   162  		return storage.Volume{}, errors.Trace(err)
   163  	}
   164  
   165  	// The response may (will?) come back before the volume transitions to,
   166  	// "creating", in which case it will not have a size or status. Wait for
   167  	// the volume to transition, so we can record its actual size.
   168  	cinderVolume, err = s.waitVolume(cinderVolume.ID, func(v *cinder.Volume) (bool, error) {
   169  		return v.Status != "", nil
   170  	})
   171  	if err != nil {
   172  		if err := s.DestroyVolumes([]string{cinderVolume.ID}); err != nil {
   173  			logger.Warningf("destroying volume %s: %s", cinderVolume.ID, err)
   174  		}
   175  		return storage.Volume{}, errors.Errorf("waiting for volume to be provisioned: %s", err)
   176  	}
   177  	logger.Debugf("created volume: %+v", cinderVolume)
   178  	return storage.Volume{arg.Tag, cinderToJujuVolumeInfo(cinderVolume)}, nil
   179  }
   180  
   181  // DescribeVolumes implements storage.VolumeSource.
   182  func (s *cinderVolumeSource) DescribeVolumes(volumeIds []string) ([]storage.VolumeInfo, error) {
   183  	// In most cases, it is quicker to get all volumes and loop
   184  	// locally than to make several round-trips to the provider.
   185  	cinderVolumes, err := s.storageAdapter.GetVolumesSimple()
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	volumesById := make(map[string]*cinder.Volume)
   190  	for i, volume := range cinderVolumes {
   191  		volumesById[volume.ID] = &cinderVolumes[i]
   192  	}
   193  	volumes := make([]storage.VolumeInfo, len(volumeIds))
   194  	for i, volumeId := range volumeIds {
   195  		cinderVolume, ok := volumesById[volumeId]
   196  		if !ok {
   197  			return nil, errors.NotFoundf("volume %q", volumeId)
   198  		}
   199  		volumes[i] = cinderToJujuVolumeInfo(cinderVolume)
   200  	}
   201  	return volumes, nil
   202  }
   203  
   204  // DestroyVolumes implements storage.VolumeSource.
   205  func (s *cinderVolumeSource) DestroyVolumes(volumeIds []string) []error {
   206  	errors := make([]error, len(volumeIds))
   207  	for i, volumeId := range volumeIds {
   208  		if err := s.storageAdapter.DeleteVolume(volumeId); err != nil {
   209  			errors[i] = err
   210  		}
   211  	}
   212  	return errors
   213  }
   214  
   215  // ValidateVolumeParams implements storage.VolumeSource.
   216  func (s *cinderVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   217  	return nil
   218  }
   219  
   220  // AttachVolumes implements storage.VolumeSource.
   221  func (s *cinderVolumeSource) AttachVolumes(args []storage.VolumeAttachmentParams) ([]storage.VolumeAttachment, error) {
   222  	attachments := make([]storage.VolumeAttachment, len(args))
   223  	for i, arg := range args {
   224  		attachment, err := s.attachVolume(arg)
   225  		if err != nil {
   226  			return nil, errors.Trace(err)
   227  		}
   228  		attachments[i] = attachment
   229  	}
   230  	return attachments, nil
   231  }
   232  
   233  func (s *cinderVolumeSource) attachVolume(arg storage.VolumeAttachmentParams) (storage.VolumeAttachment, error) {
   234  	// Check to see if the volume is already attached.
   235  	existingAttachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.InstanceId))
   236  	if err != nil {
   237  		return storage.VolumeAttachment{}, err
   238  	}
   239  	novaAttachment := findAttachment(arg.VolumeId, existingAttachments)
   240  	if novaAttachment == nil {
   241  		// A volume must be "available" before it can be attached.
   242  		if _, err := s.waitVolume(arg.VolumeId, func(v *cinder.Volume) (bool, error) {
   243  			return v.Status == "available", nil
   244  		}); err != nil {
   245  			return storage.VolumeAttachment{}, errors.Annotate(err, "waiting for volume to become available")
   246  		}
   247  		novaAttachment, err = s.storageAdapter.AttachVolume(
   248  			string(arg.InstanceId),
   249  			arg.VolumeId,
   250  			autoAssignedMountPoint,
   251  		)
   252  		if err != nil {
   253  			return storage.VolumeAttachment{}, err
   254  		}
   255  	}
   256  	return storage.VolumeAttachment{
   257  		arg.Volume,
   258  		arg.Machine,
   259  		storage.VolumeAttachmentInfo{
   260  			DeviceName: novaAttachment.Device[len("/dev/"):],
   261  		},
   262  	}, nil
   263  }
   264  
   265  func (s *cinderVolumeSource) waitVolume(
   266  	volumeId string,
   267  	pred func(*cinder.Volume) (bool, error),
   268  ) (*cinder.Volume, error) {
   269  	for a := cinderAttempt.Start(); a.Next(); {
   270  		volume, err := s.storageAdapter.GetVolume(volumeId)
   271  		if err != nil {
   272  			return nil, errors.Annotate(err, "getting volume")
   273  		}
   274  		ok, err := pred(volume)
   275  		if err != nil {
   276  			return nil, errors.Trace(err)
   277  		}
   278  		if ok {
   279  			return volume, nil
   280  		}
   281  	}
   282  	return nil, errors.New("timed out")
   283  }
   284  
   285  // DetachVolumes implements storage.VolumeSource.
   286  func (s *cinderVolumeSource) DetachVolumes(args []storage.VolumeAttachmentParams) error {
   287  	for _, arg := range args {
   288  		// Check to see if the volume is already detached.
   289  		attachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.InstanceId))
   290  		if err != nil {
   291  			return err
   292  		}
   293  		if err := detachVolume(
   294  			string(arg.InstanceId),
   295  			arg.VolumeId,
   296  			attachments,
   297  			s.storageAdapter,
   298  		); err != nil {
   299  			return err
   300  		}
   301  	}
   302  	return nil
   303  }
   304  
   305  func cinderToJujuVolumeInfo(volume *cinder.Volume) storage.VolumeInfo {
   306  	return storage.VolumeInfo{
   307  		VolumeId: volume.ID,
   308  		Size:     uint64(volume.Size * 1024),
   309  		// TODO(axw) there is currently no way to mark a volume as
   310  		// "delete on termination", so all volumes are persistent.
   311  		Persistent: true,
   312  	}
   313  }
   314  
   315  func detachVolume(instanceId, volumeId string, attachments []nova.VolumeAttachment, storageAdapter openstackStorage) error {
   316  	// TODO(axw) verify whether we need to do this find step. From looking at the example
   317  	// responses in the OpenStack docs, the "attachment ID" is always the same as the
   318  	// volume ID. So we should just be able to issue a blind detach request, and then
   319  	// ignore errors that indicate the volume is already detached.
   320  	if findAttachment(volumeId, attachments) == nil {
   321  		return nil
   322  	}
   323  	return storageAdapter.DetachVolume(instanceId, volumeId)
   324  }
   325  
   326  func findAttachment(volId string, attachments []nova.VolumeAttachment) *nova.VolumeAttachment {
   327  	for _, attachment := range attachments {
   328  		if attachment.VolumeId == volId {
   329  			return &attachment
   330  		}
   331  	}
   332  	return nil
   333  }
   334  
   335  type openstackStorage interface {
   336  	GetVolume(volumeId string) (*cinder.Volume, error)
   337  	GetVolumesSimple() ([]cinder.Volume, error)
   338  	DeleteVolume(volumeId string) error
   339  	CreateVolume(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error)
   340  	AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error)
   341  	DetachVolume(serverId, attachmentId string) error
   342  	ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error)
   343  }
   344  
   345  func newOpenstackStorageAdapter(environConfig *config.Config) (openstackStorage, error) {
   346  	ecfg, err := providerInstance.newConfig(environConfig)
   347  	if err != nil {
   348  		return nil, errors.Trace(err)
   349  	}
   350  	authClient := authClient(ecfg)
   351  	if err := authClient.Authenticate(); err != nil {
   352  		return nil, errors.Trace(err)
   353  	}
   354  
   355  	endpoint := authClient.EndpointsForRegion(ecfg.region())["volume"]
   356  	endpointUrl, err := url.Parse(endpoint)
   357  	if err != nil {
   358  		return nil, errors.Annotate(err, "error parsing endpoint")
   359  	}
   360  
   361  	return &openstackStorageAdapter{
   362  		cinderClient{cinder.Basic(endpointUrl, authClient.TenantId(), authClient.Token)},
   363  		novaClient{nova.New(authClient)},
   364  	}, nil
   365  }
   366  
   367  type openstackStorageAdapter struct {
   368  	cinderClient
   369  	novaClient
   370  }
   371  
   372  type cinderClient struct {
   373  	*cinder.Client
   374  }
   375  
   376  type novaClient struct {
   377  	*nova.Client
   378  }
   379  
   380  // CreateVolume is part of the openstackStorage interface.
   381  func (ga *openstackStorageAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   382  	resp, err := ga.cinderClient.CreateVolume(args)
   383  	if err != nil {
   384  		return nil, err
   385  	}
   386  	return &resp.Volume, nil
   387  }
   388  
   389  // GetVolumesSimple is part of the openstackStorage interface.
   390  func (ga *openstackStorageAdapter) GetVolumesSimple() ([]cinder.Volume, error) {
   391  	resp, err := ga.cinderClient.GetVolumesSimple()
   392  	if err != nil {
   393  		return nil, err
   394  	}
   395  	return resp.Volumes, nil
   396  }
   397  
   398  // GetVolume is part of the openstackStorage interface.
   399  func (ga *openstackStorageAdapter) GetVolume(volumeId string) (*cinder.Volume, error) {
   400  	resp, err := ga.cinderClient.GetVolume(volumeId)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	return &resp.Volume, nil
   405  }