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