github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/storageprovisioner/storageprovisioner_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storageprovisioner_test
     5  
     6  import (
     7  	"errors"
     8  	"time"
     9  
    10  	"github.com/juju/names"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/environs/config"
    16  	"github.com/juju/juju/instance"
    17  	"github.com/juju/juju/storage"
    18  	"github.com/juju/juju/storage/provider/registry"
    19  	coretesting "github.com/juju/juju/testing"
    20  	"github.com/juju/juju/worker"
    21  	"github.com/juju/juju/worker/storageprovisioner"
    22  )
    23  
    24  type storageProvisionerSuite struct {
    25  	coretesting.BaseSuite
    26  	provider                *dummyProvider
    27  	managedFilesystemSource *mockManagedFilesystemSource
    28  }
    29  
    30  var _ = gc.Suite(&storageProvisionerSuite{})
    31  
    32  func (s *storageProvisionerSuite) SetUpTest(c *gc.C) {
    33  	s.BaseSuite.SetUpTest(c)
    34  	s.provider = &dummyProvider{dynamic: true}
    35  	registry.RegisterProvider("dummy", s.provider)
    36  	s.AddCleanup(func(*gc.C) {
    37  		registry.RegisterProvider("dummy", nil)
    38  	})
    39  
    40  	s.managedFilesystemSource = nil
    41  	s.PatchValue(
    42  		storageprovisioner.NewManagedFilesystemSource,
    43  		func(
    44  			blockDevices map[names.VolumeTag]storage.BlockDevice,
    45  			filesystems map[names.FilesystemTag]storage.Filesystem,
    46  		) storage.FilesystemSource {
    47  			s.managedFilesystemSource = &mockManagedFilesystemSource{
    48  				blockDevices: blockDevices,
    49  				filesystems:  filesystems,
    50  			}
    51  			return s.managedFilesystemSource
    52  		},
    53  	)
    54  }
    55  
    56  func (s *storageProvisionerSuite) TestStartStop(c *gc.C) {
    57  	worker := storageprovisioner.NewStorageProvisioner(
    58  		coretesting.EnvironmentTag,
    59  		"dir",
    60  		newMockVolumeAccessor(),
    61  		newMockFilesystemAccessor(),
    62  		&mockLifecycleManager{},
    63  		newMockEnvironAccessor(c),
    64  		newMockMachineAccessor(c),
    65  	)
    66  	worker.Kill()
    67  	c.Assert(worker.Wait(), gc.IsNil)
    68  }
    69  
    70  func (s *storageProvisionerSuite) TestVolumeAdded(c *gc.C) {
    71  	expectedVolumes := []params.Volume{{
    72  		VolumeTag: "volume-1",
    73  		Info: params.VolumeInfo{
    74  			VolumeId:   "id-1",
    75  			HardwareId: "serial-1",
    76  			Size:       1024,
    77  			Persistent: true,
    78  		},
    79  	}, {
    80  		VolumeTag: "volume-2",
    81  		Info: params.VolumeInfo{
    82  			VolumeId:   "id-2",
    83  			HardwareId: "serial-2",
    84  			Size:       1024,
    85  		},
    86  	}}
    87  	expectedVolumeAttachments := []params.VolumeAttachment{{
    88  		VolumeTag:  "volume-1",
    89  		MachineTag: "machine-1",
    90  		Info: params.VolumeAttachmentInfo{
    91  			DeviceName: "/dev/sda1",
    92  			ReadOnly:   true,
    93  		},
    94  	}, {
    95  		VolumeTag:  "volume-2",
    96  		MachineTag: "machine-1",
    97  		Info: params.VolumeAttachmentInfo{
    98  			DeviceName: "/dev/sda2",
    99  		},
   100  	}}
   101  
   102  	volumeInfoSet := make(chan interface{})
   103  	volumeAccessor := newMockVolumeAccessor()
   104  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   105  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   106  		defer close(volumeInfoSet)
   107  		c.Assert(volumes, jc.SameContents, expectedVolumes)
   108  		return nil, nil
   109  	}
   110  
   111  	volumeAttachmentInfoSet := make(chan interface{})
   112  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   113  		defer close(volumeAttachmentInfoSet)
   114  		c.Assert(volumeAttachments, jc.SameContents, expectedVolumeAttachments)
   115  		return nil, nil
   116  	}
   117  
   118  	args := &workerArgs{volumes: volumeAccessor}
   119  	worker := newStorageProvisioner(c, args)
   120  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   121  	defer worker.Kill()
   122  
   123  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   124  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   125  	}, {
   126  		MachineTag: "machine-1", AttachmentTag: "volume-2",
   127  	}}
   128  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set")
   129  
   130  	// The worker should create volumes according to ids "1" and "2".
   131  	volumeAccessor.volumesWatcher.changes <- []string{"1", "2"}
   132  	// ... but not until the environment config is available.
   133  	assertNoEvent(c, volumeInfoSet, "volume info set")
   134  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   135  	args.environ.watcher.changes <- struct{}{}
   136  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   137  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   138  }
   139  
   140  func (s *storageProvisionerSuite) TestFilesystemAdded(c *gc.C) {
   141  	expectedFilesystems := []params.Filesystem{{
   142  		FilesystemTag: "filesystem-1",
   143  		Info: params.FilesystemInfo{
   144  			FilesystemId: "id-1",
   145  			Size:         1024,
   146  		},
   147  	}, {
   148  		FilesystemTag: "filesystem-2",
   149  		Info: params.FilesystemInfo{
   150  			FilesystemId: "id-2",
   151  			Size:         1024,
   152  		},
   153  	}}
   154  
   155  	filesystemInfoSet := make(chan interface{})
   156  	filesystemAccessor := newMockFilesystemAccessor()
   157  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   158  		defer close(filesystemInfoSet)
   159  		c.Assert(filesystems, jc.SameContents, expectedFilesystems)
   160  		return nil, nil
   161  	}
   162  
   163  	args := &workerArgs{filesystems: filesystemAccessor}
   164  	worker := newStorageProvisioner(c, args)
   165  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   166  	defer worker.Kill()
   167  
   168  	// The worker should create filesystems according to ids "1" and "2".
   169  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1", "2"}
   170  	// ... but not until the environment config is available.
   171  	assertNoEvent(c, filesystemInfoSet, "filesystem info set")
   172  	args.environ.watcher.changes <- struct{}{}
   173  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
   174  }
   175  
   176  func (s *storageProvisionerSuite) TestVolumeNeedsInstance(c *gc.C) {
   177  	volumeInfoSet := make(chan interface{})
   178  	volumeAccessor := newMockVolumeAccessor()
   179  	volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) {
   180  		defer close(volumeInfoSet)
   181  		return nil, nil
   182  	}
   183  	volumeAccessor.setVolumeAttachmentInfo = func([]params.VolumeAttachment) ([]params.ErrorResult, error) {
   184  		return nil, nil
   185  	}
   186  
   187  	args := &workerArgs{volumes: volumeAccessor}
   188  	worker := newStorageProvisioner(c, args)
   189  	defer worker.Wait()
   190  	defer worker.Kill()
   191  
   192  	volumeAccessor.volumesWatcher.changes <- []string{needsInstanceVolumeId}
   193  	args.environ.watcher.changes <- struct{}{}
   194  	assertNoEvent(c, volumeInfoSet, "volume info set")
   195  	args.machines.instanceIds[names.NewMachineTag("1")] = "inst-id"
   196  	args.machines.watcher.changes <- struct{}{}
   197  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   198  }
   199  
   200  func (s *storageProvisionerSuite) TestVolumeNonDynamic(c *gc.C) {
   201  	volumeInfoSet := make(chan interface{})
   202  	volumeAccessor := newMockVolumeAccessor()
   203  	volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) {
   204  		defer close(volumeInfoSet)
   205  		return nil, nil
   206  	}
   207  
   208  	args := &workerArgs{volumes: volumeAccessor}
   209  	worker := newStorageProvisioner(c, args)
   210  	defer worker.Wait()
   211  	defer worker.Kill()
   212  
   213  	// Volumes for non-dynamic providers should not be created.
   214  	s.provider.dynamic = false
   215  	args.environ.watcher.changes <- struct{}{}
   216  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   217  	assertNoEvent(c, volumeInfoSet, "volume info set")
   218  }
   219  
   220  func (s *storageProvisionerSuite) TestVolumeAttachmentAdded(c *gc.C) {
   221  	// We should get two volume attachments:
   222  	//   - volume-1 to machine-1, because the volume and
   223  	//     machine are provisioned, but the attachment is not.
   224  	//   - volume-1 to machine-0, because the volume,
   225  	//     machine, and attachment are provisioned, but
   226  	//     in a previous session, so a reattachment is
   227  	//     requested.
   228  	expectedVolumeAttachments := []params.VolumeAttachment{{
   229  		VolumeTag:  "volume-1",
   230  		MachineTag: "machine-1",
   231  		Info: params.VolumeAttachmentInfo{
   232  			DeviceName: "/dev/sda1",
   233  			ReadOnly:   true,
   234  		},
   235  	}, {
   236  		VolumeTag:  "volume-1",
   237  		MachineTag: "machine-0",
   238  		Info: params.VolumeAttachmentInfo{
   239  			DeviceName: "/dev/sda1",
   240  			ReadOnly:   true,
   241  		},
   242  	}}
   243  
   244  	var allVolumeAttachments []params.VolumeAttachment
   245  	volumeAttachmentInfoSet := make(chan interface{})
   246  	volumeAccessor := newMockVolumeAccessor()
   247  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   248  		allVolumeAttachments = append(allVolumeAttachments, volumeAttachments...)
   249  		volumeAttachmentInfoSet <- nil
   250  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   251  	}
   252  
   253  	// volume-1, machine-0, and machine-1 are provisioned.
   254  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
   255  		VolumeTag: "volume-1",
   256  		Info: params.VolumeInfo{
   257  			VolumeId: "vol-123",
   258  		},
   259  	}
   260  	volumeAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0")
   261  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   262  
   263  	// machine-0/volume-1 attachment is already created.
   264  	// We should see a reattachment.
   265  	alreadyAttached := params.MachineStorageId{
   266  		MachineTag:    "machine-0",
   267  		AttachmentTag: "volume-1",
   268  	}
   269  	volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{
   270  		MachineTag: "machine-0",
   271  		VolumeTag:  "volume-1",
   272  	}
   273  
   274  	args := &workerArgs{volumes: volumeAccessor}
   275  	worker := newStorageProvisioner(c, args)
   276  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   277  	defer worker.Kill()
   278  
   279  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   280  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   281  	}, {
   282  		MachineTag: "machine-1", AttachmentTag: "volume-2",
   283  	}, {
   284  		MachineTag: "machine-2", AttachmentTag: "volume-1",
   285  	}, {
   286  		MachineTag: "machine-0", AttachmentTag: "volume-1",
   287  	}}
   288  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   289  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   290  	args.environ.watcher.changes <- struct{}{}
   291  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   292  	c.Assert(allVolumeAttachments, jc.SameContents, expectedVolumeAttachments)
   293  
   294  	// Reattachment should only happen once per session.
   295  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached}
   296  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   297  }
   298  
   299  func (s *storageProvisionerSuite) TestFilesystemAttachmentAdded(c *gc.C) {
   300  	// We should only get a single filesystem attachment, because it is the
   301  	// only combination where both machine and filesystem are already
   302  	// provisioned, and the attachmenti s not.
   303  	// We should get two filesystem attachments:
   304  	//   - filesystem-1 to machine-1, because the filesystem and
   305  	//     machine are provisioned, but the attachment is not.
   306  	//   - filesystem-1 to machine-0, because the filesystem,
   307  	//     machine, and attachment are provisioned, but in a
   308  	//     previous session, so a reattachment is requested.
   309  	expectedFilesystemAttachments := []params.FilesystemAttachment{{
   310  		FilesystemTag: "filesystem-1",
   311  		MachineTag:    "machine-1",
   312  		Info: params.FilesystemAttachmentInfo{
   313  			MountPoint: "/srv/fs-123",
   314  		},
   315  	}, {
   316  		FilesystemTag: "filesystem-1",
   317  		MachineTag:    "machine-0",
   318  		Info: params.FilesystemAttachmentInfo{
   319  			MountPoint: "/srv/fs-123",
   320  		},
   321  	}}
   322  
   323  	var allFilesystemAttachments []params.FilesystemAttachment
   324  	filesystemAttachmentInfoSet := make(chan interface{})
   325  	filesystemAccessor := newMockFilesystemAccessor()
   326  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
   327  		allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...)
   328  		filesystemAttachmentInfoSet <- nil
   329  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
   330  	}
   331  
   332  	// filesystem-1 and machine-1 are provisioned.
   333  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{
   334  		FilesystemTag: "filesystem-1",
   335  		Info: params.FilesystemInfo{
   336  			FilesystemId: "fs-123",
   337  		},
   338  	}
   339  	filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0")
   340  	filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   341  
   342  	// machine-0/filesystem-1 attachment is already created.
   343  	// We should see a reattachment.
   344  	alreadyAttached := params.MachineStorageId{
   345  		MachineTag:    "machine-0",
   346  		AttachmentTag: "filesystem-1",
   347  	}
   348  	filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{
   349  		MachineTag:    "machine-0",
   350  		FilesystemTag: "filesystem-1",
   351  	}
   352  
   353  	args := &workerArgs{filesystems: filesystemAccessor}
   354  	worker := newStorageProvisioner(c, args)
   355  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   356  	defer worker.Kill()
   357  
   358  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   359  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   360  	}, {
   361  		MachineTag: "machine-1", AttachmentTag: "filesystem-2",
   362  	}, {
   363  		MachineTag: "machine-2", AttachmentTag: "filesystem-1",
   364  	}, {
   365  		MachineTag: "machine-0", AttachmentTag: "filesystem-1",
   366  	}}
   367  	// ... but not until the environment config is available.
   368  	assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set")
   369  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   370  	args.environ.watcher.changes <- struct{}{}
   371  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
   372  	c.Assert(allFilesystemAttachments, jc.SameContents, expectedFilesystemAttachments)
   373  
   374  	// Reattachment should only happen once per session.
   375  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached}
   376  	assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set")
   377  }
   378  
   379  func (s *storageProvisionerSuite) TestCreateVolumeBackedFilesystem(c *gc.C) {
   380  	filesystemInfoSet := make(chan interface{})
   381  	filesystemAccessor := newMockFilesystemAccessor()
   382  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   383  		filesystemInfoSet <- filesystems
   384  		return nil, nil
   385  	}
   386  
   387  	args := &workerArgs{
   388  		scope:       names.NewMachineTag("0"),
   389  		filesystems: filesystemAccessor,
   390  	}
   391  	worker := newStorageProvisioner(c, args)
   392  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   393  	defer worker.Kill()
   394  
   395  	args.volumes.blockDevices[params.MachineStorageId{
   396  		MachineTag:    "machine-0",
   397  		AttachmentTag: "volume-0-0",
   398  	}] = storage.BlockDevice{
   399  		DeviceName: "xvdf1",
   400  		Size:       123,
   401  	}
   402  	filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0", "0/1"}
   403  	assertNoEvent(c, filesystemInfoSet, "filesystem info set")
   404  	args.environ.watcher.changes <- struct{}{}
   405  
   406  	// Only the block device for volume 0/0 is attached at the moment,
   407  	// so only the corresponding filesystem will be created.
   408  	filesystemInfo := waitChannel(
   409  		c, filesystemInfoSet,
   410  		"waiting for filesystem info to be set",
   411  	).([]params.Filesystem)
   412  	c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{
   413  		FilesystemTag: "filesystem-0-0",
   414  		Info: params.FilesystemInfo{
   415  			FilesystemId: "xvdf1",
   416  			Size:         123,
   417  		},
   418  	}})
   419  
   420  	// If we now attach the block device for volume 0/1 and trigger the
   421  	// notification, then the storage provisioner will wake up and create
   422  	// the filesystem.
   423  	args.volumes.blockDevices[params.MachineStorageId{
   424  		MachineTag:    "machine-0",
   425  		AttachmentTag: "volume-0-1",
   426  	}] = storage.BlockDevice{
   427  		DeviceName: "xvdf2",
   428  		Size:       246,
   429  	}
   430  	args.volumes.blockDevicesWatcher.changes <- struct{}{}
   431  	filesystemInfo = waitChannel(
   432  		c, filesystemInfoSet,
   433  		"waiting for filesystem info to be set",
   434  	).([]params.Filesystem)
   435  	c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{
   436  		FilesystemTag: "filesystem-0-1",
   437  		Info: params.FilesystemInfo{
   438  			FilesystemId: "xvdf2",
   439  			Size:         246,
   440  		},
   441  	}})
   442  }
   443  
   444  func (s *storageProvisionerSuite) TestAttachVolumeBackedFilesystem(c *gc.C) {
   445  	infoSet := make(chan interface{})
   446  	filesystemAccessor := newMockFilesystemAccessor()
   447  	filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
   448  		infoSet <- attachments
   449  		return nil, nil
   450  	}
   451  
   452  	args := &workerArgs{
   453  		scope:       names.NewMachineTag("0"),
   454  		filesystems: filesystemAccessor,
   455  	}
   456  	worker := newStorageProvisioner(c, args)
   457  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   458  	defer worker.Kill()
   459  
   460  	filesystemAccessor.provisionedFilesystems["filesystem-0-0"] = params.Filesystem{
   461  		FilesystemTag: "filesystem-0-0",
   462  		VolumeTag:     "volume-0-0",
   463  		Info: params.FilesystemInfo{
   464  			FilesystemId: "whatever",
   465  			Size:         123,
   466  		},
   467  	}
   468  	filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0")
   469  
   470  	args.volumes.blockDevices[params.MachineStorageId{
   471  		MachineTag:    "machine-0",
   472  		AttachmentTag: "volume-0-0",
   473  	}] = storage.BlockDevice{
   474  		DeviceName: "xvdf1",
   475  		Size:       123,
   476  	}
   477  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   478  		MachineTag:    "machine-0",
   479  		AttachmentTag: "filesystem-0-0",
   480  	}}
   481  	assertNoEvent(c, infoSet, "filesystem attachment info set")
   482  	args.environ.watcher.changes <- struct{}{}
   483  	filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0"}
   484  
   485  	info := waitChannel(
   486  		c, infoSet, "waiting for filesystem attachment info to be set",
   487  	).([]params.FilesystemAttachment)
   488  	c.Assert(info, jc.DeepEquals, []params.FilesystemAttachment{{
   489  		FilesystemTag: "filesystem-0-0",
   490  		MachineTag:    "machine-0",
   491  		Info: params.FilesystemAttachmentInfo{
   492  			MountPoint: "/mnt/xvdf1",
   493  			ReadOnly:   true,
   494  		},
   495  	}})
   496  }
   497  
   498  func (s *storageProvisionerSuite) TestUpdateEnvironConfig(c *gc.C) {
   499  	volumeAccessor := newMockVolumeAccessor()
   500  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   501  	s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) {
   502  		c.Assert(envConfig, gc.NotNil)
   503  		c.Assert(sourceConfig, gc.NotNil)
   504  		c.Assert(envConfig.AllAttrs()["foo"], gc.Equals, "bar")
   505  		return nil, errors.New("zinga")
   506  	}
   507  
   508  	args := &workerArgs{volumes: volumeAccessor}
   509  	worker := newStorageProvisioner(c, args)
   510  	defer worker.Wait()
   511  	defer worker.Kill()
   512  
   513  	newConfig, err := args.environ.cfg.Apply(map[string]interface{}{"foo": "bar"})
   514  	c.Assert(err, jc.ErrorIsNil)
   515  
   516  	args.environ.watcher.changes <- struct{}{}
   517  	args.environ.setConfig(newConfig)
   518  	args.environ.watcher.changes <- struct{}{}
   519  	args.volumes.volumesWatcher.changes <- []string{"1", "2"}
   520  
   521  	err = worker.Wait()
   522  	c.Assert(err, gc.ErrorMatches, `processing pending volumes: creating volumes: getting volume source: getting storage source "dummy": zinga`)
   523  }
   524  
   525  func (s *storageProvisionerSuite) TestResourceTags(c *gc.C) {
   526  	volumeInfoSet := make(chan interface{})
   527  	volumeAccessor := newMockVolumeAccessor()
   528  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   529  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   530  		defer close(volumeInfoSet)
   531  		return nil, nil
   532  	}
   533  
   534  	filesystemInfoSet := make(chan interface{})
   535  	filesystemAccessor := newMockFilesystemAccessor()
   536  	filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   537  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   538  		defer close(filesystemInfoSet)
   539  		return nil, nil
   540  	}
   541  
   542  	var volumeSource dummyVolumeSource
   543  	s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) {
   544  		return &volumeSource, nil
   545  	}
   546  
   547  	var filesystemSource dummyFilesystemSource
   548  	s.provider.filesystemSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.FilesystemSource, error) {
   549  		return &filesystemSource, nil
   550  	}
   551  
   552  	args := &workerArgs{
   553  		volumes:     volumeAccessor,
   554  		filesystems: filesystemAccessor,
   555  	}
   556  	worker := newStorageProvisioner(c, args)
   557  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   558  	defer worker.Kill()
   559  
   560  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   561  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   562  	args.environ.watcher.changes <- struct{}{}
   563  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   564  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
   565  	c.Assert(volumeSource.createVolumesArgs, jc.DeepEquals, [][]storage.VolumeParams{{{
   566  		Tag:          names.NewVolumeTag("1"),
   567  		Size:         1024,
   568  		Provider:     "dummy",
   569  		Attributes:   map[string]interface{}{"persistent": true},
   570  		ResourceTags: map[string]string{"very": "fancy"},
   571  		Attachment: &storage.VolumeAttachmentParams{
   572  			Volume: names.NewVolumeTag("1"),
   573  			AttachmentParams: storage.AttachmentParams{
   574  				Machine:    names.NewMachineTag("1"),
   575  				Provider:   "dummy",
   576  				InstanceId: "already-provisioned-1",
   577  				ReadOnly:   true,
   578  			},
   579  		},
   580  	}}})
   581  	c.Assert(filesystemSource.createFilesystemsArgs, jc.DeepEquals, [][]storage.FilesystemParams{{{
   582  		Tag:          names.NewFilesystemTag("1"),
   583  		Size:         1024,
   584  		Provider:     "dummy",
   585  		ResourceTags: map[string]string{"very": "fancy"},
   586  	}}})
   587  }
   588  
   589  func (s *storageProvisionerSuite) TestSetVolumeInfoErrorStopsWorker(c *gc.C) {
   590  	volumeAccessor := newMockVolumeAccessor()
   591  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   592  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   593  		return []params.ErrorResult{{Error: &params.Error{Message: "message", Code: "code"}}}, nil
   594  	}
   595  
   596  	args := &workerArgs{volumes: volumeAccessor}
   597  	worker := newStorageProvisioner(c, args)
   598  	defer worker.Wait()
   599  	defer worker.Kill()
   600  
   601  	done := make(chan interface{})
   602  	go func() {
   603  		defer close(done)
   604  		err := worker.Wait()
   605  		c.Assert(err, gc.ErrorMatches, "processing pending volumes: publishing volume 1 to state: message")
   606  	}()
   607  
   608  	args.volumes.volumesWatcher.changes <- []string{"1"}
   609  	args.environ.watcher.changes <- struct{}{}
   610  	waitChannel(c, done, "waiting for worker to exit")
   611  }
   612  
   613  func (s *storageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) {
   614  	removed := make(chan interface{})
   615  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
   616  		defer close(removed)
   617  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
   618  			MachineTag:    "machine-0",
   619  			AttachmentTag: "volume-0",
   620  		}})
   621  		return make([]params.ErrorResult, len(ids)), nil
   622  	}
   623  
   624  	args := &workerArgs{
   625  		life: &mockLifecycleManager{removeAttachments: removeAttachments},
   626  	}
   627  	worker := newStorageProvisioner(c, args)
   628  	defer worker.Wait()
   629  	defer worker.Kill()
   630  
   631  	args.volumes.attachmentsWatcher.changes <- []params.MachineStorageId{{
   632  		MachineTag: "machine-0", AttachmentTag: "volume-0",
   633  	}}
   634  	args.environ.watcher.changes <- struct{}{}
   635  	waitChannel(c, removed, "waiting for attachment to be removed")
   636  }
   637  
   638  func (s *storageProvisionerSuite) TestDetachVolumes(c *gc.C) {
   639  	var attached bool
   640  	volumeAttachmentInfoSet := make(chan interface{})
   641  	volumeAccessor := newMockVolumeAccessor()
   642  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   643  		close(volumeAttachmentInfoSet)
   644  		attached = true
   645  		for _, a := range volumeAttachments {
   646  			id := params.MachineStorageId{
   647  				MachineTag:    a.MachineTag,
   648  				AttachmentTag: a.VolumeTag,
   649  			}
   650  			volumeAccessor.provisionedAttachments[id] = a
   651  		}
   652  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   653  	}
   654  
   655  	expectedAttachmentIds := []params.MachineStorageId{{
   656  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   657  	}}
   658  
   659  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
   660  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
   661  		life := params.Alive
   662  		if attached {
   663  			life = params.Dying
   664  		}
   665  		return []params.LifeResult{{Life: life}}, nil
   666  	}
   667  
   668  	detached := make(chan interface{})
   669  	s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) error {
   670  		c.Assert(args, gc.HasLen, 1)
   671  		c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag)
   672  		c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag)
   673  		defer close(detached)
   674  		return nil
   675  	}
   676  
   677  	removed := make(chan interface{})
   678  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
   679  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
   680  		close(removed)
   681  		return make([]params.ErrorResult, len(ids)), nil
   682  	}
   683  
   684  	// volume-1 and machine-1 are provisioned.
   685  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
   686  		VolumeTag: "volume-1",
   687  		Info: params.VolumeInfo{
   688  			VolumeId: "vol-123",
   689  		},
   690  	}
   691  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   692  
   693  	args := &workerArgs{
   694  		volumes: volumeAccessor,
   695  		life: &mockLifecycleManager{
   696  			attachmentLife:    attachmentLife,
   697  			removeAttachments: removeAttachments,
   698  		},
   699  	}
   700  	worker := newStorageProvisioner(c, args)
   701  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   702  	defer worker.Kill()
   703  
   704  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   705  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   706  	}}
   707  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   708  	args.environ.watcher.changes <- struct{}{}
   709  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   710  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   711  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   712  	}}
   713  	waitChannel(c, detached, "waiting for volume to be detached")
   714  	waitChannel(c, removed, "waiting for attachment to be removed")
   715  }
   716  
   717  func (s *storageProvisionerSuite) TestDetachFilesystemsUnattached(c *gc.C) {
   718  	removed := make(chan interface{})
   719  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
   720  		defer close(removed)
   721  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
   722  			MachineTag:    "machine-0",
   723  			AttachmentTag: "filesystem-0",
   724  		}})
   725  		return make([]params.ErrorResult, len(ids)), nil
   726  	}
   727  
   728  	args := &workerArgs{
   729  		life: &mockLifecycleManager{removeAttachments: removeAttachments},
   730  	}
   731  	worker := newStorageProvisioner(c, args)
   732  	defer worker.Wait()
   733  	defer worker.Kill()
   734  
   735  	args.filesystems.attachmentsWatcher.changes <- []params.MachineStorageId{{
   736  		MachineTag: "machine-0", AttachmentTag: "filesystem-0",
   737  	}}
   738  	args.environ.watcher.changes <- struct{}{}
   739  	waitChannel(c, removed, "waiting for attachment to be removed")
   740  }
   741  
   742  func (s *storageProvisionerSuite) TestDetachFilesystems(c *gc.C) {
   743  	var attached bool
   744  	filesystemAttachmentInfoSet := make(chan interface{})
   745  	filesystemAccessor := newMockFilesystemAccessor()
   746  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
   747  		close(filesystemAttachmentInfoSet)
   748  		attached = true
   749  		for _, a := range filesystemAttachments {
   750  			id := params.MachineStorageId{
   751  				MachineTag:    a.MachineTag,
   752  				AttachmentTag: a.FilesystemTag,
   753  			}
   754  			filesystemAccessor.provisionedAttachments[id] = a
   755  		}
   756  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
   757  	}
   758  
   759  	expectedAttachmentIds := []params.MachineStorageId{{
   760  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   761  	}}
   762  
   763  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
   764  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
   765  		life := params.Alive
   766  		if attached {
   767  			life = params.Dying
   768  		}
   769  		return []params.LifeResult{{Life: life}}, nil
   770  	}
   771  
   772  	detached := make(chan interface{})
   773  	s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) error {
   774  		c.Assert(args, gc.HasLen, 1)
   775  		c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag)
   776  		c.Assert(args[0].Filesystem.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag)
   777  		defer close(detached)
   778  		return nil
   779  	}
   780  
   781  	removed := make(chan interface{})
   782  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
   783  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
   784  		close(removed)
   785  		return make([]params.ErrorResult, len(ids)), nil
   786  	}
   787  
   788  	// filesystem-1 and machine-1 are provisioned.
   789  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{
   790  		FilesystemTag: "filesystem-1",
   791  		Info: params.FilesystemInfo{
   792  			FilesystemId: "fs-id",
   793  		},
   794  	}
   795  	filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   796  
   797  	args := &workerArgs{
   798  		filesystems: filesystemAccessor,
   799  		life: &mockLifecycleManager{
   800  			attachmentLife:    attachmentLife,
   801  			removeAttachments: removeAttachments,
   802  		},
   803  	}
   804  	worker := newStorageProvisioner(c, args)
   805  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   806  	defer worker.Kill()
   807  
   808  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   809  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   810  	}}
   811  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   812  	args.environ.watcher.changes <- struct{}{}
   813  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
   814  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   815  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   816  	}}
   817  	waitChannel(c, detached, "waiting for filesystem to be detached")
   818  	waitChannel(c, removed, "waiting for attachment to be removed")
   819  }
   820  
   821  func (s *storageProvisionerSuite) TestDestroyVolumes(c *gc.C) {
   822  	provisionedVolume := names.NewVolumeTag("1")
   823  	unprovisionedVolume := names.NewVolumeTag("2")
   824  
   825  	volumeAccessor := newMockVolumeAccessor()
   826  	volumeAccessor.provisionVolume(provisionedVolume)
   827  
   828  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
   829  		results := make([]params.LifeResult, len(tags))
   830  		for i := range results {
   831  			results[i].Life = params.Dead
   832  		}
   833  		return results, nil
   834  	}
   835  
   836  	destroyedChan := make(chan interface{}, 1)
   837  	s.provider.destroyVolumesFunc = func(volumeIds []string) []error {
   838  		destroyedChan <- volumeIds
   839  		return make([]error, len(volumeIds))
   840  	}
   841  
   842  	removedChan := make(chan interface{}, 1)
   843  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
   844  		removedChan <- tags
   845  		return make([]params.ErrorResult, len(tags)), nil
   846  	}
   847  
   848  	args := &workerArgs{
   849  		volumes: volumeAccessor,
   850  		life: &mockLifecycleManager{
   851  			life:   life,
   852  			remove: remove,
   853  		},
   854  	}
   855  	worker := newStorageProvisioner(c, args)
   856  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   857  	defer worker.Kill()
   858  
   859  	volumeAccessor.volumesWatcher.changes <- []string{
   860  		provisionedVolume.Id(),
   861  		unprovisionedVolume.Id(),
   862  	}
   863  	args.environ.watcher.changes <- struct{}{}
   864  
   865  	// Both volumes should be removed; the provisioned one
   866  	// should be deprovisioned first.
   867  
   868  	destroyed := waitChannel(c, destroyedChan, "waiting for volume to be deprovisioned")
   869  	assertNoEvent(c, destroyedChan, "volumes deprovisioned")
   870  	c.Assert(destroyed, jc.DeepEquals, []string{"vol-1"})
   871  
   872  	var removed []names.Tag
   873  	for len(removed) < 2 {
   874  		tags := waitChannel(c, removedChan, "waiting for volumes to be removed").([]names.Tag)
   875  		removed = append(removed, tags...)
   876  	}
   877  	c.Assert(removed, jc.SameContents, []names.Tag{provisionedVolume, unprovisionedVolume})
   878  	assertNoEvent(c, removedChan, "volumes removed")
   879  }
   880  
   881  func (s *storageProvisionerSuite) TestDestroyFilesystems(c *gc.C) {
   882  	provisionedFilesystem := names.NewFilesystemTag("1")
   883  	unprovisionedFilesystem := names.NewFilesystemTag("2")
   884  
   885  	filesystemAccessor := newMockFilesystemAccessor()
   886  	filesystemAccessor.provisionFilesystem(provisionedFilesystem)
   887  
   888  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
   889  		results := make([]params.LifeResult, len(tags))
   890  		for i := range results {
   891  			results[i].Life = params.Dead
   892  		}
   893  		return results, nil
   894  	}
   895  
   896  	removedChan := make(chan interface{}, 1)
   897  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
   898  		removedChan <- tags
   899  		return make([]params.ErrorResult, len(tags)), nil
   900  	}
   901  
   902  	args := &workerArgs{
   903  		filesystems: filesystemAccessor,
   904  		life: &mockLifecycleManager{
   905  			life:   life,
   906  			remove: remove,
   907  		},
   908  	}
   909  	worker := newStorageProvisioner(c, args)
   910  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   911  	defer worker.Kill()
   912  
   913  	filesystemAccessor.filesystemsWatcher.changes <- []string{
   914  		provisionedFilesystem.Id(),
   915  		unprovisionedFilesystem.Id(),
   916  	}
   917  	args.environ.watcher.changes <- struct{}{}
   918  
   919  	// Both filesystems should be removed; the provisioned one
   920  	// *should* be deprovisioned first, but we don't currently
   921  	// have the ability to do so via the storage provider API.
   922  
   923  	var removed []names.Tag
   924  	for len(removed) < 2 {
   925  		tags := waitChannel(c, removedChan, "waiting for filesystems to be removed").([]names.Tag)
   926  		removed = append(removed, tags...)
   927  	}
   928  	c.Assert(removed, jc.SameContents, []names.Tag{provisionedFilesystem, unprovisionedFilesystem})
   929  	assertNoEvent(c, removedChan, "filesystems removed")
   930  }
   931  
   932  func newStorageProvisioner(c *gc.C, args *workerArgs) worker.Worker {
   933  	if args == nil {
   934  		args = &workerArgs{}
   935  	}
   936  	if args.scope == nil {
   937  		args.scope = coretesting.EnvironmentTag
   938  	}
   939  	if args.volumes == nil {
   940  		args.volumes = newMockVolumeAccessor()
   941  	}
   942  	if args.filesystems == nil {
   943  		args.filesystems = newMockFilesystemAccessor()
   944  	}
   945  	if args.life == nil {
   946  		args.life = &mockLifecycleManager{}
   947  	}
   948  	if args.environ == nil {
   949  		args.environ = newMockEnvironAccessor(c)
   950  	}
   951  	if args.machines == nil {
   952  		args.machines = newMockMachineAccessor(c)
   953  	}
   954  	return storageprovisioner.NewStorageProvisioner(
   955  		args.scope,
   956  		"storage-dir",
   957  		args.volumes,
   958  		args.filesystems,
   959  		args.life,
   960  		args.environ,
   961  		args.machines,
   962  	)
   963  }
   964  
   965  type workerArgs struct {
   966  	scope       names.Tag
   967  	volumes     *mockVolumeAccessor
   968  	filesystems *mockFilesystemAccessor
   969  	life        *mockLifecycleManager
   970  	environ     *mockEnvironAccessor
   971  	machines    *mockMachineAccessor
   972  }
   973  
   974  func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} {
   975  	select {
   976  	case v := <-ch:
   977  		return v
   978  	case <-time.After(coretesting.LongWait):
   979  		c.Fatalf("timed out " + activity)
   980  		panic("unreachable")
   981  	}
   982  }
   983  
   984  func assertNoEvent(c *gc.C, ch <-chan interface{}, event string) {
   985  	select {
   986  	case <-ch:
   987  		c.Fatalf("unexpected " + event)
   988  	case <-time.After(coretesting.ShortWait):
   989  	}
   990  }
   991  
   992  // TODO(wallyworld) - test destroying volumes when done