github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/openstack/cinder_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack_test
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	gitjujutesting "github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/goose.v2/cinder"
    16  	gooseerrors "gopkg.in/goose.v2/errors"
    17  	"gopkg.in/goose.v2/identity"
    18  	"gopkg.in/goose.v2/nova"
    19  	"gopkg.in/juju/names.v2"
    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/openstack"
    25  	"github.com/juju/juju/storage"
    26  	"github.com/juju/juju/testing"
    27  )
    28  
    29  const (
    30  	mockVolId    = "0"
    31  	mockVolSize  = 1024 * 2
    32  	mockVolName  = "123"
    33  	mockServerId = "mock-server-id"
    34  	mockVolJson  = `{"volume":{"id": "` + mockVolId + `", "size":1,"name":"` + mockVolName + `"}}`
    35  )
    36  
    37  var (
    38  	mockVolumeTag  = names.NewVolumeTag(mockVolName)
    39  	mockMachineTag = names.NewMachineTag("456")
    40  )
    41  
    42  var _ = gc.Suite(&cinderVolumeSourceSuite{})
    43  
    44  type cinderVolumeSourceSuite struct {
    45  	testing.BaseSuite
    46  
    47  	callCtx           *context.CloudCallContext
    48  	invalidCredential bool
    49  }
    50  
    51  func (s *cinderVolumeSourceSuite) SetUpTest(c *gc.C) {
    52  	s.BaseSuite.SetUpTest(c)
    53  	s.callCtx = &context.CloudCallContext{
    54  		InvalidateCredentialFunc: func(string) error {
    55  			s.invalidCredential = true
    56  			return nil
    57  		},
    58  	}
    59  }
    60  
    61  func (s *cinderVolumeSourceSuite) TearDownTest(c *gc.C) {
    62  	s.invalidCredential = false
    63  	s.BaseSuite.TearDownTest(c)
    64  }
    65  
    66  func init() {
    67  	// Override attempt strategy to speed things up.
    68  	openstack.CinderAttempt.Delay = 0
    69  }
    70  
    71  func toStringPtr(s string) *string {
    72  	return &s
    73  }
    74  
    75  func (s *cinderVolumeSourceSuite) TestAttachVolumes(c *gc.C) {
    76  	mockAdapter := &mockAdapter{
    77  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
    78  			c.Check(volId, gc.Equals, mockVolId)
    79  			c.Check(serverId, gc.Equals, mockServerId)
    80  			return &nova.VolumeAttachment{
    81  				Id:       volId,
    82  				VolumeId: volId,
    83  				ServerId: serverId,
    84  				Device:   toStringPtr("/dev/sda"),
    85  			}, nil
    86  		},
    87  	}
    88  
    89  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
    90  	results, err := volSource.AttachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{
    91  		Volume:   mockVolumeTag,
    92  		VolumeId: mockVolId,
    93  		AttachmentParams: storage.AttachmentParams{
    94  			Provider:   openstack.CinderProviderType,
    95  			Machine:    mockMachineTag,
    96  			InstanceId: instance.Id(mockServerId),
    97  		}},
    98  	})
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	c.Check(results, jc.DeepEquals, []storage.AttachVolumesResult{{
   101  		VolumeAttachment: &storage.VolumeAttachment{
   102  			mockVolumeTag,
   103  			mockMachineTag,
   104  			storage.VolumeAttachmentInfo{
   105  				DeviceName: "sda",
   106  			},
   107  		},
   108  	}})
   109  }
   110  
   111  var testUnauthorisedGooseError = gooseerrors.NewUnauthorisedf(nil, "", "invalid auth")
   112  
   113  func (s *cinderVolumeSourceSuite) TestAttachVolumesInvalidCredential(c *gc.C) {
   114  	c.Assert(s.invalidCredential, jc.IsFalse)
   115  	mockAdapter := &mockAdapter{
   116  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
   117  			return &nova.VolumeAttachment{}, testUnauthorisedGooseError
   118  		},
   119  	}
   120  
   121  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   122  	_, err := volSource.AttachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{
   123  		Volume:   mockVolumeTag,
   124  		VolumeId: mockVolId,
   125  		AttachmentParams: storage.AttachmentParams{
   126  			Provider:   openstack.CinderProviderType,
   127  			Machine:    mockMachineTag,
   128  			InstanceId: instance.Id(mockServerId),
   129  		}},
   130  	})
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	c.Assert(s.invalidCredential, jc.IsTrue)
   133  }
   134  
   135  func (s *cinderVolumeSourceSuite) TestAttachVolumesNoDevice(c *gc.C) {
   136  	mockAdapter := &mockAdapter{
   137  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
   138  			return &nova.VolumeAttachment{
   139  				Id:       volId,
   140  				VolumeId: volId,
   141  				ServerId: serverId,
   142  				Device:   nil,
   143  			}, nil
   144  		},
   145  	}
   146  
   147  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   148  	results, err := volSource.AttachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{
   149  		Volume:   mockVolumeTag,
   150  		VolumeId: mockVolId,
   151  		AttachmentParams: storage.AttachmentParams{
   152  			Provider:   openstack.CinderProviderType,
   153  			Machine:    mockMachineTag,
   154  			InstanceId: instance.Id(mockServerId),
   155  		}},
   156  	})
   157  	c.Assert(err, jc.ErrorIsNil)
   158  	c.Assert(results, gc.HasLen, 1)
   159  	c.Assert(results[0].Error, gc.ErrorMatches, "device not assigned to volume attachment")
   160  }
   161  
   162  func (s *cinderVolumeSourceSuite) TestCreateVolume(c *gc.C) {
   163  	const (
   164  		requestedSize = 2 * 1024
   165  		providedSize  = 3 * 1024
   166  	)
   167  
   168  	s.PatchValue(openstack.CinderAttempt, utils.AttemptStrategy{Min: 3})
   169  
   170  	var getVolumeCalls int
   171  	mockAdapter := &mockAdapter{
   172  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   173  			c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{
   174  				Size: requestedSize / 1024,
   175  				Name: "juju-testmodel-volume-123",
   176  			})
   177  			return &cinder.Volume{
   178  				ID: mockVolId,
   179  			}, nil
   180  		},
   181  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   182  			var status string
   183  			getVolumeCalls++
   184  			if getVolumeCalls > 1 {
   185  				status = "available"
   186  			}
   187  			return &cinder.Volume{
   188  				ID:     volumeId,
   189  				Size:   providedSize / 1024,
   190  				Status: status,
   191  			}, nil
   192  		},
   193  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
   194  			c.Check(volId, gc.Equals, mockVolId)
   195  			c.Check(serverId, gc.Equals, mockServerId)
   196  			return &nova.VolumeAttachment{
   197  				Id:       volId,
   198  				VolumeId: volId,
   199  				ServerId: serverId,
   200  				Device:   toStringPtr("/dev/sda"),
   201  			}, nil
   202  		},
   203  	}
   204  
   205  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   206  	results, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{
   207  		Provider: openstack.CinderProviderType,
   208  		Tag:      mockVolumeTag,
   209  		Size:     requestedSize,
   210  		Attachment: &storage.VolumeAttachmentParams{
   211  			AttachmentParams: storage.AttachmentParams{
   212  				Provider:   openstack.CinderProviderType,
   213  				Machine:    mockMachineTag,
   214  				InstanceId: instance.Id(mockServerId),
   215  			},
   216  		},
   217  	}})
   218  	c.Assert(err, jc.ErrorIsNil)
   219  	c.Assert(results, gc.HasLen, 1)
   220  	c.Assert(results[0].Error, jc.ErrorIsNil)
   221  
   222  	c.Check(results[0].Volume, jc.DeepEquals, &storage.Volume{
   223  		mockVolumeTag,
   224  		storage.VolumeInfo{
   225  			VolumeId:   mockVolId,
   226  			Size:       providedSize,
   227  			Persistent: true,
   228  		},
   229  	})
   230  
   231  	// should have been 2 calls to GetVolume: twice initially
   232  	// to wait until the volume became available.
   233  	c.Check(getVolumeCalls, gc.Equals, 2)
   234  }
   235  
   236  func (s *cinderVolumeSourceSuite) TestCreateVolumeVolumeType(c *gc.C) {
   237  	var created bool
   238  	mockAdapter := &mockAdapter{
   239  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   240  			created = true
   241  			c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{
   242  				Size:       1,
   243  				Name:       "juju-testmodel-volume-123",
   244  				VolumeType: "SSD",
   245  			})
   246  			return &cinder.Volume{ID: mockVolId}, nil
   247  		},
   248  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   249  			return &cinder.Volume{
   250  				ID:     volumeId,
   251  				Size:   1,
   252  				Status: "available",
   253  			}, nil
   254  		},
   255  	}
   256  
   257  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   258  	_, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{
   259  		Provider: openstack.CinderProviderType,
   260  		Tag:      mockVolumeTag,
   261  		Size:     1024,
   262  		Attributes: map[string]interface{}{
   263  			"volume-type": "SSD",
   264  		},
   265  	}})
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	c.Assert(created, jc.IsTrue)
   268  }
   269  
   270  func (s *cinderVolumeSourceSuite) TestCreateVolumeInvalidCredential(c *gc.C) {
   271  	c.Assert(s.invalidCredential, jc.IsFalse)
   272  	mockAdapter := &mockAdapter{
   273  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   274  			return &cinder.Volume{}, testUnauthorisedGooseError
   275  		},
   276  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   277  			return &cinder.Volume{}, testUnauthorisedGooseError
   278  		},
   279  	}
   280  
   281  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   282  	_, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{
   283  		Provider: openstack.CinderProviderType,
   284  		Tag:      mockVolumeTag,
   285  		Size:     1024,
   286  		Attributes: map[string]interface{}{
   287  			"volume-type": "SSD",
   288  		},
   289  	}})
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	c.Assert(s.invalidCredential, jc.IsTrue)
   292  }
   293  
   294  func (s *cinderVolumeSourceSuite) TestResourceTags(c *gc.C) {
   295  	var created bool
   296  	mockAdapter := &mockAdapter{
   297  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   298  			created = true
   299  			c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{
   300  				Size: 1,
   301  				Name: "juju-testmodel-volume-123",
   302  				Metadata: map[string]string{
   303  					"ResourceTag1": "Value1",
   304  					"ResourceTag2": "Value2",
   305  				},
   306  			})
   307  			return &cinder.Volume{ID: mockVolId}, nil
   308  		},
   309  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   310  			return &cinder.Volume{
   311  				ID:     volumeId,
   312  				Size:   1,
   313  				Status: "available",
   314  			}, nil
   315  		},
   316  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
   317  			return &nova.VolumeAttachment{
   318  				Id:       volId,
   319  				VolumeId: volId,
   320  				ServerId: serverId,
   321  				Device:   toStringPtr("/dev/sda"),
   322  			}, nil
   323  		},
   324  	}
   325  
   326  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   327  	_, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{
   328  		Provider: openstack.CinderProviderType,
   329  		Tag:      mockVolumeTag,
   330  		Size:     1024,
   331  		ResourceTags: map[string]string{
   332  			"ResourceTag1": "Value1",
   333  			"ResourceTag2": "Value2",
   334  		},
   335  		Attachment: &storage.VolumeAttachmentParams{
   336  			AttachmentParams: storage.AttachmentParams{
   337  				Provider:   openstack.CinderProviderType,
   338  				Machine:    mockMachineTag,
   339  				InstanceId: instance.Id(mockServerId),
   340  			},
   341  		},
   342  	}})
   343  	c.Assert(err, jc.ErrorIsNil)
   344  	c.Assert(created, jc.IsTrue)
   345  }
   346  
   347  func (s *cinderVolumeSourceSuite) TestListVolumes(c *gc.C) {
   348  	mockAdapter := &mockAdapter{
   349  		getVolumesDetail: func() ([]cinder.Volume, error) {
   350  			return []cinder.Volume{{
   351  				ID: "volume-1",
   352  			}, {
   353  				ID: "volume-2",
   354  				Metadata: map[string]string{
   355  					tags.JujuModel: "something-else",
   356  				},
   357  			}, {
   358  				ID: "volume-3",
   359  				Metadata: map[string]string{
   360  					tags.JujuModel: testing.ModelTag.Id(),
   361  				},
   362  			}}, nil
   363  		},
   364  	}
   365  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   366  	volumeIds, err := volSource.ListVolumes(s.callCtx)
   367  	c.Assert(err, jc.ErrorIsNil)
   368  	c.Check(volumeIds, jc.DeepEquals, []string{"volume-3"})
   369  }
   370  
   371  func (s *cinderVolumeSourceSuite) TestListVolumesInvalidCredential(c *gc.C) {
   372  	c.Assert(s.invalidCredential, jc.IsFalse)
   373  	mockAdapter := &mockAdapter{
   374  		getVolumesDetail: func() ([]cinder.Volume, error) {
   375  			return []cinder.Volume{}, testUnauthorisedGooseError
   376  		},
   377  	}
   378  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   379  	_, err := volSource.ListVolumes(s.callCtx)
   380  	c.Assert(err, gc.ErrorMatches, "invalid auth")
   381  	c.Assert(s.invalidCredential, jc.IsTrue)
   382  }
   383  
   384  func (s *cinderVolumeSourceSuite) TestDescribeVolumes(c *gc.C) {
   385  	mockAdapter := &mockAdapter{
   386  		getVolumesDetail: func() ([]cinder.Volume, error) {
   387  			return []cinder.Volume{{
   388  				ID:   mockVolId,
   389  				Size: mockVolSize / 1024,
   390  			}}, nil
   391  		},
   392  	}
   393  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   394  	volumes, err := volSource.DescribeVolumes(s.callCtx, []string{mockVolId})
   395  	c.Assert(err, jc.ErrorIsNil)
   396  	c.Check(volumes, jc.DeepEquals, []storage.DescribeVolumesResult{{
   397  		VolumeInfo: &storage.VolumeInfo{
   398  			VolumeId:   mockVolId,
   399  			Size:       mockVolSize,
   400  			Persistent: true,
   401  		},
   402  	}})
   403  }
   404  
   405  func (s *cinderVolumeSourceSuite) TestDescribeVolumesInvalidCredential(c *gc.C) {
   406  	c.Assert(s.invalidCredential, jc.IsFalse)
   407  	mockAdapter := &mockAdapter{
   408  		getVolumesDetail: func() ([]cinder.Volume, error) {
   409  			return []cinder.Volume{}, testUnauthorisedGooseError
   410  		},
   411  	}
   412  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   413  	_, err := volSource.DescribeVolumes(s.callCtx, []string{mockVolId})
   414  	c.Assert(err, gc.ErrorMatches, "invalid auth")
   415  	c.Assert(s.invalidCredential, jc.IsTrue)
   416  }
   417  
   418  func (s *cinderVolumeSourceSuite) TestDestroyVolumes(c *gc.C) {
   419  	mockAdapter := &mockAdapter{}
   420  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   421  	errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId})
   422  	c.Assert(err, jc.ErrorIsNil)
   423  	c.Assert(errs, jc.DeepEquals, []error{nil})
   424  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   425  		{"GetVolume", []interface{}{mockVolId}},
   426  		{"DeleteVolume", []interface{}{mockVolId}},
   427  	})
   428  }
   429  
   430  func (s *cinderVolumeSourceSuite) TestDestroyVolumesNotFound(c *gc.C) {
   431  	mockAdapter := &mockAdapter{
   432  		getVolume: func(volId string) (*cinder.Volume, error) {
   433  			return nil, errors.NotFoundf("volume %q", volId)
   434  		},
   435  	}
   436  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   437  	errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId})
   438  	c.Assert(err, jc.ErrorIsNil)
   439  	c.Assert(errs, jc.DeepEquals, []error{nil})
   440  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   441  		{"GetVolume", []interface{}{mockVolId}},
   442  	})
   443  }
   444  
   445  func (s *cinderVolumeSourceSuite) TestDestroyVolumesAttached(c *gc.C) {
   446  	statuses := []string{"in-use", "detaching", "available"}
   447  
   448  	mockAdapter := &mockAdapter{
   449  		getVolume: func(volId string) (*cinder.Volume, error) {
   450  			c.Assert(statuses, gc.Not(gc.HasLen), 0)
   451  			status := statuses[0]
   452  			statuses = statuses[1:]
   453  			return &cinder.Volume{
   454  				ID:     volId,
   455  				Status: status,
   456  			}, nil
   457  		},
   458  	}
   459  
   460  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   461  	errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId})
   462  	c.Assert(err, jc.ErrorIsNil)
   463  	c.Assert(errs, gc.HasLen, 1)
   464  	c.Assert(errs[0], jc.ErrorIsNil)
   465  	c.Assert(statuses, gc.HasLen, 0)
   466  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{
   467  		"GetVolume", []interface{}{mockVolId},
   468  	}, {
   469  		"GetVolume", []interface{}{mockVolId},
   470  	}, {
   471  		"GetVolume", []interface{}{mockVolId},
   472  	}, {
   473  		"DeleteVolume", []interface{}{mockVolId},
   474  	}})
   475  }
   476  
   477  func (s *cinderVolumeSourceSuite) TestDestroyVolumesInvalidCredential(c *gc.C) {
   478  	c.Assert(s.invalidCredential, jc.IsFalse)
   479  	mockAdapter := &mockAdapter{
   480  		getVolume: func(volId string) (*cinder.Volume, error) {
   481  			return &cinder.Volume{}, testUnauthorisedGooseError
   482  		},
   483  	}
   484  
   485  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   486  	errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId})
   487  	c.Assert(err, jc.ErrorIsNil)
   488  	c.Assert(errs, gc.HasLen, 1)
   489  	c.Assert(errs[0], gc.ErrorMatches, "getting volume: invalid auth")
   490  	c.Assert(s.invalidCredential, jc.IsTrue)
   491  	mockAdapter.CheckCallNames(c, "GetVolume")
   492  }
   493  
   494  func (s *cinderVolumeSourceSuite) TestReleaseVolumes(c *gc.C) {
   495  	mockAdapter := &mockAdapter{}
   496  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   497  	errs, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId})
   498  	c.Assert(err, jc.ErrorIsNil)
   499  	c.Assert(errs, jc.DeepEquals, []error{nil})
   500  	metadata := map[string]string{
   501  		"juju-controller-uuid": "",
   502  		"juju-model-uuid":      "",
   503  	}
   504  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   505  		{"GetVolume", []interface{}{mockVolId}},
   506  		{"SetVolumeMetadata", []interface{}{mockVolId, metadata}},
   507  	})
   508  }
   509  
   510  func (s *cinderVolumeSourceSuite) TestReleaseVolumesAttached(c *gc.C) {
   511  	mockAdapter := &mockAdapter{
   512  		getVolume: func(volId string) (*cinder.Volume, error) {
   513  			return &cinder.Volume{
   514  				ID:     volId,
   515  				Status: "in-use",
   516  			}, nil
   517  		},
   518  	}
   519  
   520  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   521  	errs, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId})
   522  	c.Assert(err, jc.ErrorIsNil)
   523  	c.Assert(errs, gc.HasLen, 1)
   524  	c.Assert(errs[0], gc.ErrorMatches, `cannot release volume "0": volume still in-use`)
   525  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{
   526  		"GetVolume", []interface{}{mockVolId},
   527  	}})
   528  }
   529  
   530  func (s *cinderVolumeSourceSuite) TestReleaseVolumesInvalidCredential(c *gc.C) {
   531  	c.Assert(s.invalidCredential, jc.IsFalse)
   532  	mockAdapter := &mockAdapter{
   533  		getVolume: func(volId string) (*cinder.Volume, error) {
   534  			return &cinder.Volume{}, testUnauthorisedGooseError
   535  		},
   536  	}
   537  
   538  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   539  	_, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId})
   540  	c.Assert(err, jc.ErrorIsNil)
   541  	c.Assert(s.invalidCredential, jc.IsTrue)
   542  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{
   543  		"GetVolume", []interface{}{mockVolId},
   544  	}})
   545  }
   546  
   547  func (s *cinderVolumeSourceSuite) TestReleaseVolumesDetaching(c *gc.C) {
   548  	statuses := []string{"detaching", "available"}
   549  
   550  	mockAdapter := &mockAdapter{
   551  		getVolume: func(volId string) (*cinder.Volume, error) {
   552  			c.Assert(statuses, gc.Not(gc.HasLen), 0)
   553  			status := statuses[0]
   554  			statuses = statuses[1:]
   555  			return &cinder.Volume{
   556  				ID:     volId,
   557  				Status: status,
   558  			}, nil
   559  		},
   560  	}
   561  
   562  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   563  	errs, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId})
   564  	c.Assert(err, jc.ErrorIsNil)
   565  	c.Assert(errs, gc.HasLen, 1)
   566  	c.Assert(errs[0], jc.ErrorIsNil)
   567  	c.Assert(statuses, gc.HasLen, 0)
   568  	mockAdapter.CheckCallNames(c, "GetVolume", "GetVolume", "SetVolumeMetadata")
   569  }
   570  
   571  func (s *cinderVolumeSourceSuite) TestDetachVolumes(c *gc.C) {
   572  	const mockServerId2 = mockServerId + "2"
   573  
   574  	var numDetachCalls int
   575  	mockAdapter := &mockAdapter{
   576  		detachVolume: func(serverId, volId string) error {
   577  			numDetachCalls++
   578  			if volId == "42" {
   579  				return errors.NotFoundf("attachment")
   580  			}
   581  			c.Check(serverId, gc.Equals, mockServerId)
   582  			c.Check(volId, gc.Equals, mockVolId)
   583  			return nil
   584  		},
   585  	}
   586  
   587  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   588  	errs, err := volSource.DetachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{
   589  		Volume:   names.NewVolumeTag("123"),
   590  		VolumeId: mockVolId,
   591  		AttachmentParams: storage.AttachmentParams{
   592  			Machine:    names.NewMachineTag("0"),
   593  			InstanceId: mockServerId,
   594  		},
   595  	}, {
   596  		Volume:   names.NewVolumeTag("42"),
   597  		VolumeId: "42",
   598  		AttachmentParams: storage.AttachmentParams{
   599  			Machine:    names.NewMachineTag("0"),
   600  			InstanceId: mockServerId2,
   601  		},
   602  	}})
   603  	c.Assert(err, jc.ErrorIsNil)
   604  	c.Assert(errs, jc.DeepEquals, []error{nil, nil})
   605  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   606  		{"DetachVolume", []interface{}{mockServerId, mockVolId}},
   607  		{"DetachVolume", []interface{}{mockServerId2, "42"}},
   608  	})
   609  }
   610  
   611  func (s *cinderVolumeSourceSuite) TestCreateVolumeCleanupDestroys(c *gc.C) {
   612  	var numCreateCalls, numDestroyCalls, numGetCalls int
   613  	mockAdapter := &mockAdapter{
   614  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   615  			numCreateCalls++
   616  			if numCreateCalls == 3 {
   617  				return nil, errors.New("no volume for you")
   618  			}
   619  			return &cinder.Volume{
   620  				ID:     fmt.Sprint(numCreateCalls),
   621  				Status: "",
   622  			}, nil
   623  		},
   624  		deleteVolume: func(volId string) error {
   625  			numDestroyCalls++
   626  			c.Assert(volId, gc.Equals, "2")
   627  			return errors.New("destroy fails")
   628  		},
   629  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   630  			numGetCalls++
   631  			if numGetCalls == 2 {
   632  				return nil, errors.New("no volume details for you")
   633  			}
   634  			return &cinder.Volume{
   635  				ID:     "4",
   636  				Size:   mockVolSize / 1024,
   637  				Status: "available",
   638  			}, nil
   639  		},
   640  	}
   641  
   642  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   643  	volumeParams := []storage.VolumeParams{{
   644  		Provider: openstack.CinderProviderType,
   645  		Tag:      names.NewVolumeTag("0"),
   646  		Size:     mockVolSize,
   647  		Attachment: &storage.VolumeAttachmentParams{
   648  			AttachmentParams: storage.AttachmentParams{
   649  				Provider:   openstack.CinderProviderType,
   650  				Machine:    mockMachineTag,
   651  				InstanceId: instance.Id(mockServerId),
   652  			},
   653  		},
   654  	}, {
   655  		Provider: openstack.CinderProviderType,
   656  		Tag:      names.NewVolumeTag("1"),
   657  		Size:     mockVolSize,
   658  		Attachment: &storage.VolumeAttachmentParams{
   659  			AttachmentParams: storage.AttachmentParams{
   660  				Provider:   openstack.CinderProviderType,
   661  				Machine:    mockMachineTag,
   662  				InstanceId: instance.Id(mockServerId),
   663  			},
   664  		},
   665  	}, {
   666  		Provider: openstack.CinderProviderType,
   667  		Tag:      names.NewVolumeTag("2"),
   668  		Size:     mockVolSize,
   669  		Attachment: &storage.VolumeAttachmentParams{
   670  			AttachmentParams: storage.AttachmentParams{
   671  				Provider:   openstack.CinderProviderType,
   672  				Machine:    mockMachineTag,
   673  				InstanceId: instance.Id(mockServerId),
   674  			},
   675  		},
   676  	}}
   677  	results, err := volSource.CreateVolumes(s.callCtx, volumeParams)
   678  	c.Assert(err, jc.ErrorIsNil)
   679  	c.Assert(results, gc.HasLen, 3)
   680  	c.Assert(results[0].Error, jc.ErrorIsNil)
   681  	c.Assert(results[1].Error, gc.ErrorMatches, "waiting for volume to be provisioned: getting volume: no volume details for you")
   682  	c.Assert(results[2].Error, gc.ErrorMatches, "no volume for you")
   683  	c.Assert(numCreateCalls, gc.Equals, 3)
   684  	c.Assert(numGetCalls, gc.Equals, 2)
   685  	c.Assert(numDestroyCalls, gc.Equals, 1)
   686  }
   687  
   688  func (s *cinderVolumeSourceSuite) TestImportVolume(c *gc.C) {
   689  	mockAdapter := &mockAdapter{
   690  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   691  			return &cinder.Volume{
   692  				ID:     volumeId,
   693  				Size:   mockVolSize / 1024,
   694  				Status: "available",
   695  			}, nil
   696  		},
   697  	}
   698  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   699  	c.Assert(volSource, gc.Implements, new(storage.VolumeImporter))
   700  
   701  	tags := map[string]string{
   702  		"a": "b",
   703  		"c": "d",
   704  	}
   705  	info, err := volSource.(storage.VolumeImporter).ImportVolume(s.callCtx, mockVolId, tags)
   706  	c.Assert(err, jc.ErrorIsNil)
   707  	c.Assert(info, jc.DeepEquals, storage.VolumeInfo{
   708  		VolumeId:   mockVolId,
   709  		Size:       mockVolSize,
   710  		Persistent: true,
   711  	})
   712  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   713  		{"GetVolume", []interface{}{mockVolId}},
   714  		{"SetVolumeMetadata", []interface{}{mockVolId, tags}},
   715  	})
   716  }
   717  
   718  func (s *cinderVolumeSourceSuite) TestImportVolumeInUse(c *gc.C) {
   719  	mockAdapter := &mockAdapter{
   720  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   721  			return &cinder.Volume{
   722  				ID:     volumeId,
   723  				Status: "in-use",
   724  			}, nil
   725  		},
   726  	}
   727  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   728  	_, err := volSource.(storage.VolumeImporter).ImportVolume(s.callCtx, mockVolId, nil)
   729  	c.Assert(err, gc.ErrorMatches, `cannot import volume "0" with status "in-use"`)
   730  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   731  		{"GetVolume", []interface{}{mockVolId}},
   732  	})
   733  }
   734  
   735  func (s *cinderVolumeSourceSuite) TestImportVolumeInvalidCredential(c *gc.C) {
   736  	c.Assert(s.invalidCredential, jc.IsFalse)
   737  	mockAdapter := &mockAdapter{
   738  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   739  			return &cinder.Volume{}, testUnauthorisedGooseError
   740  		},
   741  	}
   742  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   743  	_, err := volSource.(storage.VolumeImporter).ImportVolume(s.callCtx, mockVolId, nil)
   744  	c.Assert(err, gc.ErrorMatches, `getting volume: invalid auth`)
   745  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   746  		{"GetVolume", []interface{}{mockVolId}},
   747  	})
   748  	c.Assert(s.invalidCredential, jc.IsTrue)
   749  }
   750  
   751  type mockAdapter struct {
   752  	gitjujutesting.Stub
   753  	getVolume             func(string) (*cinder.Volume, error)
   754  	getVolumesDetail      func() ([]cinder.Volume, error)
   755  	deleteVolume          func(string) error
   756  	createVolume          func(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error)
   757  	attachVolume          func(string, string, string) (*nova.VolumeAttachment, error)
   758  	volumeStatusNotifier  func(string, string, int, time.Duration) <-chan error
   759  	detachVolume          func(string, string) error
   760  	listVolumeAttachments func(string) ([]nova.VolumeAttachment, error)
   761  	setVolumeMetadata     func(string, map[string]string) (map[string]string, error)
   762  }
   763  
   764  func (ma *mockAdapter) GetVolume(volumeId string) (*cinder.Volume, error) {
   765  	ma.MethodCall(ma, "GetVolume", volumeId)
   766  	if ma.getVolume != nil {
   767  		return ma.getVolume(volumeId)
   768  	}
   769  	return &cinder.Volume{
   770  		ID:     volumeId,
   771  		Status: "available",
   772  	}, nil
   773  }
   774  
   775  func (ma *mockAdapter) GetVolumesDetail() ([]cinder.Volume, error) {
   776  	ma.MethodCall(ma, "GetVolumesDetail")
   777  	if ma.getVolumesDetail != nil {
   778  		return ma.getVolumesDetail()
   779  	}
   780  	return nil, nil
   781  }
   782  
   783  func (ma *mockAdapter) DeleteVolume(volId string) error {
   784  	ma.MethodCall(ma, "DeleteVolume", volId)
   785  	if ma.deleteVolume != nil {
   786  		return ma.deleteVolume(volId)
   787  	}
   788  	return nil
   789  }
   790  
   791  func (ma *mockAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   792  	ma.MethodCall(ma, "CreateVolume", args)
   793  	if ma.createVolume != nil {
   794  		return ma.createVolume(args)
   795  	}
   796  	return nil, errors.NotImplementedf("CreateVolume")
   797  }
   798  
   799  func (ma *mockAdapter) AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error) {
   800  	ma.MethodCall(ma, "AttachVolume", serverId, volumeId, mountPoint)
   801  	if ma.attachVolume != nil {
   802  		return ma.attachVolume(serverId, volumeId, mountPoint)
   803  	}
   804  	return nil, errors.NotImplementedf("AttachVolume")
   805  }
   806  
   807  func (ma *mockAdapter) DetachVolume(serverId, attachmentId string) error {
   808  	ma.MethodCall(ma, "DetachVolume", serverId, attachmentId)
   809  	if ma.detachVolume != nil {
   810  		return ma.detachVolume(serverId, attachmentId)
   811  	}
   812  	return nil
   813  }
   814  
   815  func (ma *mockAdapter) ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error) {
   816  	ma.MethodCall(ma, "ListVolumeAttachments", serverId)
   817  	if ma.listVolumeAttachments != nil {
   818  		return ma.listVolumeAttachments(serverId)
   819  	}
   820  	return nil, nil
   821  }
   822  
   823  func (ma *mockAdapter) SetVolumeMetadata(volumeId string, metadata map[string]string) (map[string]string, error) {
   824  	ma.MethodCall(ma, "SetVolumeMetadata", volumeId, metadata)
   825  	if ma.setVolumeMetadata != nil {
   826  		return ma.setVolumeMetadata(volumeId, metadata)
   827  	}
   828  	return nil, nil
   829  }
   830  
   831  type testEndpointResolver struct {
   832  	authenticated   bool
   833  	regionEndpoints map[string]identity.ServiceURLs
   834  }
   835  
   836  func (r *testEndpointResolver) IsAuthenticated() bool {
   837  	return r.authenticated
   838  }
   839  
   840  func (r *testEndpointResolver) Authenticate() error {
   841  	r.authenticated = true
   842  	return nil
   843  }
   844  
   845  func (r *testEndpointResolver) EndpointsForRegion(region string) identity.ServiceURLs {
   846  	if !r.authenticated {
   847  		return identity.ServiceURLs{}
   848  	}
   849  	return r.regionEndpoints[region]
   850  }
   851  
   852  func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointVolume(c *gc.C) {
   853  	client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{
   854  		"west": map[string]string{"volume": "http://cinder.testing/v1"},
   855  	}}
   856  	url, err := openstack.GetVolumeEndpointURL(client, "west")
   857  	c.Assert(err, jc.ErrorIsNil)
   858  	c.Assert(url.String(), gc.Equals, "http://cinder.testing/v1")
   859  }
   860  
   861  func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointVolumeV2(c *gc.C) {
   862  	client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{
   863  		"west": map[string]string{"volumev2": "http://cinder.testing/v2"},
   864  	}}
   865  	url, err := openstack.GetVolumeEndpointURL(client, "west")
   866  	c.Assert(err, jc.ErrorIsNil)
   867  	c.Assert(url.String(), gc.Equals, "http://cinder.testing/v2")
   868  }
   869  
   870  func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointPreferV2(c *gc.C) {
   871  	client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{
   872  		"south": map[string]string{
   873  			"volume":   "http://cinder.testing/v1",
   874  			"volumev2": "http://cinder.testing/v2",
   875  		},
   876  	}}
   877  	url, err := openstack.GetVolumeEndpointURL(client, "south")
   878  	c.Assert(err, jc.ErrorIsNil)
   879  	c.Assert(url.String(), gc.Equals, "http://cinder.testing/v2")
   880  }
   881  
   882  func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointMissing(c *gc.C) {
   883  	client := &testEndpointResolver{}
   884  	url, err := openstack.GetVolumeEndpointURL(client, "east")
   885  	c.Assert(err, gc.ErrorMatches, `endpoint "volume" in region "east" not found`)
   886  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   887  	c.Assert(url, gc.IsNil)
   888  }
   889  
   890  func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointBadURL(c *gc.C) {
   891  	client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{
   892  		"north": map[string]string{"volumev2": "some %4"},
   893  	}}
   894  	url, err := openstack.GetVolumeEndpointURL(client, "north")
   895  	c.Assert(err, gc.ErrorMatches, `parse some %4: .*`)
   896  	c.Assert(url, gc.IsNil)
   897  }