github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/openstack/cinder_test.go (about)

     1  package openstack_test
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names"
     9  	gitjujutesting "github.com/juju/testing"
    10  	jc "github.com/juju/testing/checkers"
    11  	"github.com/juju/utils"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/goose.v1/cinder"
    14  	"gopkg.in/goose.v1/nova"
    15  
    16  	"github.com/juju/juju/environs/tags"
    17  	"github.com/juju/juju/instance"
    18  	"github.com/juju/juju/provider/openstack"
    19  	"github.com/juju/juju/storage"
    20  	"github.com/juju/juju/testing"
    21  )
    22  
    23  const (
    24  	mockVolId    = "0"
    25  	mockVolSize  = 1024 * 2
    26  	mockVolName  = "123"
    27  	mockServerId = "mock-server-id"
    28  	mockVolJson  = `{"volume":{"id": "` + mockVolId + `", "size":1,"name":"` + mockVolName + `"}}`
    29  )
    30  
    31  var (
    32  	mockVolumeTag  = names.NewVolumeTag(mockVolName)
    33  	mockMachineTag = names.NewMachineTag("456")
    34  )
    35  
    36  var _ = gc.Suite(&cinderVolumeSourceSuite{})
    37  
    38  type cinderVolumeSourceSuite struct {
    39  	testing.BaseSuite
    40  }
    41  
    42  func init() {
    43  	// Override attempt strategy to speed things up.
    44  	openstack.CinderAttempt.Delay = 0
    45  }
    46  
    47  func (s *cinderVolumeSourceSuite) TestAttachVolumes(c *gc.C) {
    48  	mockAdapter := &mockAdapter{
    49  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
    50  			c.Check(volId, gc.Equals, mockVolId)
    51  			c.Check(serverId, gc.Equals, mockServerId)
    52  			return &nova.VolumeAttachment{
    53  				Id:       volId,
    54  				VolumeId: volId,
    55  				ServerId: serverId,
    56  				Device:   "/dev/sda",
    57  			}, nil
    58  		},
    59  	}
    60  
    61  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
    62  	results, err := volSource.AttachVolumes([]storage.VolumeAttachmentParams{{
    63  		Volume:   mockVolumeTag,
    64  		VolumeId: mockVolId,
    65  		AttachmentParams: storage.AttachmentParams{
    66  			Provider:   openstack.CinderProviderType,
    67  			Machine:    mockMachineTag,
    68  			InstanceId: instance.Id(mockServerId),
    69  		}},
    70  	})
    71  	c.Assert(err, jc.ErrorIsNil)
    72  	c.Check(results, jc.DeepEquals, []storage.AttachVolumesResult{{
    73  		VolumeAttachment: &storage.VolumeAttachment{
    74  			mockVolumeTag,
    75  			mockMachineTag,
    76  			storage.VolumeAttachmentInfo{
    77  				DeviceName: "sda",
    78  			},
    79  		},
    80  	}})
    81  }
    82  
    83  func (s *cinderVolumeSourceSuite) TestCreateVolume(c *gc.C) {
    84  	const (
    85  		requestedSize = 2 * 1024
    86  		providedSize  = 3 * 1024
    87  	)
    88  
    89  	s.PatchValue(openstack.CinderAttempt, utils.AttemptStrategy{Min: 3})
    90  
    91  	var getVolumeCalls int
    92  	mockAdapter := &mockAdapter{
    93  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
    94  			c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{
    95  				Size: requestedSize / 1024,
    96  				Name: "juju-testenv-volume-123",
    97  			})
    98  			return &cinder.Volume{
    99  				ID: mockVolId,
   100  			}, nil
   101  		},
   102  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   103  			var status string
   104  			getVolumeCalls++
   105  			if getVolumeCalls > 1 {
   106  				status = "available"
   107  			}
   108  			return &cinder.Volume{
   109  				ID:     volumeId,
   110  				Size:   providedSize / 1024,
   111  				Status: status,
   112  			}, nil
   113  		},
   114  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
   115  			c.Check(volId, gc.Equals, mockVolId)
   116  			c.Check(serverId, gc.Equals, mockServerId)
   117  			return &nova.VolumeAttachment{
   118  				Id:       volId,
   119  				VolumeId: volId,
   120  				ServerId: serverId,
   121  				Device:   "/dev/sda",
   122  			}, nil
   123  		},
   124  	}
   125  
   126  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   127  	results, err := volSource.CreateVolumes([]storage.VolumeParams{{
   128  		Provider: openstack.CinderProviderType,
   129  		Tag:      mockVolumeTag,
   130  		Size:     requestedSize,
   131  		Attachment: &storage.VolumeAttachmentParams{
   132  			AttachmentParams: storage.AttachmentParams{
   133  				Provider:   openstack.CinderProviderType,
   134  				Machine:    mockMachineTag,
   135  				InstanceId: instance.Id(mockServerId),
   136  			},
   137  		},
   138  	}})
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	c.Assert(results, gc.HasLen, 1)
   141  	c.Assert(results[0].Error, jc.ErrorIsNil)
   142  
   143  	c.Check(results[0].Volume, jc.DeepEquals, &storage.Volume{
   144  		mockVolumeTag,
   145  		storage.VolumeInfo{
   146  			VolumeId:   mockVolId,
   147  			Size:       providedSize,
   148  			Persistent: true,
   149  		},
   150  	})
   151  
   152  	// should have been 2 calls to GetVolume: twice initially
   153  	// to wait until the volume became available.
   154  	c.Check(getVolumeCalls, gc.Equals, 2)
   155  }
   156  
   157  func (s *cinderVolumeSourceSuite) TestResourceTags(c *gc.C) {
   158  	var created bool
   159  	mockAdapter := &mockAdapter{
   160  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   161  			created = true
   162  			c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{
   163  				Size: 1,
   164  				Name: "juju-testenv-volume-123",
   165  				Metadata: map[string]string{
   166  					"ResourceTag1": "Value1",
   167  					"ResourceTag2": "Value2",
   168  				},
   169  			})
   170  			return &cinder.Volume{ID: mockVolId}, nil
   171  		},
   172  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   173  			return &cinder.Volume{
   174  				ID:     volumeId,
   175  				Size:   1,
   176  				Status: "available",
   177  			}, nil
   178  		},
   179  		attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) {
   180  			return &nova.VolumeAttachment{
   181  				Id:       volId,
   182  				VolumeId: volId,
   183  				ServerId: serverId,
   184  				Device:   "/dev/sda",
   185  			}, nil
   186  		},
   187  	}
   188  
   189  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   190  	_, err := volSource.CreateVolumes([]storage.VolumeParams{{
   191  		Provider: openstack.CinderProviderType,
   192  		Tag:      mockVolumeTag,
   193  		Size:     1024,
   194  		ResourceTags: map[string]string{
   195  			"ResourceTag1": "Value1",
   196  			"ResourceTag2": "Value2",
   197  		},
   198  		Attachment: &storage.VolumeAttachmentParams{
   199  			AttachmentParams: storage.AttachmentParams{
   200  				Provider:   openstack.CinderProviderType,
   201  				Machine:    mockMachineTag,
   202  				InstanceId: instance.Id(mockServerId),
   203  			},
   204  		},
   205  	}})
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	c.Assert(created, jc.IsTrue)
   208  }
   209  
   210  func (s *cinderVolumeSourceSuite) TestListVolumes(c *gc.C) {
   211  	mockAdapter := &mockAdapter{
   212  		getVolumesDetail: func() ([]cinder.Volume, error) {
   213  			return []cinder.Volume{{
   214  				ID: "volume-1",
   215  			}, {
   216  				ID: "volume-2",
   217  				Metadata: map[string]string{
   218  					tags.JujuEnv: "something-else",
   219  				},
   220  			}, {
   221  				ID: "volume-3",
   222  				Metadata: map[string]string{
   223  					tags.JujuEnv: testing.EnvironmentTag.Id(),
   224  				},
   225  			}}, nil
   226  		},
   227  	}
   228  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   229  	volumeIds, err := volSource.ListVolumes()
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	c.Check(volumeIds, jc.DeepEquals, []string{"volume-3"})
   232  }
   233  
   234  func (s *cinderVolumeSourceSuite) TestDescribeVolumes(c *gc.C) {
   235  	mockAdapter := &mockAdapter{
   236  		getVolumesDetail: func() ([]cinder.Volume, error) {
   237  			return []cinder.Volume{{
   238  				ID:   mockVolId,
   239  				Size: mockVolSize / 1024,
   240  			}}, nil
   241  		},
   242  	}
   243  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   244  	volumes, err := volSource.DescribeVolumes([]string{mockVolId})
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	c.Check(volumes, jc.DeepEquals, []storage.DescribeVolumesResult{{
   247  		VolumeInfo: &storage.VolumeInfo{
   248  			VolumeId:   mockVolId,
   249  			Size:       mockVolSize,
   250  			Persistent: true,
   251  		},
   252  	}})
   253  }
   254  
   255  func (s *cinderVolumeSourceSuite) TestDestroyVolumes(c *gc.C) {
   256  	mockAdapter := &mockAdapter{}
   257  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   258  	errs, err := volSource.DestroyVolumes([]string{mockVolId})
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	c.Assert(errs, jc.DeepEquals, []error{nil})
   261  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{
   262  		{"GetVolume", []interface{}{mockVolId}},
   263  		{"DeleteVolume", []interface{}{mockVolId}},
   264  	})
   265  }
   266  
   267  func (s *cinderVolumeSourceSuite) TestDestroyVolumesAttached(c *gc.C) {
   268  	statuses := []string{"in-use", "detaching", "available"}
   269  
   270  	mockAdapter := &mockAdapter{
   271  		getVolume: func(volId string) (*cinder.Volume, error) {
   272  			c.Assert(statuses, gc.Not(gc.HasLen), 0)
   273  			status := statuses[0]
   274  			statuses = statuses[1:]
   275  			return &cinder.Volume{
   276  				ID:     volId,
   277  				Status: status,
   278  			}, nil
   279  		},
   280  	}
   281  
   282  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   283  	errs, err := volSource.DestroyVolumes([]string{mockVolId})
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Assert(errs, gc.HasLen, 1)
   286  	c.Assert(errs[0], jc.ErrorIsNil)
   287  	c.Assert(statuses, gc.HasLen, 0)
   288  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{
   289  		"GetVolume", []interface{}{mockVolId},
   290  	}, {
   291  		"GetVolume", []interface{}{mockVolId},
   292  	}, {
   293  		"GetVolume", []interface{}{mockVolId},
   294  	}, {
   295  		"DeleteVolume", []interface{}{mockVolId},
   296  	}})
   297  }
   298  
   299  func (s *cinderVolumeSourceSuite) TestDetachVolumes(c *gc.C) {
   300  	const mockServerId2 = mockServerId + "2"
   301  
   302  	var numListCalls, numDetachCalls int
   303  	mockAdapter := &mockAdapter{
   304  		listVolumeAttachments: func(serverId string) ([]nova.VolumeAttachment, error) {
   305  			numListCalls++
   306  			if serverId == mockServerId2 {
   307  				// no attachments
   308  				return nil, nil
   309  			}
   310  			c.Check(serverId, gc.Equals, mockServerId)
   311  			return []nova.VolumeAttachment{{
   312  				Id:       mockVolId,
   313  				VolumeId: mockVolId,
   314  				ServerId: mockServerId,
   315  				Device:   "/dev/sda",
   316  			}}, nil
   317  		},
   318  		detachVolume: func(serverId, volId string) error {
   319  			numDetachCalls++
   320  			c.Check(serverId, gc.Equals, mockServerId)
   321  			c.Check(volId, gc.Equals, mockVolId)
   322  			return nil
   323  		},
   324  	}
   325  
   326  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   327  	errs, err := volSource.DetachVolumes([]storage.VolumeAttachmentParams{{
   328  		Volume:   names.NewVolumeTag("123"),
   329  		VolumeId: mockVolId,
   330  		AttachmentParams: storage.AttachmentParams{
   331  			Machine:    names.NewMachineTag("0"),
   332  			InstanceId: mockServerId,
   333  		},
   334  	}, {
   335  		Volume:   names.NewVolumeTag("42"),
   336  		VolumeId: "42",
   337  		AttachmentParams: storage.AttachmentParams{
   338  			Machine:    names.NewMachineTag("0"),
   339  			InstanceId: mockServerId2,
   340  		},
   341  	}})
   342  	c.Assert(err, jc.ErrorIsNil)
   343  	c.Assert(errs, jc.DeepEquals, []error{nil, nil})
   344  	// DetachVolume should only be called for existing attachments.
   345  	mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{
   346  		"ListVolumeAttachments", []interface{}{mockServerId},
   347  	}, {
   348  		"DetachVolume", []interface{}{mockServerId, mockVolId},
   349  	}, {
   350  		"ListVolumeAttachments", []interface{}{mockServerId2},
   351  	}})
   352  }
   353  
   354  func (s *cinderVolumeSourceSuite) TestCreateVolumeCleanupDestroys(c *gc.C) {
   355  	var numCreateCalls, numDestroyCalls, numGetCalls int
   356  	mockAdapter := &mockAdapter{
   357  		createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   358  			numCreateCalls++
   359  			if numCreateCalls == 3 {
   360  				return nil, errors.New("no volume for you")
   361  			}
   362  			return &cinder.Volume{
   363  				ID:     fmt.Sprint(numCreateCalls),
   364  				Status: "",
   365  			}, nil
   366  		},
   367  		deleteVolume: func(volId string) error {
   368  			numDestroyCalls++
   369  			c.Assert(volId, gc.Equals, "2")
   370  			return errors.New("destroy fails")
   371  		},
   372  		getVolume: func(volumeId string) (*cinder.Volume, error) {
   373  			numGetCalls++
   374  			if numGetCalls == 2 {
   375  				return nil, errors.New("no volume details for you")
   376  			}
   377  			return &cinder.Volume{
   378  				ID:     "4",
   379  				Size:   mockVolSize / 1024,
   380  				Status: "available",
   381  			}, nil
   382  		},
   383  	}
   384  
   385  	volSource := openstack.NewCinderVolumeSource(mockAdapter)
   386  	volumeParams := []storage.VolumeParams{{
   387  		Provider: openstack.CinderProviderType,
   388  		Tag:      names.NewVolumeTag("0"),
   389  		Size:     mockVolSize,
   390  		Attachment: &storage.VolumeAttachmentParams{
   391  			AttachmentParams: storage.AttachmentParams{
   392  				Provider:   openstack.CinderProviderType,
   393  				Machine:    mockMachineTag,
   394  				InstanceId: instance.Id(mockServerId),
   395  			},
   396  		},
   397  	}, {
   398  		Provider: openstack.CinderProviderType,
   399  		Tag:      names.NewVolumeTag("1"),
   400  		Size:     mockVolSize,
   401  		Attachment: &storage.VolumeAttachmentParams{
   402  			AttachmentParams: storage.AttachmentParams{
   403  				Provider:   openstack.CinderProviderType,
   404  				Machine:    mockMachineTag,
   405  				InstanceId: instance.Id(mockServerId),
   406  			},
   407  		},
   408  	}, {
   409  		Provider: openstack.CinderProviderType,
   410  		Tag:      names.NewVolumeTag("2"),
   411  		Size:     mockVolSize,
   412  		Attachment: &storage.VolumeAttachmentParams{
   413  			AttachmentParams: storage.AttachmentParams{
   414  				Provider:   openstack.CinderProviderType,
   415  				Machine:    mockMachineTag,
   416  				InstanceId: instance.Id(mockServerId),
   417  			},
   418  		},
   419  	}}
   420  	results, err := volSource.CreateVolumes(volumeParams)
   421  	c.Assert(err, jc.ErrorIsNil)
   422  	c.Assert(results, gc.HasLen, 3)
   423  	c.Assert(results[0].Error, jc.ErrorIsNil)
   424  	c.Assert(results[1].Error, gc.ErrorMatches, "waiting for volume to be provisioned: getting volume: no volume details for you")
   425  	c.Assert(results[2].Error, gc.ErrorMatches, "no volume for you")
   426  	c.Assert(numCreateCalls, gc.Equals, 3)
   427  	c.Assert(numGetCalls, gc.Equals, 2)
   428  	c.Assert(numDestroyCalls, gc.Equals, 1)
   429  }
   430  
   431  type mockAdapter struct {
   432  	gitjujutesting.Stub
   433  	getVolume             func(string) (*cinder.Volume, error)
   434  	getVolumesDetail      func() ([]cinder.Volume, error)
   435  	deleteVolume          func(string) error
   436  	createVolume          func(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error)
   437  	attachVolume          func(string, string, string) (*nova.VolumeAttachment, error)
   438  	volumeStatusNotifier  func(string, string, int, time.Duration) <-chan error
   439  	detachVolume          func(string, string) error
   440  	listVolumeAttachments func(string) ([]nova.VolumeAttachment, error)
   441  }
   442  
   443  func (ma *mockAdapter) GetVolume(volumeId string) (*cinder.Volume, error) {
   444  	ma.MethodCall(ma, "GetVolume", volumeId)
   445  	if ma.getVolume != nil {
   446  		return ma.getVolume(volumeId)
   447  	}
   448  	return &cinder.Volume{
   449  		ID:     volumeId,
   450  		Status: "available",
   451  	}, nil
   452  }
   453  
   454  func (ma *mockAdapter) GetVolumesDetail() ([]cinder.Volume, error) {
   455  	ma.MethodCall(ma, "GetVolumesDetail")
   456  	if ma.getVolumesDetail != nil {
   457  		return ma.getVolumesDetail()
   458  	}
   459  	return nil, nil
   460  }
   461  
   462  func (ma *mockAdapter) DeleteVolume(volId string) error {
   463  	ma.MethodCall(ma, "DeleteVolume", volId)
   464  	if ma.deleteVolume != nil {
   465  		return ma.deleteVolume(volId)
   466  	}
   467  	return nil
   468  }
   469  
   470  func (ma *mockAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) {
   471  	ma.MethodCall(ma, "CreateVolume", args)
   472  	if ma.createVolume != nil {
   473  		return ma.createVolume(args)
   474  	}
   475  	return nil, errors.NotImplementedf("CreateVolume")
   476  }
   477  
   478  func (ma *mockAdapter) AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error) {
   479  	ma.MethodCall(ma, "AttachVolume", serverId, volumeId, mountPoint)
   480  	if ma.attachVolume != nil {
   481  		return ma.attachVolume(serverId, volumeId, mountPoint)
   482  	}
   483  	return nil, errors.NotImplementedf("AttachVolume")
   484  }
   485  
   486  func (ma *mockAdapter) DetachVolume(serverId, attachmentId string) error {
   487  	ma.MethodCall(ma, "DetachVolume", serverId, attachmentId)
   488  	if ma.detachVolume != nil {
   489  		return ma.detachVolume(serverId, attachmentId)
   490  	}
   491  	return nil
   492  }
   493  
   494  func (ma *mockAdapter) ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error) {
   495  	ma.MethodCall(ma, "ListVolumeAttachments", serverId)
   496  	if ma.listVolumeAttachments != nil {
   497  		return ma.listVolumeAttachments(serverId)
   498  	}
   499  	return nil, nil
   500  }