github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"fmt"
     8  	"math"
     9  	"net/url"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/schema"
    15  	"github.com/juju/utils"
    16  	"gopkg.in/goose.v2/cinder"
    17  	gooseerrors "gopkg.in/goose.v2/errors"
    18  	"gopkg.in/goose.v2/identity"
    19  	"gopkg.in/goose.v2/nova"
    20  
    21  	"github.com/juju/juju/core/instance"
    22  	"github.com/juju/juju/environs/context"
    23  	"github.com/juju/juju/environs/tags"
    24  	"github.com/juju/juju/provider/common"
    25  	"github.com/juju/juju/storage"
    26  )
    27  
    28  const (
    29  	CinderProviderType = storage.ProviderType("cinder")
    30  
    31  	cinderVolumeType = "volume-type"
    32  
    33  	// autoAssignedMountPoint specifies the value to pass in when
    34  	// you'd like Cinder to automatically assign a mount point.
    35  	autoAssignedMountPoint = ""
    36  
    37  	volumeStatusAvailable = "available"
    38  	volumeStatusDeleting  = "deleting"
    39  	volumeStatusError     = "error"
    40  	volumeStatusInUse     = "in-use"
    41  )
    42  
    43  var cinderConfigFields = schema.Fields{
    44  	cinderVolumeType: schema.String(),
    45  }
    46  
    47  var cinderConfigChecker = schema.FieldMap(
    48  	cinderConfigFields,
    49  	schema.Defaults{
    50  		cinderVolumeType: schema.Omit,
    51  	},
    52  )
    53  
    54  type cinderConfig struct {
    55  	volumeType string
    56  }
    57  
    58  func newCinderConfig(attrs map[string]interface{}) (*cinderConfig, error) {
    59  	out, err := cinderConfigChecker.Coerce(attrs, nil)
    60  	if err != nil {
    61  		return nil, errors.Annotate(err, "validating Cinder storage config")
    62  	}
    63  	coerced := out.(map[string]interface{})
    64  	volumeType, _ := coerced[cinderVolumeType].(string)
    65  	cinderConfig := &cinderConfig{
    66  		volumeType: volumeType,
    67  	}
    68  	return cinderConfig, nil
    69  }
    70  
    71  // StorageProviderTypes implements storage.ProviderRegistry.
    72  func (env *Environ) StorageProviderTypes() ([]storage.ProviderType, error) {
    73  	var types []storage.ProviderType
    74  	if _, err := env.cinderProvider(); err == nil {
    75  		types = append(types, CinderProviderType)
    76  	} else if !errors.IsNotSupported(err) {
    77  		return nil, errors.Trace(err)
    78  	}
    79  	return types, nil
    80  }
    81  
    82  // StorageProvider implements storage.ProviderRegistry.
    83  func (env *Environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
    84  	if t != CinderProviderType {
    85  		return nil, errors.NotFoundf("storage provider %q", t)
    86  	}
    87  	return env.cinderProvider()
    88  }
    89  
    90  func (env *Environ) cinderProvider() (*cinderProvider, error) {
    91  	storageAdapter, err := newOpenstackStorage(env)
    92  	if err != nil {
    93  		return nil, errors.Trace(err)
    94  	}
    95  	return &cinderProvider{
    96  		storageAdapter: storageAdapter,
    97  		envName:        env.name,
    98  		modelUUID:      env.uuid,
    99  		namespace:      env.namespace,
   100  	}, nil
   101  }
   102  
   103  var newOpenstackStorage = func(env *Environ) (OpenstackStorage, error) {
   104  	env.ecfgMutex.Lock()
   105  	defer env.ecfgMutex.Unlock()
   106  
   107  	client := env.clientUnlocked
   108  	if env.volumeURL == nil {
   109  		url, err := getVolumeEndpointURL(client, env.cloud.Region)
   110  		if errors.IsNotFound(err) {
   111  			// No volume endpoint found; Cinder is not supported.
   112  			return nil, errors.NotSupportedf("volumes")
   113  		} else if err != nil {
   114  			return nil, errors.Trace(err)
   115  		}
   116  		env.volumeURL = url
   117  		logger.Debugf("volume URL: %v", url)
   118  	}
   119  
   120  	cinderCl := cinderClient{cinder.Basic(env.volumeURL, client.TenantId(), client.Token)}
   121  
   122  	cloudSpec := env.cloud
   123  	if len(cloudSpec.CACertificates) > 0 {
   124  		cinderCl = cinderClient{cinder.BasicTLSConfig(
   125  			env.volumeURL,
   126  			client.TenantId(),
   127  			client.Token,
   128  			tlsConfig(cloudSpec.CACertificates)),
   129  		}
   130  	}
   131  
   132  	return &openstackStorageAdapter{
   133  		cinderCl,
   134  		novaClient{env.novaUnlocked},
   135  	}, nil
   136  }
   137  
   138  type cinderProvider struct {
   139  	storageAdapter OpenstackStorage
   140  	envName        string
   141  	modelUUID      string
   142  	namespace      instance.Namespace
   143  }
   144  
   145  var _ storage.Provider = (*cinderProvider)(nil)
   146  
   147  var cinderAttempt = utils.AttemptStrategy{
   148  	Total: 1 * time.Minute,
   149  	Delay: 5 * time.Second,
   150  }
   151  
   152  // VolumeSource implements storage.Provider.
   153  func (p *cinderProvider) VolumeSource(providerConfig *storage.Config) (storage.VolumeSource, error) {
   154  	if err := p.ValidateConfig(providerConfig); err != nil {
   155  		return nil, err
   156  	}
   157  	source := &cinderVolumeSource{
   158  		storageAdapter: p.storageAdapter,
   159  		envName:        p.envName,
   160  		modelUUID:      p.modelUUID,
   161  		namespace:      p.namespace,
   162  	}
   163  	return source, nil
   164  }
   165  
   166  // FilesystemSource implements storage.Provider.
   167  func (p *cinderProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
   168  	return nil, errors.NotSupportedf("filesystems")
   169  }
   170  
   171  // Supports implements storage.Provider.
   172  func (p *cinderProvider) Supports(kind storage.StorageKind) bool {
   173  	switch kind {
   174  	case storage.StorageKindBlock:
   175  		return true
   176  	}
   177  	return false
   178  }
   179  
   180  // Scope implements storage.Provider.
   181  func (s *cinderProvider) Scope() storage.Scope {
   182  	return storage.ScopeEnviron
   183  }
   184  
   185  // ValidateConfig implements storage.Provider.
   186  func (p *cinderProvider) ValidateConfig(cfg *storage.Config) error {
   187  	// TODO(axw) 2015-05-01 #1450737
   188  	// Reject attempts to create non-persistent volumes.
   189  	_, err := newCinderConfig(cfg.Attrs())
   190  	return errors.Trace(err)
   191  }
   192  
   193  // Dynamic implements storage.Provider.
   194  func (p *cinderProvider) Dynamic() bool {
   195  	return true
   196  }
   197  
   198  // Releasable is defined on the Provider interface.
   199  func (*cinderProvider) Releasable() bool {
   200  	return true
   201  }
   202  
   203  // DefaultPools implements storage.Provider.
   204  func (p *cinderProvider) DefaultPools() []*storage.Config {
   205  	return nil
   206  }
   207  
   208  type cinderVolumeSource struct {
   209  	storageAdapter OpenstackStorage
   210  	envName        string // non unique, informational only
   211  	modelUUID      string
   212  	namespace      instance.Namespace
   213  }
   214  
   215  var _ storage.VolumeSource = (*cinderVolumeSource)(nil)
   216  
   217  // CreateVolumes implements storage.VolumeSource.
   218  func (s *cinderVolumeSource) CreateVolumes(ctx context.ProviderCallContext, args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   219  	results := make([]storage.CreateVolumesResult, len(args))
   220  	for i, arg := range args {
   221  		volume, err := s.createVolume(arg)
   222  		if err != nil {
   223  			results[i].Error = errors.Trace(err)
   224  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
   225  				// If it is an unauthorised error, no need to continue since we will 100% fail...
   226  				break
   227  			}
   228  			continue
   229  		}
   230  		results[i].Volume = volume
   231  	}
   232  	return results, nil
   233  }
   234  
   235  func (s *cinderVolumeSource) createVolume(arg storage.VolumeParams) (*storage.Volume, error) {
   236  	cinderConfig, err := newCinderConfig(arg.Attributes)
   237  	if err != nil {
   238  		return nil, errors.Trace(err)
   239  	}
   240  
   241  	var metadata interface{}
   242  	if len(arg.ResourceTags) > 0 {
   243  		metadata = arg.ResourceTags
   244  	}
   245  	cinderVolume, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{
   246  		// The Cinder documentation incorrectly states the
   247  		// size parameter is in GB. It is actually GiB.
   248  		Size:       int(math.Ceil(float64(arg.Size / 1024))),
   249  		Name:       resourceName(s.namespace, s.envName, arg.Tag.String()),
   250  		VolumeType: cinderConfig.volumeType,
   251  		// TODO(axw) use the AZ of the initially attached machine.
   252  		AvailabilityZone: "",
   253  		Metadata:         metadata,
   254  	})
   255  	if err != nil {
   256  		return nil, errors.Trace(err)
   257  	}
   258  
   259  	// The response may (will?) come back before the volume transitions to
   260  	// "creating", in which case it will not have a size or status. Wait for
   261  	// the volume to transition, so we can record its actual size.
   262  	volumeId := cinderVolume.ID
   263  	cinderVolume, err = waitVolume(s.storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) {
   264  		return v.Status != "", nil
   265  	})
   266  	if err != nil {
   267  		if err := s.storageAdapter.DeleteVolume(volumeId); err != nil {
   268  			logger.Warningf("destroying volume %s: %s", volumeId, err)
   269  		}
   270  		return nil, errors.Errorf("waiting for volume to be provisioned: %s", err)
   271  	}
   272  	logger.Debugf("created volume: %+v", cinderVolume)
   273  	return &storage.Volume{arg.Tag, cinderToJujuVolumeInfo(cinderVolume)}, nil
   274  }
   275  
   276  // ListVolumes is specified on the storage.VolumeSource interface.
   277  func (s *cinderVolumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) {
   278  	cinderVolumes, err := modelCinderVolumes(s.storageAdapter, s.modelUUID)
   279  	if err != nil {
   280  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   281  		return nil, errors.Trace(err)
   282  	}
   283  	return volumeInfoToVolumeIds(cinderToJujuVolumeInfos(cinderVolumes)), nil
   284  }
   285  
   286  // modelCinderVolumes returns all of the cinder volumes for the model.
   287  func modelCinderVolumes(storageAdapter OpenstackStorage, modelUUID string) ([]cinder.Volume, error) {
   288  	return cinderVolumes(storageAdapter, func(v *cinder.Volume) bool {
   289  		return v.Metadata[tags.JujuModel] == modelUUID
   290  	})
   291  }
   292  
   293  // controllerCinderVolumes returns all of the cinder volumes for the model.
   294  func controllerCinderVolumes(storageAdapter OpenstackStorage, controllerUUID string) ([]cinder.Volume, error) {
   295  	return cinderVolumes(storageAdapter, func(v *cinder.Volume) bool {
   296  		return v.Metadata[tags.JujuController] == controllerUUID
   297  	})
   298  }
   299  
   300  // cinderVolumes returns all of the cinder volumes matching the given predicate.
   301  func cinderVolumes(storageAdapter OpenstackStorage, pred func(*cinder.Volume) bool) ([]cinder.Volume, error) {
   302  	allCinderVolumes, err := storageAdapter.GetVolumesDetail()
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	var matching []cinder.Volume
   307  	for _, v := range allCinderVolumes {
   308  		if pred(&v) {
   309  			matching = append(matching, v)
   310  		}
   311  	}
   312  	return matching, nil
   313  }
   314  
   315  func volumeInfoToVolumeIds(volumes []storage.VolumeInfo) []string {
   316  	volumeIds := make([]string, len(volumes))
   317  	for i, volume := range volumes {
   318  		volumeIds[i] = volume.VolumeId
   319  	}
   320  	return volumeIds
   321  }
   322  
   323  // DescribeVolumes implements storage.VolumeSource.
   324  func (s *cinderVolumeSource) DescribeVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]storage.DescribeVolumesResult, error) {
   325  	// In most cases, it is quicker to get all volumes and loop
   326  	// locally than to make several round-trips to the provider.
   327  	cinderVolumes, err := s.storageAdapter.GetVolumesDetail()
   328  	if err != nil {
   329  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   330  		return nil, errors.Trace(err)
   331  	}
   332  	volumesById := make(map[string]*cinder.Volume)
   333  	for i, volume := range cinderVolumes {
   334  		volumesById[volume.ID] = &cinderVolumes[i]
   335  	}
   336  	results := make([]storage.DescribeVolumesResult, len(volumeIds))
   337  	for i, volumeId := range volumeIds {
   338  		cinderVolume, ok := volumesById[volumeId]
   339  		if !ok {
   340  			results[i].Error = errors.NotFoundf("volume %q", volumeId)
   341  			continue
   342  		}
   343  		info := cinderToJujuVolumeInfo(cinderVolume)
   344  		results[i].VolumeInfo = &info
   345  	}
   346  	return results, nil
   347  }
   348  
   349  // DestroyVolumes implements storage.VolumeSource.
   350  func (s *cinderVolumeSource) DestroyVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) {
   351  	return foreachVolume(ctx, s.storageAdapter, volumeIds, destroyVolume), nil
   352  }
   353  
   354  // ReleaseVolumes implements storage.VolumeSource.
   355  func (s *cinderVolumeSource) ReleaseVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) {
   356  	return foreachVolume(ctx, s.storageAdapter, volumeIds, releaseVolume), nil
   357  }
   358  
   359  func foreachVolume(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, volumeIds []string, f func(context.ProviderCallContext, OpenstackStorage, string) error) []error {
   360  	var wg sync.WaitGroup
   361  	wg.Add(len(volumeIds))
   362  	results := make([]error, len(volumeIds))
   363  	for i, volumeId := range volumeIds {
   364  		go func(i int, volumeId string) {
   365  			defer wg.Done()
   366  			results[i] = f(ctx, storageAdapter, volumeId)
   367  		}(i, volumeId)
   368  	}
   369  	wg.Wait()
   370  	return results
   371  }
   372  
   373  func destroyVolume(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, volumeId string) error {
   374  	logger.Debugf("destroying volume %q", volumeId)
   375  	// Volumes must not be in-use when destroying. A volume may
   376  	// still be in-use when the instance it is attached to is
   377  	// in the process of being terminated.
   378  	var issuedDetach bool
   379  	volume, err := waitVolume(storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) {
   380  		switch v.Status {
   381  		default:
   382  			// Not ready for deletion; keep waiting.
   383  			return false, nil
   384  		case volumeStatusAvailable, volumeStatusDeleting, volumeStatusError:
   385  			return true, nil
   386  		case volumeStatusInUse:
   387  			// Detach below.
   388  			break
   389  		}
   390  		// Volume is still attached, so detach it.
   391  		if !issuedDetach {
   392  			args := make([]storage.VolumeAttachmentParams, len(v.Attachments))
   393  			for i, a := range v.Attachments {
   394  				args[i].VolumeId = volumeId
   395  				args[i].InstanceId = instance.Id(a.ServerId)
   396  			}
   397  			if len(args) > 0 {
   398  				results := detachVolumes(ctx, storageAdapter, args)
   399  				for _, err := range results {
   400  					if err != nil {
   401  						return false, errors.Trace(err)
   402  					}
   403  				}
   404  			}
   405  			issuedDetach = true
   406  		}
   407  		return false, nil
   408  	})
   409  	if err != nil {
   410  		if errors.IsNotFound(err) {
   411  			// The volume wasn't found; nothing
   412  			// to destroy, so we're done.
   413  			return nil
   414  		}
   415  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   416  		return errors.Trace(err)
   417  	}
   418  	if volume.Status == volumeStatusDeleting {
   419  		// Already being deleted, nothing to do.
   420  		return nil
   421  	}
   422  	if err := storageAdapter.DeleteVolume(volumeId); err != nil {
   423  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   424  		return errors.Trace(err)
   425  	}
   426  	return nil
   427  }
   428  
   429  func releaseVolume(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, volumeId string) error {
   430  	logger.Debugf("releasing volume %q", volumeId)
   431  	_, err := waitVolume(storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) {
   432  		switch v.Status {
   433  		case volumeStatusAvailable, volumeStatusError:
   434  			return true, nil
   435  		case volumeStatusDeleting:
   436  			return false, errors.New("volume is being deleted")
   437  		case volumeStatusInUse:
   438  			return false, errors.New("volume still in-use")
   439  		}
   440  		// Not ready for releasing; keep waiting.
   441  		return false, nil
   442  	})
   443  	if err != nil {
   444  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   445  		return errors.Annotatef(err, "cannot release volume %q", volumeId)
   446  	}
   447  	// Drop the model and controller tags from the volume.
   448  	tags := map[string]string{
   449  		tags.JujuModel:      "",
   450  		tags.JujuController: "",
   451  	}
   452  	_, err = storageAdapter.SetVolumeMetadata(volumeId, tags)
   453  	common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   454  	return errors.Annotate(err, "tagging volume")
   455  }
   456  
   457  // ValidateVolumeParams implements storage.VolumeSource.
   458  func (s *cinderVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   459  	_, err := newCinderConfig(params.Attributes)
   460  	return errors.Trace(err)
   461  }
   462  
   463  // AttachVolumes implements storage.VolumeSource.
   464  func (s *cinderVolumeSource) AttachVolumes(ctx context.ProviderCallContext, args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   465  	results := make([]storage.AttachVolumesResult, len(args))
   466  	for i, arg := range args {
   467  		attachment, err := s.attachVolume(arg)
   468  		if err != nil {
   469  			results[i].Error = errors.Trace(err)
   470  			if denial := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denial {
   471  				// We do not want to continue here as we'll 100% fail if we got unauthorised error.
   472  				break
   473  			}
   474  			continue
   475  		}
   476  		results[i].VolumeAttachment = attachment
   477  	}
   478  	return results, nil
   479  }
   480  
   481  func (s *cinderVolumeSource) attachVolume(arg storage.VolumeAttachmentParams) (*storage.VolumeAttachment, error) {
   482  	// Check to see if the volume is already attached.
   483  	existingAttachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.InstanceId))
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  	novaAttachment := findAttachment(arg.VolumeId, existingAttachments)
   488  	if novaAttachment == nil {
   489  		// A volume must be "available" before it can be attached.
   490  		if _, err := waitVolume(s.storageAdapter, arg.VolumeId, func(v *cinder.Volume) (bool, error) {
   491  			return v.Status == "available", nil
   492  		}); err != nil {
   493  			return nil, errors.Annotate(err, "waiting for volume to become available")
   494  		}
   495  		novaAttachment, err = s.storageAdapter.AttachVolume(
   496  			string(arg.InstanceId),
   497  			arg.VolumeId,
   498  			autoAssignedMountPoint,
   499  		)
   500  		if err != nil {
   501  			return nil, err
   502  		}
   503  	}
   504  	if novaAttachment.Device == nil {
   505  		return nil, errors.Errorf("device not assigned to volume attachment")
   506  	}
   507  	return &storage.VolumeAttachment{
   508  		arg.Volume,
   509  		arg.Machine,
   510  		storage.VolumeAttachmentInfo{
   511  			DeviceName: (*novaAttachment.Device)[len("/dev/"):],
   512  		},
   513  	}, nil
   514  }
   515  
   516  // ImportVolume is part of the storage.VolumeImporter interface.
   517  func (s *cinderVolumeSource) ImportVolume(ctx context.ProviderCallContext, volumeId string, resourceTags map[string]string) (storage.VolumeInfo, error) {
   518  	volume, err := s.storageAdapter.GetVolume(volumeId)
   519  	if err != nil {
   520  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   521  		return storage.VolumeInfo{}, errors.Annotate(err, "getting volume")
   522  	}
   523  	if volume.Status != volumeStatusAvailable {
   524  		return storage.VolumeInfo{}, errors.Errorf(
   525  			"cannot import volume %q with status %q", volumeId, volume.Status,
   526  		)
   527  	}
   528  	if _, err := s.storageAdapter.SetVolumeMetadata(volumeId, resourceTags); err != nil {
   529  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   530  		return storage.VolumeInfo{}, errors.Annotatef(err, "tagging volume %q", volumeId)
   531  	}
   532  	return cinderToJujuVolumeInfo(volume), nil
   533  }
   534  
   535  func waitVolume(
   536  	storageAdapter OpenstackStorage,
   537  	volumeId string,
   538  	pred func(*cinder.Volume) (bool, error),
   539  ) (*cinder.Volume, error) {
   540  	for a := cinderAttempt.Start(); a.Next(); {
   541  		volume, err := storageAdapter.GetVolume(volumeId)
   542  		if err != nil {
   543  			return nil, errors.Annotate(err, "getting volume")
   544  		}
   545  		ok, err := pred(volume)
   546  		if err != nil {
   547  			return nil, errors.Trace(err)
   548  		}
   549  		if ok {
   550  			return volume, nil
   551  		}
   552  	}
   553  	return nil, errors.New("timed out")
   554  }
   555  
   556  // DetachVolumes implements storage.VolumeSource.
   557  func (s *cinderVolumeSource) DetachVolumes(ctx context.ProviderCallContext, args []storage.VolumeAttachmentParams) ([]error, error) {
   558  	return detachVolumes(ctx, s.storageAdapter, args), nil
   559  }
   560  
   561  func detachVolumes(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, args []storage.VolumeAttachmentParams) []error {
   562  	results := make([]error, len(args))
   563  	for i, arg := range args {
   564  		if err := detachVolume(
   565  			string(arg.InstanceId),
   566  			arg.VolumeId,
   567  			storageAdapter,
   568  		); err != nil {
   569  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   570  			results[i] = errors.Annotatef(
   571  				err, "detaching volume %s from server %s",
   572  				arg.VolumeId, arg.InstanceId,
   573  			)
   574  			continue
   575  		}
   576  	}
   577  	return results
   578  }
   579  
   580  func cinderToJujuVolumeInfos(volumes []cinder.Volume) []storage.VolumeInfo {
   581  	out := make([]storage.VolumeInfo, len(volumes))
   582  	for i, v := range volumes {
   583  		out[i] = cinderToJujuVolumeInfo(&v)
   584  	}
   585  	return out
   586  }
   587  
   588  func cinderToJujuVolumeInfo(volume *cinder.Volume) storage.VolumeInfo {
   589  	return storage.VolumeInfo{
   590  		VolumeId:   volume.ID,
   591  		Size:       uint64(volume.Size * 1024),
   592  		Persistent: true,
   593  	}
   594  }
   595  
   596  func detachVolume(instanceId, volumeId string, storageAdapter OpenstackStorage) error {
   597  	err := storageAdapter.DetachVolume(instanceId, volumeId)
   598  	if err != nil && !errors.IsNotFound(err) {
   599  		return errors.Trace(err)
   600  	}
   601  	// The volume was successfully detached, or was
   602  	// already detached (i.e. NotFound error case).
   603  	return nil
   604  }
   605  
   606  func findAttachment(volId string, attachments []nova.VolumeAttachment) *nova.VolumeAttachment {
   607  	for _, attachment := range attachments {
   608  		if attachment.VolumeId == volId {
   609  			return &attachment
   610  		}
   611  	}
   612  	return nil
   613  }
   614  
   615  type OpenstackStorage interface {
   616  	GetVolume(volumeId string) (*cinder.Volume, error)
   617  	GetVolumesDetail() ([]cinder.Volume, error)
   618  	DeleteVolume(volumeId string) error
   619  	CreateVolume(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error)
   620  	AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error)
   621  	DetachVolume(serverId, attachmentId string) error
   622  	ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error)
   623  	SetVolumeMetadata(volumeId string, metadata map[string]string) (map[string]string, error)
   624  }
   625  
   626  type endpointResolver interface {
   627  	Authenticate() error
   628  	IsAuthenticated() bool
   629  	EndpointsForRegion(region string) identity.ServiceURLs
   630  }
   631  
   632  func getVolumeEndpointURL(client endpointResolver, region string) (*url.URL, error) {
   633  	if !client.IsAuthenticated() {
   634  		if err := authenticateClient(client); err != nil {
   635  			return nil, errors.Trace(err)
   636  		}
   637  	}
   638  	endpointMap := client.EndpointsForRegion(region)
   639  	// The cinder openstack charm appends 'v2' to the type for the v2 api.
   640  	endpoint, ok := endpointMap["volumev2"]
   641  	if !ok {
   642  		logger.Debugf(`endpoint "volumev2" not found for %q region, trying "volume"`, region)
   643  		endpoint, ok = endpointMap["volume"]
   644  		if !ok {
   645  			return nil, errors.NotFoundf(`endpoint "volume" in region %q`, region)
   646  		}
   647  	}
   648  	return url.Parse(endpoint)
   649  }
   650  
   651  type openstackStorageAdapter struct {
   652  	cinderClient
   653  	novaClient
   654  }
   655  
   656  type cinderClient struct {
   657  	*cinder.Client
   658  }
   659  
   660  type novaClient struct {
   661  	*nova.Client
   662  }
   663  
   664  // CreateVolume is part of the OpenstackStorage interface.
   665  func (ga *openstackStorageAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   666  	resp, err := ga.cinderClient.CreateVolume(args)
   667  	if err != nil {
   668  		return nil, err
   669  	}
   670  	return &resp.Volume, nil
   671  }
   672  
   673  // GetVolumesDetail is part of the OpenstackStorage interface.
   674  func (ga *openstackStorageAdapter) GetVolumesDetail() ([]cinder.Volume, error) {
   675  	resp, err := ga.cinderClient.GetVolumesDetail()
   676  	if err != nil {
   677  		return nil, err
   678  	}
   679  	return resp.Volumes, nil
   680  }
   681  
   682  // GetVolume is part of the OpenstackStorage interface.
   683  func (ga *openstackStorageAdapter) GetVolume(volumeId string) (*cinder.Volume, error) {
   684  	resp, err := ga.cinderClient.GetVolume(volumeId)
   685  	if err != nil {
   686  		if gooseerrors.IsNotFound(err) {
   687  			return nil, errors.NotFoundf("volume %q", volumeId)
   688  		}
   689  		return nil, err
   690  	}
   691  	return &resp.Volume, nil
   692  }
   693  
   694  // SetVolumeMetadata is part of the OpenstackStorage interface.
   695  func (ga *openstackStorageAdapter) SetVolumeMetadata(volumeId string, metadata map[string]string) (map[string]string, error) {
   696  	return ga.cinderClient.SetVolumeMetadata(volumeId, metadata)
   697  }
   698  
   699  // DeleteVolume is part of the OpenstackStorage interface.
   700  func (ga *openstackStorageAdapter) DeleteVolume(volumeId string) error {
   701  	if err := ga.cinderClient.DeleteVolume(volumeId); err != nil {
   702  		if gooseerrors.IsNotFound(err) {
   703  			return errors.NotFoundf("volume %q", volumeId)
   704  		}
   705  		return err
   706  	}
   707  	return nil
   708  }
   709  
   710  // DetachVolume is part of the OpenstackStorage interface.
   711  func (ga *openstackStorageAdapter) DetachVolume(serverId, attachmentId string) error {
   712  	if err := ga.novaClient.DetachVolume(serverId, attachmentId); err != nil {
   713  		if gooseerrors.IsNotFound(err) {
   714  			return errors.NewNotFound(nil,
   715  				fmt.Sprintf("volume %q is not attached to server %q",
   716  					attachmentId, serverId,
   717  				),
   718  			)
   719  		}
   720  		return err
   721  	}
   722  	return nil
   723  }