github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	"github.com/juju/utils/clock"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/environs/config"
    17  	"github.com/juju/juju/instance"
    18  	"github.com/juju/juju/storage"
    19  	"github.com/juju/juju/storage/provider/registry"
    20  	coretesting "github.com/juju/juju/testing"
    21  	"github.com/juju/juju/worker"
    22  	"github.com/juju/juju/worker/storageprovisioner"
    23  )
    24  
    25  type storageProvisionerSuite struct {
    26  	coretesting.BaseSuite
    27  	provider                *dummyProvider
    28  	managedFilesystemSource *mockManagedFilesystemSource
    29  }
    30  
    31  var _ = gc.Suite(&storageProvisionerSuite{})
    32  
    33  func (s *storageProvisionerSuite) SetUpTest(c *gc.C) {
    34  	s.BaseSuite.SetUpTest(c)
    35  	s.provider = &dummyProvider{dynamic: true}
    36  	registry.RegisterProvider("dummy", s.provider)
    37  	s.AddCleanup(func(*gc.C) {
    38  		registry.RegisterProvider("dummy", nil)
    39  	})
    40  
    41  	s.managedFilesystemSource = nil
    42  	s.PatchValue(
    43  		storageprovisioner.NewManagedFilesystemSource,
    44  		func(
    45  			blockDevices map[names.VolumeTag]storage.BlockDevice,
    46  			filesystems map[names.FilesystemTag]storage.Filesystem,
    47  		) storage.FilesystemSource {
    48  			s.managedFilesystemSource = &mockManagedFilesystemSource{
    49  				blockDevices: blockDevices,
    50  				filesystems:  filesystems,
    51  			}
    52  			return s.managedFilesystemSource
    53  		},
    54  	)
    55  }
    56  
    57  func (s *storageProvisionerSuite) TestStartStop(c *gc.C) {
    58  	worker := storageprovisioner.NewStorageProvisioner(
    59  		coretesting.EnvironmentTag,
    60  		"dir",
    61  		newMockVolumeAccessor(),
    62  		newMockFilesystemAccessor(),
    63  		&mockLifecycleManager{},
    64  		newMockEnvironAccessor(c),
    65  		newMockMachineAccessor(c),
    66  		&mockStatusSetter{},
    67  		&mockClock{},
    68  	)
    69  	worker.Kill()
    70  	c.Assert(worker.Wait(), gc.IsNil)
    71  }
    72  
    73  func (s *storageProvisionerSuite) TestVolumeAdded(c *gc.C) {
    74  	expectedVolumes := []params.Volume{{
    75  		VolumeTag: "volume-1",
    76  		Info: params.VolumeInfo{
    77  			VolumeId:   "id-1",
    78  			HardwareId: "serial-1",
    79  			Size:       1024,
    80  			Persistent: true,
    81  		},
    82  	}, {
    83  		VolumeTag: "volume-2",
    84  		Info: params.VolumeInfo{
    85  			VolumeId:   "id-2",
    86  			HardwareId: "serial-2",
    87  			Size:       1024,
    88  		},
    89  	}}
    90  	expectedVolumeAttachments := []params.VolumeAttachment{{
    91  		VolumeTag:  "volume-1",
    92  		MachineTag: "machine-1",
    93  		Info: params.VolumeAttachmentInfo{
    94  			DeviceName: "/dev/sda1",
    95  			ReadOnly:   true,
    96  		},
    97  	}, {
    98  		VolumeTag:  "volume-2",
    99  		MachineTag: "machine-1",
   100  		Info: params.VolumeAttachmentInfo{
   101  			DeviceName: "/dev/sda2",
   102  		},
   103  	}}
   104  
   105  	volumeInfoSet := make(chan interface{})
   106  	volumeAccessor := newMockVolumeAccessor()
   107  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   108  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   109  		defer close(volumeInfoSet)
   110  		c.Assert(volumes, jc.SameContents, expectedVolumes)
   111  		return nil, nil
   112  	}
   113  
   114  	volumeAttachmentInfoSet := make(chan interface{})
   115  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   116  		defer close(volumeAttachmentInfoSet)
   117  		c.Assert(volumeAttachments, jc.SameContents, expectedVolumeAttachments)
   118  		return nil, nil
   119  	}
   120  
   121  	args := &workerArgs{volumes: volumeAccessor}
   122  	worker := newStorageProvisioner(c, args)
   123  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   124  	defer worker.Kill()
   125  
   126  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   127  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   128  	}, {
   129  		MachineTag: "machine-1", AttachmentTag: "volume-2",
   130  	}}
   131  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set")
   132  
   133  	// The worker should create volumes according to ids "1" and "2".
   134  	volumeAccessor.volumesWatcher.changes <- []string{"1", "2"}
   135  	// ... but not until the environment config is available.
   136  	assertNoEvent(c, volumeInfoSet, "volume info set")
   137  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   138  	args.environ.watcher.changes <- struct{}{}
   139  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   140  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   141  }
   142  
   143  func (s *storageProvisionerSuite) TestCreateVolumeCreatesAttachment(c *gc.C) {
   144  	volumeAccessor := newMockVolumeAccessor()
   145  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   146  
   147  	volumeAttachmentInfoSet := make(chan interface{})
   148  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   149  		defer close(volumeAttachmentInfoSet)
   150  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   151  	}
   152  
   153  	s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   154  		volumeAccessor.provisionedAttachments[params.MachineStorageId{
   155  			MachineTag:    args[0].Attachment.Machine.String(),
   156  			AttachmentTag: args[0].Attachment.Volume.String(),
   157  		}] = params.VolumeAttachment{
   158  			VolumeTag:  args[0].Attachment.Volume.String(),
   159  			MachineTag: args[0].Attachment.Machine.String(),
   160  		}
   161  		return []storage.CreateVolumesResult{{
   162  			Volume: &storage.Volume{
   163  				Tag: args[0].Tag,
   164  				VolumeInfo: storage.VolumeInfo{
   165  					VolumeId: "vol-ume",
   166  				},
   167  			},
   168  			VolumeAttachment: &storage.VolumeAttachment{
   169  				Volume:  args[0].Attachment.Volume,
   170  				Machine: args[0].Attachment.Machine,
   171  			},
   172  		}}, nil
   173  	}
   174  
   175  	attachVolumesCalled := make(chan interface{})
   176  	s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   177  		defer close(attachVolumesCalled)
   178  		return nil, errors.New("should not be called")
   179  	}
   180  
   181  	args := &workerArgs{volumes: volumeAccessor}
   182  	worker := newStorageProvisioner(c, args)
   183  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   184  	defer worker.Kill()
   185  
   186  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   187  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   188  	}}
   189  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set")
   190  
   191  	// The worker should create volumes according to ids "1".
   192  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   193  	args.environ.watcher.changes <- struct{}{}
   194  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   195  	assertNoEvent(c, attachVolumesCalled, "AttachVolumes called")
   196  }
   197  
   198  func (s *storageProvisionerSuite) TestCreateVolumeRetry(c *gc.C) {
   199  	volumeInfoSet := make(chan interface{})
   200  	volumeAccessor := newMockVolumeAccessor()
   201  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   202  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   203  		defer close(volumeInfoSet)
   204  		return make([]params.ErrorResult, len(volumes)), nil
   205  	}
   206  
   207  	// mockFunc's After will progress the current time by the specified
   208  	// duration and signal the channel immediately.
   209  	clock := &mockClock{}
   210  	var createVolumeTimes []time.Time
   211  
   212  	s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   213  		createVolumeTimes = append(createVolumeTimes, clock.Now())
   214  		if len(createVolumeTimes) < 10 {
   215  			return []storage.CreateVolumesResult{{Error: errors.New("badness")}}, nil
   216  		}
   217  		return []storage.CreateVolumesResult{{
   218  			Volume: &storage.Volume{Tag: args[0].Tag},
   219  		}}, nil
   220  	}
   221  
   222  	args := &workerArgs{volumes: volumeAccessor, clock: clock}
   223  	worker := newStorageProvisioner(c, args)
   224  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   225  	defer worker.Kill()
   226  
   227  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   228  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   229  	}}
   230  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   231  	args.environ.watcher.changes <- struct{}{}
   232  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   233  	c.Assert(createVolumeTimes, gc.HasLen, 10)
   234  
   235  	// The first attempt should have been immediate: T0.
   236  	c.Assert(createVolumeTimes[0], gc.Equals, time.Time{})
   237  
   238  	delays := make([]time.Duration, len(createVolumeTimes)-1)
   239  	for i := range createVolumeTimes[1:] {
   240  		delays[i] = createVolumeTimes[i+1].Sub(createVolumeTimes[i])
   241  	}
   242  	c.Assert(delays, jc.DeepEquals, []time.Duration{
   243  		30 * time.Second,
   244  		1 * time.Minute,
   245  		2 * time.Minute,
   246  		4 * time.Minute,
   247  		8 * time.Minute,
   248  		16 * time.Minute,
   249  		30 * time.Minute, // ceiling reached
   250  		30 * time.Minute,
   251  		30 * time.Minute,
   252  	})
   253  
   254  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   255  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   256  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   257  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   258  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   259  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   260  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   261  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   262  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   263  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   264  		{Tag: "volume-1", Status: "attaching", Info: ""},
   265  	})
   266  }
   267  
   268  func (s *storageProvisionerSuite) TestAttachVolumeRetry(c *gc.C) {
   269  	volumeInfoSet := make(chan interface{})
   270  	volumeAccessor := newMockVolumeAccessor()
   271  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   272  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   273  		defer close(volumeInfoSet)
   274  		return make([]params.ErrorResult, len(volumes)), nil
   275  	}
   276  	volumeAttachmentInfoSet := make(chan interface{})
   277  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   278  		defer close(volumeAttachmentInfoSet)
   279  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   280  	}
   281  
   282  	// mockFunc's After will progress the current time by the specified
   283  	// duration and signal the channel immediately.
   284  	clock := &mockClock{}
   285  	var attachVolumeTimes []time.Time
   286  
   287  	s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   288  		attachVolumeTimes = append(attachVolumeTimes, clock.Now())
   289  		if len(attachVolumeTimes) < 10 {
   290  			return []storage.AttachVolumesResult{{Error: errors.New("badness")}}, nil
   291  		}
   292  		return []storage.AttachVolumesResult{{
   293  			VolumeAttachment: &storage.VolumeAttachment{
   294  				args[0].Volume,
   295  				args[0].Machine,
   296  				storage.VolumeAttachmentInfo{
   297  					DeviceName: "/dev/sda1",
   298  				},
   299  			},
   300  		}}, nil
   301  	}
   302  
   303  	args := &workerArgs{volumes: volumeAccessor, clock: clock}
   304  	worker := newStorageProvisioner(c, args)
   305  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   306  	defer worker.Kill()
   307  
   308  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   309  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   310  	}}
   311  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   312  	args.environ.watcher.changes <- struct{}{}
   313  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   314  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   315  	c.Assert(attachVolumeTimes, gc.HasLen, 10)
   316  
   317  	// The first attempt should have been immediate: T0.
   318  	c.Assert(attachVolumeTimes[0], gc.Equals, time.Time{})
   319  
   320  	delays := make([]time.Duration, len(attachVolumeTimes)-1)
   321  	for i := range attachVolumeTimes[1:] {
   322  		delays[i] = attachVolumeTimes[i+1].Sub(attachVolumeTimes[i])
   323  	}
   324  	c.Assert(delays, jc.DeepEquals, []time.Duration{
   325  		30 * time.Second,
   326  		1 * time.Minute,
   327  		2 * time.Minute,
   328  		4 * time.Minute,
   329  		8 * time.Minute,
   330  		16 * time.Minute,
   331  		30 * time.Minute, // ceiling reached
   332  		30 * time.Minute,
   333  		30 * time.Minute,
   334  	})
   335  
   336  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   337  		{Tag: "volume-1", Status: "attaching", Info: ""},        // CreateVolumes
   338  		{Tag: "volume-1", Status: "attaching", Info: "badness"}, // AttachVolumes
   339  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   340  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   341  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   342  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   343  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   344  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   345  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   346  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   347  		{Tag: "volume-1", Status: "attached", Info: ""},
   348  	})
   349  }
   350  
   351  func (s *storageProvisionerSuite) TestFilesystemAdded(c *gc.C) {
   352  	expectedFilesystems := []params.Filesystem{{
   353  		FilesystemTag: "filesystem-1",
   354  		Info: params.FilesystemInfo{
   355  			FilesystemId: "id-1",
   356  			Size:         1024,
   357  		},
   358  	}, {
   359  		FilesystemTag: "filesystem-2",
   360  		Info: params.FilesystemInfo{
   361  			FilesystemId: "id-2",
   362  			Size:         1024,
   363  		},
   364  	}}
   365  
   366  	filesystemInfoSet := make(chan interface{})
   367  	filesystemAccessor := newMockFilesystemAccessor()
   368  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   369  		defer close(filesystemInfoSet)
   370  		c.Assert(filesystems, jc.SameContents, expectedFilesystems)
   371  		return nil, nil
   372  	}
   373  
   374  	args := &workerArgs{filesystems: filesystemAccessor}
   375  	worker := newStorageProvisioner(c, args)
   376  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   377  	defer worker.Kill()
   378  
   379  	// The worker should create filesystems according to ids "1" and "2".
   380  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1", "2"}
   381  	// ... but not until the environment config is available.
   382  	assertNoEvent(c, filesystemInfoSet, "filesystem info set")
   383  	args.environ.watcher.changes <- struct{}{}
   384  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
   385  }
   386  
   387  func (s *storageProvisionerSuite) TestVolumeNeedsInstance(c *gc.C) {
   388  	volumeInfoSet := make(chan interface{})
   389  	volumeAccessor := newMockVolumeAccessor()
   390  	volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) {
   391  		defer close(volumeInfoSet)
   392  		return nil, nil
   393  	}
   394  	volumeAccessor.setVolumeAttachmentInfo = func([]params.VolumeAttachment) ([]params.ErrorResult, error) {
   395  		return nil, nil
   396  	}
   397  
   398  	args := &workerArgs{volumes: volumeAccessor}
   399  	worker := newStorageProvisioner(c, args)
   400  	defer worker.Wait()
   401  	defer worker.Kill()
   402  
   403  	volumeAccessor.volumesWatcher.changes <- []string{needsInstanceVolumeId}
   404  	args.environ.watcher.changes <- struct{}{}
   405  	assertNoEvent(c, volumeInfoSet, "volume info set")
   406  	args.machines.instanceIds[names.NewMachineTag("1")] = "inst-id"
   407  	args.machines.watcher.changes <- struct{}{}
   408  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   409  }
   410  
   411  func (s *storageProvisionerSuite) TestVolumeNonDynamic(c *gc.C) {
   412  	volumeInfoSet := make(chan interface{})
   413  	volumeAccessor := newMockVolumeAccessor()
   414  	volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) {
   415  		defer close(volumeInfoSet)
   416  		return nil, nil
   417  	}
   418  
   419  	args := &workerArgs{volumes: volumeAccessor}
   420  	worker := newStorageProvisioner(c, args)
   421  	defer worker.Wait()
   422  	defer worker.Kill()
   423  
   424  	// Volumes for non-dynamic providers should not be created.
   425  	s.provider.dynamic = false
   426  	args.environ.watcher.changes <- struct{}{}
   427  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   428  	assertNoEvent(c, volumeInfoSet, "volume info set")
   429  }
   430  
   431  func (s *storageProvisionerSuite) TestVolumeAttachmentAdded(c *gc.C) {
   432  	// We should get two volume attachments:
   433  	//   - volume-1 to machine-1, because the volume and
   434  	//     machine are provisioned, but the attachment is not.
   435  	//   - volume-1 to machine-0, because the volume,
   436  	//     machine, and attachment are provisioned, but
   437  	//     in a previous session, so a reattachment is
   438  	//     requested.
   439  	expectedVolumeAttachments := []params.VolumeAttachment{{
   440  		VolumeTag:  "volume-1",
   441  		MachineTag: "machine-1",
   442  		Info: params.VolumeAttachmentInfo{
   443  			DeviceName: "/dev/sda1",
   444  			ReadOnly:   true,
   445  		},
   446  	}, {
   447  		VolumeTag:  "volume-1",
   448  		MachineTag: "machine-0",
   449  		Info: params.VolumeAttachmentInfo{
   450  			DeviceName: "/dev/sda1",
   451  			ReadOnly:   true,
   452  		},
   453  	}}
   454  
   455  	var allVolumeAttachments []params.VolumeAttachment
   456  	volumeAttachmentInfoSet := make(chan interface{})
   457  	volumeAccessor := newMockVolumeAccessor()
   458  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   459  		allVolumeAttachments = append(allVolumeAttachments, volumeAttachments...)
   460  		volumeAttachmentInfoSet <- nil
   461  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   462  	}
   463  
   464  	// volume-1, machine-0, and machine-1 are provisioned.
   465  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
   466  		VolumeTag: "volume-1",
   467  		Info: params.VolumeInfo{
   468  			VolumeId: "vol-123",
   469  		},
   470  	}
   471  	volumeAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0")
   472  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   473  
   474  	// machine-0/volume-1 attachment is already created.
   475  	// We should see a reattachment.
   476  	alreadyAttached := params.MachineStorageId{
   477  		MachineTag:    "machine-0",
   478  		AttachmentTag: "volume-1",
   479  	}
   480  	volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{
   481  		MachineTag: "machine-0",
   482  		VolumeTag:  "volume-1",
   483  	}
   484  
   485  	args := &workerArgs{volumes: volumeAccessor}
   486  	worker := newStorageProvisioner(c, args)
   487  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   488  	defer worker.Kill()
   489  
   490  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   491  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   492  	}, {
   493  		MachineTag: "machine-1", AttachmentTag: "volume-2",
   494  	}, {
   495  		MachineTag: "machine-2", AttachmentTag: "volume-1",
   496  	}, {
   497  		MachineTag: "machine-0", AttachmentTag: "volume-1",
   498  	}}
   499  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   500  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   501  	args.environ.watcher.changes <- struct{}{}
   502  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   503  	c.Assert(allVolumeAttachments, jc.SameContents, expectedVolumeAttachments)
   504  
   505  	// Reattachment should only happen once per session.
   506  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached}
   507  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   508  }
   509  
   510  func (s *storageProvisionerSuite) TestFilesystemAttachmentAdded(c *gc.C) {
   511  	// We should only get a single filesystem attachment, because it is the
   512  	// only combination where both machine and filesystem are already
   513  	// provisioned, and the attachmenti s not.
   514  	// We should get two filesystem attachments:
   515  	//   - filesystem-1 to machine-1, because the filesystem and
   516  	//     machine are provisioned, but the attachment is not.
   517  	//   - filesystem-1 to machine-0, because the filesystem,
   518  	//     machine, and attachment are provisioned, but in a
   519  	//     previous session, so a reattachment is requested.
   520  	expectedFilesystemAttachments := []params.FilesystemAttachment{{
   521  		FilesystemTag: "filesystem-1",
   522  		MachineTag:    "machine-1",
   523  		Info: params.FilesystemAttachmentInfo{
   524  			MountPoint: "/srv/fs-123",
   525  		},
   526  	}, {
   527  		FilesystemTag: "filesystem-1",
   528  		MachineTag:    "machine-0",
   529  		Info: params.FilesystemAttachmentInfo{
   530  			MountPoint: "/srv/fs-123",
   531  		},
   532  	}}
   533  
   534  	var allFilesystemAttachments []params.FilesystemAttachment
   535  	filesystemAttachmentInfoSet := make(chan interface{})
   536  	filesystemAccessor := newMockFilesystemAccessor()
   537  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
   538  		allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...)
   539  		filesystemAttachmentInfoSet <- nil
   540  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
   541  	}
   542  
   543  	// filesystem-1 and machine-1 are provisioned.
   544  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{
   545  		FilesystemTag: "filesystem-1",
   546  		Info: params.FilesystemInfo{
   547  			FilesystemId: "fs-123",
   548  		},
   549  	}
   550  	filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0")
   551  	filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   552  
   553  	// machine-0/filesystem-1 attachment is already created.
   554  	// We should see a reattachment.
   555  	alreadyAttached := params.MachineStorageId{
   556  		MachineTag:    "machine-0",
   557  		AttachmentTag: "filesystem-1",
   558  	}
   559  	filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{
   560  		MachineTag:    "machine-0",
   561  		FilesystemTag: "filesystem-1",
   562  	}
   563  
   564  	args := &workerArgs{filesystems: filesystemAccessor}
   565  	worker := newStorageProvisioner(c, args)
   566  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   567  	defer worker.Kill()
   568  
   569  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   570  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   571  	}, {
   572  		MachineTag: "machine-1", AttachmentTag: "filesystem-2",
   573  	}, {
   574  		MachineTag: "machine-2", AttachmentTag: "filesystem-1",
   575  	}, {
   576  		MachineTag: "machine-0", AttachmentTag: "filesystem-1",
   577  	}}
   578  	// ... but not until the environment config is available.
   579  	assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set")
   580  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   581  	args.environ.watcher.changes <- struct{}{}
   582  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
   583  	c.Assert(allFilesystemAttachments, jc.SameContents, expectedFilesystemAttachments)
   584  
   585  	// Reattachment should only happen once per session.
   586  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached}
   587  	assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set")
   588  }
   589  
   590  func (s *storageProvisionerSuite) TestCreateVolumeBackedFilesystem(c *gc.C) {
   591  	filesystemInfoSet := make(chan interface{})
   592  	filesystemAccessor := newMockFilesystemAccessor()
   593  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   594  		filesystemInfoSet <- filesystems
   595  		return nil, nil
   596  	}
   597  
   598  	args := &workerArgs{
   599  		scope:       names.NewMachineTag("0"),
   600  		filesystems: filesystemAccessor,
   601  	}
   602  	worker := newStorageProvisioner(c, args)
   603  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   604  	defer worker.Kill()
   605  
   606  	args.volumes.blockDevices[params.MachineStorageId{
   607  		MachineTag:    "machine-0",
   608  		AttachmentTag: "volume-0-0",
   609  	}] = storage.BlockDevice{
   610  		DeviceName: "xvdf1",
   611  		Size:       123,
   612  	}
   613  	filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0", "0/1"}
   614  	assertNoEvent(c, filesystemInfoSet, "filesystem info set")
   615  	args.environ.watcher.changes <- struct{}{}
   616  
   617  	// Only the block device for volume 0/0 is attached at the moment,
   618  	// so only the corresponding filesystem will be created.
   619  	filesystemInfo := waitChannel(
   620  		c, filesystemInfoSet,
   621  		"waiting for filesystem info to be set",
   622  	).([]params.Filesystem)
   623  	c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{
   624  		FilesystemTag: "filesystem-0-0",
   625  		Info: params.FilesystemInfo{
   626  			FilesystemId: "xvdf1",
   627  			Size:         123,
   628  		},
   629  	}})
   630  
   631  	// If we now attach the block device for volume 0/1 and trigger the
   632  	// notification, then the storage provisioner will wake up and create
   633  	// the filesystem.
   634  	args.volumes.blockDevices[params.MachineStorageId{
   635  		MachineTag:    "machine-0",
   636  		AttachmentTag: "volume-0-1",
   637  	}] = storage.BlockDevice{
   638  		DeviceName: "xvdf2",
   639  		Size:       246,
   640  	}
   641  	args.volumes.blockDevicesWatcher.changes <- struct{}{}
   642  	filesystemInfo = waitChannel(
   643  		c, filesystemInfoSet,
   644  		"waiting for filesystem info to be set",
   645  	).([]params.Filesystem)
   646  	c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{
   647  		FilesystemTag: "filesystem-0-1",
   648  		Info: params.FilesystemInfo{
   649  			FilesystemId: "xvdf2",
   650  			Size:         246,
   651  		},
   652  	}})
   653  }
   654  
   655  func (s *storageProvisionerSuite) TestAttachVolumeBackedFilesystem(c *gc.C) {
   656  	infoSet := make(chan interface{})
   657  	filesystemAccessor := newMockFilesystemAccessor()
   658  	filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
   659  		infoSet <- attachments
   660  		return nil, nil
   661  	}
   662  
   663  	args := &workerArgs{
   664  		scope:       names.NewMachineTag("0"),
   665  		filesystems: filesystemAccessor,
   666  	}
   667  	worker := newStorageProvisioner(c, args)
   668  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   669  	defer worker.Kill()
   670  
   671  	filesystemAccessor.provisionedFilesystems["filesystem-0-0"] = params.Filesystem{
   672  		FilesystemTag: "filesystem-0-0",
   673  		VolumeTag:     "volume-0-0",
   674  		Info: params.FilesystemInfo{
   675  			FilesystemId: "whatever",
   676  			Size:         123,
   677  		},
   678  	}
   679  	filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0")
   680  
   681  	args.volumes.blockDevices[params.MachineStorageId{
   682  		MachineTag:    "machine-0",
   683  		AttachmentTag: "volume-0-0",
   684  	}] = storage.BlockDevice{
   685  		DeviceName: "xvdf1",
   686  		Size:       123,
   687  	}
   688  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   689  		MachineTag:    "machine-0",
   690  		AttachmentTag: "filesystem-0-0",
   691  	}}
   692  	assertNoEvent(c, infoSet, "filesystem attachment info set")
   693  	args.environ.watcher.changes <- struct{}{}
   694  	filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0"}
   695  
   696  	info := waitChannel(
   697  		c, infoSet, "waiting for filesystem attachment info to be set",
   698  	).([]params.FilesystemAttachment)
   699  	c.Assert(info, jc.DeepEquals, []params.FilesystemAttachment{{
   700  		FilesystemTag: "filesystem-0-0",
   701  		MachineTag:    "machine-0",
   702  		Info: params.FilesystemAttachmentInfo{
   703  			MountPoint: "/mnt/xvdf1",
   704  			ReadOnly:   true,
   705  		},
   706  	}})
   707  }
   708  
   709  func (s *storageProvisionerSuite) TestUpdateEnvironConfig(c *gc.C) {
   710  	volumeAccessor := newMockVolumeAccessor()
   711  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   712  	s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) {
   713  		c.Assert(envConfig, gc.NotNil)
   714  		c.Assert(sourceConfig, gc.NotNil)
   715  		c.Assert(envConfig.AllAttrs()["foo"], gc.Equals, "bar")
   716  		return nil, errors.New("zinga")
   717  	}
   718  
   719  	args := &workerArgs{volumes: volumeAccessor}
   720  	worker := newStorageProvisioner(c, args)
   721  	defer worker.Wait()
   722  	defer worker.Kill()
   723  
   724  	newConfig, err := args.environ.cfg.Apply(map[string]interface{}{"foo": "bar"})
   725  	c.Assert(err, jc.ErrorIsNil)
   726  
   727  	args.environ.watcher.changes <- struct{}{}
   728  	args.environ.setConfig(newConfig)
   729  	args.environ.watcher.changes <- struct{}{}
   730  	args.volumes.volumesWatcher.changes <- []string{"1", "2"}
   731  
   732  	err = worker.Wait()
   733  	c.Assert(err, gc.ErrorMatches, `creating volumes: getting volume source: getting storage source "dummy": zinga`)
   734  }
   735  
   736  func (s *storageProvisionerSuite) TestResourceTags(c *gc.C) {
   737  	volumeInfoSet := make(chan interface{})
   738  	volumeAccessor := newMockVolumeAccessor()
   739  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   740  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   741  		defer close(volumeInfoSet)
   742  		return nil, nil
   743  	}
   744  
   745  	filesystemInfoSet := make(chan interface{})
   746  	filesystemAccessor := newMockFilesystemAccessor()
   747  	filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   748  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   749  		defer close(filesystemInfoSet)
   750  		return nil, nil
   751  	}
   752  
   753  	var volumeSource dummyVolumeSource
   754  	s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) {
   755  		return &volumeSource, nil
   756  	}
   757  
   758  	var filesystemSource dummyFilesystemSource
   759  	s.provider.filesystemSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.FilesystemSource, error) {
   760  		return &filesystemSource, nil
   761  	}
   762  
   763  	args := &workerArgs{
   764  		volumes:     volumeAccessor,
   765  		filesystems: filesystemAccessor,
   766  	}
   767  	worker := newStorageProvisioner(c, args)
   768  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   769  	defer worker.Kill()
   770  
   771  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   772  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   773  	args.environ.watcher.changes <- struct{}{}
   774  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   775  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
   776  	c.Assert(volumeSource.createVolumesArgs, jc.DeepEquals, [][]storage.VolumeParams{{{
   777  		Tag:          names.NewVolumeTag("1"),
   778  		Size:         1024,
   779  		Provider:     "dummy",
   780  		Attributes:   map[string]interface{}{"persistent": true},
   781  		ResourceTags: map[string]string{"very": "fancy"},
   782  		Attachment: &storage.VolumeAttachmentParams{
   783  			Volume: names.NewVolumeTag("1"),
   784  			AttachmentParams: storage.AttachmentParams{
   785  				Machine:    names.NewMachineTag("1"),
   786  				Provider:   "dummy",
   787  				InstanceId: "already-provisioned-1",
   788  				ReadOnly:   true,
   789  			},
   790  		},
   791  	}}})
   792  	c.Assert(filesystemSource.createFilesystemsArgs, jc.DeepEquals, [][]storage.FilesystemParams{{{
   793  		Tag:          names.NewFilesystemTag("1"),
   794  		Size:         1024,
   795  		Provider:     "dummy",
   796  		ResourceTags: map[string]string{"very": "fancy"},
   797  	}}})
   798  }
   799  
   800  func (s *storageProvisionerSuite) TestSetVolumeInfoErrorStopsWorker(c *gc.C) {
   801  	volumeAccessor := newMockVolumeAccessor()
   802  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   803  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   804  		return nil, errors.New("belly up")
   805  	}
   806  
   807  	args := &workerArgs{volumes: volumeAccessor}
   808  	worker := newStorageProvisioner(c, args)
   809  	defer worker.Wait()
   810  	defer worker.Kill()
   811  
   812  	done := make(chan interface{})
   813  	go func() {
   814  		defer close(done)
   815  		err := worker.Wait()
   816  		c.Assert(err, gc.ErrorMatches, "creating volumes: publishing volumes to state: belly up")
   817  	}()
   818  
   819  	args.volumes.volumesWatcher.changes <- []string{"1"}
   820  	args.environ.watcher.changes <- struct{}{}
   821  	waitChannel(c, done, "waiting for worker to exit")
   822  }
   823  
   824  func (s *storageProvisionerSuite) TestSetVolumeInfoErrorResultDoesNotStopWorker(c *gc.C) {
   825  	volumeAccessor := newMockVolumeAccessor()
   826  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   827  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   828  		return []params.ErrorResult{{Error: &params.Error{Message: "message", Code: "code"}}}, nil
   829  	}
   830  
   831  	args := &workerArgs{volumes: volumeAccessor}
   832  	worker := newStorageProvisioner(c, args)
   833  	defer func() {
   834  		err := worker.Wait()
   835  		c.Assert(err, jc.ErrorIsNil)
   836  	}()
   837  	defer worker.Kill()
   838  
   839  	done := make(chan interface{})
   840  	go func() {
   841  		defer close(done)
   842  		worker.Wait()
   843  	}()
   844  
   845  	args.volumes.volumesWatcher.changes <- []string{"1"}
   846  	args.environ.watcher.changes <- struct{}{}
   847  	assertNoEvent(c, done, "worker exited")
   848  }
   849  
   850  func (s *storageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) {
   851  	removed := make(chan interface{})
   852  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
   853  		defer close(removed)
   854  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
   855  			MachineTag:    "machine-0",
   856  			AttachmentTag: "volume-0",
   857  		}})
   858  		return make([]params.ErrorResult, len(ids)), nil
   859  	}
   860  
   861  	args := &workerArgs{
   862  		life: &mockLifecycleManager{removeAttachments: removeAttachments},
   863  	}
   864  	worker := newStorageProvisioner(c, args)
   865  	defer worker.Wait()
   866  	defer worker.Kill()
   867  
   868  	args.volumes.attachmentsWatcher.changes <- []params.MachineStorageId{{
   869  		MachineTag: "machine-0", AttachmentTag: "volume-0",
   870  	}}
   871  	args.environ.watcher.changes <- struct{}{}
   872  	waitChannel(c, removed, "waiting for attachment to be removed")
   873  }
   874  
   875  func (s *storageProvisionerSuite) TestDetachVolumes(c *gc.C) {
   876  	var attached bool
   877  	volumeAttachmentInfoSet := make(chan interface{})
   878  	volumeAccessor := newMockVolumeAccessor()
   879  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   880  		close(volumeAttachmentInfoSet)
   881  		attached = true
   882  		for _, a := range volumeAttachments {
   883  			id := params.MachineStorageId{
   884  				MachineTag:    a.MachineTag,
   885  				AttachmentTag: a.VolumeTag,
   886  			}
   887  			volumeAccessor.provisionedAttachments[id] = a
   888  		}
   889  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   890  	}
   891  
   892  	expectedAttachmentIds := []params.MachineStorageId{{
   893  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   894  	}}
   895  
   896  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
   897  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
   898  		life := params.Alive
   899  		if attached {
   900  			life = params.Dying
   901  		}
   902  		return []params.LifeResult{{Life: life}}, nil
   903  	}
   904  
   905  	detached := make(chan interface{})
   906  	s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) {
   907  		c.Assert(args, gc.HasLen, 1)
   908  		c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag)
   909  		c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag)
   910  		defer close(detached)
   911  		return make([]error, len(args)), nil
   912  	}
   913  
   914  	removed := make(chan interface{})
   915  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
   916  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
   917  		close(removed)
   918  		return make([]params.ErrorResult, len(ids)), nil
   919  	}
   920  
   921  	// volume-1 and machine-1 are provisioned.
   922  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
   923  		VolumeTag: "volume-1",
   924  		Info: params.VolumeInfo{
   925  			VolumeId: "vol-123",
   926  		},
   927  	}
   928  	volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
   929  
   930  	args := &workerArgs{
   931  		volumes: volumeAccessor,
   932  		life: &mockLifecycleManager{
   933  			attachmentLife:    attachmentLife,
   934  			removeAttachments: removeAttachments,
   935  		},
   936  	}
   937  	worker := newStorageProvisioner(c, args)
   938  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   939  	defer worker.Kill()
   940  
   941  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   942  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   943  	}}
   944  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   945  	args.environ.watcher.changes <- struct{}{}
   946  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   947  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
   948  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   949  	}}
   950  	waitChannel(c, detached, "waiting for volume to be detached")
   951  	waitChannel(c, removed, "waiting for attachment to be removed")
   952  }
   953  
   954  func (s *storageProvisionerSuite) TestDetachVolumesRetry(c *gc.C) {
   955  	machine := names.NewMachineTag("1")
   956  	volume := names.NewVolumeTag("1")
   957  	attachmentId := params.MachineStorageId{
   958  		MachineTag: machine.String(), AttachmentTag: volume.String(),
   959  	}
   960  	volumeAccessor := newMockVolumeAccessor()
   961  	volumeAccessor.provisionedAttachments[attachmentId] = params.VolumeAttachment{
   962  		MachineTag: machine.String(),
   963  		VolumeTag:  volume.String(),
   964  	}
   965  	volumeAccessor.provisionedVolumes[volume.String()] = params.Volume{
   966  		VolumeTag: volume.String(),
   967  		Info: params.VolumeInfo{
   968  			VolumeId: "vol-123",
   969  		},
   970  	}
   971  	volumeAccessor.provisionedMachines[machine.String()] = instance.Id("already-provisioned-1")
   972  
   973  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
   974  		return []params.LifeResult{{Life: params.Dying}}, nil
   975  	}
   976  
   977  	// mockFunc's After will progress the current time by the specified
   978  	// duration and signal the channel immediately.
   979  	clock := &mockClock{}
   980  	var detachVolumeTimes []time.Time
   981  
   982  	s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) {
   983  		detachVolumeTimes = append(detachVolumeTimes, clock.Now())
   984  		if len(detachVolumeTimes) < 10 {
   985  			return []error{errors.New("badness")}, nil
   986  		}
   987  		return []error{nil}, nil
   988  	}
   989  
   990  	removed := make(chan interface{})
   991  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
   992  		close(removed)
   993  		return make([]params.ErrorResult, len(ids)), nil
   994  	}
   995  
   996  	args := &workerArgs{
   997  		volumes: volumeAccessor,
   998  		clock:   clock,
   999  		life: &mockLifecycleManager{
  1000  			attachmentLife:    attachmentLife,
  1001  			removeAttachments: removeAttachments,
  1002  		},
  1003  	}
  1004  	worker := newStorageProvisioner(c, args)
  1005  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1006  	defer worker.Kill()
  1007  
  1008  	volumeAccessor.volumesWatcher.changes <- []string{volume.Id()}
  1009  	args.environ.watcher.changes <- struct{}{}
  1010  	volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{attachmentId}
  1011  	waitChannel(c, removed, "waiting for attachment to be removed")
  1012  	c.Assert(detachVolumeTimes, gc.HasLen, 10)
  1013  
  1014  	// The first attempt should have been immediate: T0.
  1015  	c.Assert(detachVolumeTimes[0], gc.Equals, time.Time{})
  1016  
  1017  	delays := make([]time.Duration, len(detachVolumeTimes)-1)
  1018  	for i := range detachVolumeTimes[1:] {
  1019  		delays[i] = detachVolumeTimes[i+1].Sub(detachVolumeTimes[i])
  1020  	}
  1021  	c.Assert(delays, jc.DeepEquals, []time.Duration{
  1022  		30 * time.Second,
  1023  		1 * time.Minute,
  1024  		2 * time.Minute,
  1025  		4 * time.Minute,
  1026  		8 * time.Minute,
  1027  		16 * time.Minute,
  1028  		30 * time.Minute, // ceiling reached
  1029  		30 * time.Minute,
  1030  		30 * time.Minute,
  1031  	})
  1032  
  1033  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
  1034  		{Tag: "volume-1", Status: "detaching", Info: "badness"}, // DetachVolumes
  1035  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1036  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1037  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1038  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1039  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1040  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1041  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1042  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1043  		{Tag: "volume-1", Status: "detached", Info: ""},
  1044  	})
  1045  }
  1046  
  1047  func (s *storageProvisionerSuite) TestDetachFilesystemsUnattached(c *gc.C) {
  1048  	removed := make(chan interface{})
  1049  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1050  		defer close(removed)
  1051  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
  1052  			MachineTag:    "machine-0",
  1053  			AttachmentTag: "filesystem-0",
  1054  		}})
  1055  		return make([]params.ErrorResult, len(ids)), nil
  1056  	}
  1057  
  1058  	args := &workerArgs{
  1059  		life: &mockLifecycleManager{removeAttachments: removeAttachments},
  1060  	}
  1061  	worker := newStorageProvisioner(c, args)
  1062  	defer worker.Wait()
  1063  	defer worker.Kill()
  1064  
  1065  	args.filesystems.attachmentsWatcher.changes <- []params.MachineStorageId{{
  1066  		MachineTag: "machine-0", AttachmentTag: "filesystem-0",
  1067  	}}
  1068  	args.environ.watcher.changes <- struct{}{}
  1069  	waitChannel(c, removed, "waiting for attachment to be removed")
  1070  }
  1071  
  1072  func (s *storageProvisionerSuite) TestDetachFilesystems(c *gc.C) {
  1073  	var attached bool
  1074  	filesystemAttachmentInfoSet := make(chan interface{})
  1075  	filesystemAccessor := newMockFilesystemAccessor()
  1076  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
  1077  		close(filesystemAttachmentInfoSet)
  1078  		attached = true
  1079  		for _, a := range filesystemAttachments {
  1080  			id := params.MachineStorageId{
  1081  				MachineTag:    a.MachineTag,
  1082  				AttachmentTag: a.FilesystemTag,
  1083  			}
  1084  			filesystemAccessor.provisionedAttachments[id] = a
  1085  		}
  1086  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
  1087  	}
  1088  
  1089  	expectedAttachmentIds := []params.MachineStorageId{{
  1090  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1091  	}}
  1092  
  1093  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  1094  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1095  		life := params.Alive
  1096  		if attached {
  1097  			life = params.Dying
  1098  		}
  1099  		return []params.LifeResult{{Life: life}}, nil
  1100  	}
  1101  
  1102  	detached := make(chan interface{})
  1103  	s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]error, error) {
  1104  		c.Assert(args, gc.HasLen, 1)
  1105  		c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag)
  1106  		c.Assert(args[0].Filesystem.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag)
  1107  		defer close(detached)
  1108  		return make([]error, len(args)), nil
  1109  	}
  1110  
  1111  	removed := make(chan interface{})
  1112  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1113  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1114  		close(removed)
  1115  		return make([]params.ErrorResult, len(ids)), nil
  1116  	}
  1117  
  1118  	// filesystem-1 and machine-1 are provisioned.
  1119  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{
  1120  		FilesystemTag: "filesystem-1",
  1121  		Info: params.FilesystemInfo{
  1122  			FilesystemId: "fs-id",
  1123  		},
  1124  	}
  1125  	filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1")
  1126  
  1127  	args := &workerArgs{
  1128  		filesystems: filesystemAccessor,
  1129  		life: &mockLifecycleManager{
  1130  			attachmentLife:    attachmentLife,
  1131  			removeAttachments: removeAttachments,
  1132  		},
  1133  	}
  1134  	worker := newStorageProvisioner(c, args)
  1135  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1136  	defer worker.Kill()
  1137  
  1138  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
  1139  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1140  	}}
  1141  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
  1142  	args.environ.watcher.changes <- struct{}{}
  1143  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
  1144  	filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{
  1145  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1146  	}}
  1147  	waitChannel(c, detached, "waiting for filesystem to be detached")
  1148  	waitChannel(c, removed, "waiting for attachment to be removed")
  1149  }
  1150  
  1151  func (s *storageProvisionerSuite) TestDestroyVolumes(c *gc.C) {
  1152  	provisionedVolume := names.NewVolumeTag("1")
  1153  	unprovisionedVolume := names.NewVolumeTag("2")
  1154  
  1155  	volumeAccessor := newMockVolumeAccessor()
  1156  	volumeAccessor.provisionVolume(provisionedVolume)
  1157  
  1158  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
  1159  		results := make([]params.LifeResult, len(tags))
  1160  		for i := range results {
  1161  			results[i].Life = params.Dead
  1162  		}
  1163  		return results, nil
  1164  	}
  1165  
  1166  	destroyedChan := make(chan interface{}, 1)
  1167  	s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) {
  1168  		destroyedChan <- volumeIds
  1169  		return make([]error, len(volumeIds)), nil
  1170  	}
  1171  
  1172  	removedChan := make(chan interface{}, 1)
  1173  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
  1174  		removedChan <- tags
  1175  		return make([]params.ErrorResult, len(tags)), nil
  1176  	}
  1177  
  1178  	args := &workerArgs{
  1179  		volumes: volumeAccessor,
  1180  		life: &mockLifecycleManager{
  1181  			life:   life,
  1182  			remove: remove,
  1183  		},
  1184  	}
  1185  	worker := newStorageProvisioner(c, args)
  1186  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1187  	defer worker.Kill()
  1188  
  1189  	volumeAccessor.volumesWatcher.changes <- []string{
  1190  		provisionedVolume.Id(),
  1191  		unprovisionedVolume.Id(),
  1192  	}
  1193  	args.environ.watcher.changes <- struct{}{}
  1194  
  1195  	// Both volumes should be removed; the provisioned one
  1196  	// should be deprovisioned first.
  1197  
  1198  	destroyed := waitChannel(c, destroyedChan, "waiting for volume to be deprovisioned")
  1199  	assertNoEvent(c, destroyedChan, "volumes deprovisioned")
  1200  	c.Assert(destroyed, jc.DeepEquals, []string{"vol-1"})
  1201  
  1202  	var removed []names.Tag
  1203  	for len(removed) < 2 {
  1204  		tags := waitChannel(c, removedChan, "waiting for volumes to be removed").([]names.Tag)
  1205  		removed = append(removed, tags...)
  1206  	}
  1207  	c.Assert(removed, jc.SameContents, []names.Tag{provisionedVolume, unprovisionedVolume})
  1208  	assertNoEvent(c, removedChan, "volumes removed")
  1209  }
  1210  
  1211  func (s *storageProvisionerSuite) TestDestroyVolumesRetry(c *gc.C) {
  1212  	volume := names.NewVolumeTag("1")
  1213  	volumeAccessor := newMockVolumeAccessor()
  1214  	volumeAccessor.provisionVolume(volume)
  1215  
  1216  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
  1217  		return []params.LifeResult{{Life: params.Dead}}, nil
  1218  	}
  1219  
  1220  	// mockFunc's After will progress the current time by the specified
  1221  	// duration and signal the channel immediately.
  1222  	clock := &mockClock{}
  1223  	var destroyVolumeTimes []time.Time
  1224  
  1225  	s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) {
  1226  		destroyVolumeTimes = append(destroyVolumeTimes, clock.Now())
  1227  		if len(destroyVolumeTimes) < 10 {
  1228  			return []error{errors.New("badness")}, nil
  1229  		}
  1230  		return []error{nil}, nil
  1231  	}
  1232  
  1233  	removedChan := make(chan interface{}, 1)
  1234  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
  1235  		removedChan <- tags
  1236  		return make([]params.ErrorResult, len(tags)), nil
  1237  	}
  1238  
  1239  	args := &workerArgs{
  1240  		volumes: volumeAccessor,
  1241  		clock:   clock,
  1242  		life: &mockLifecycleManager{
  1243  			life:   life,
  1244  			remove: remove,
  1245  		},
  1246  	}
  1247  	worker := newStorageProvisioner(c, args)
  1248  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1249  	defer worker.Kill()
  1250  
  1251  	volumeAccessor.volumesWatcher.changes <- []string{volume.Id()}
  1252  	args.environ.watcher.changes <- struct{}{}
  1253  	waitChannel(c, removedChan, "waiting for volume to be removed")
  1254  	c.Assert(destroyVolumeTimes, gc.HasLen, 10)
  1255  
  1256  	// The first attempt should have been immediate: T0.
  1257  	c.Assert(destroyVolumeTimes[0], gc.Equals, time.Time{})
  1258  
  1259  	delays := make([]time.Duration, len(destroyVolumeTimes)-1)
  1260  	for i := range destroyVolumeTimes[1:] {
  1261  		delays[i] = destroyVolumeTimes[i+1].Sub(destroyVolumeTimes[i])
  1262  	}
  1263  	c.Assert(delays, jc.DeepEquals, []time.Duration{
  1264  		30 * time.Second,
  1265  		1 * time.Minute,
  1266  		2 * time.Minute,
  1267  		4 * time.Minute,
  1268  		8 * time.Minute,
  1269  		16 * time.Minute,
  1270  		30 * time.Minute, // ceiling reached
  1271  		30 * time.Minute,
  1272  		30 * time.Minute,
  1273  	})
  1274  
  1275  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
  1276  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1277  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1278  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1279  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1280  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1281  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1282  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1283  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1284  		{Tag: "volume-1", Status: "destroying", Info: "badness"},
  1285  	})
  1286  }
  1287  
  1288  func (s *storageProvisionerSuite) TestDestroyFilesystems(c *gc.C) {
  1289  	provisionedFilesystem := names.NewFilesystemTag("1")
  1290  	unprovisionedFilesystem := names.NewFilesystemTag("2")
  1291  
  1292  	filesystemAccessor := newMockFilesystemAccessor()
  1293  	filesystemAccessor.provisionFilesystem(provisionedFilesystem)
  1294  
  1295  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
  1296  		results := make([]params.LifeResult, len(tags))
  1297  		for i := range results {
  1298  			results[i].Life = params.Dead
  1299  		}
  1300  		return results, nil
  1301  	}
  1302  
  1303  	removedChan := make(chan interface{}, 1)
  1304  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
  1305  		removedChan <- tags
  1306  		return make([]params.ErrorResult, len(tags)), nil
  1307  	}
  1308  
  1309  	args := &workerArgs{
  1310  		filesystems: filesystemAccessor,
  1311  		life: &mockLifecycleManager{
  1312  			life:   life,
  1313  			remove: remove,
  1314  		},
  1315  	}
  1316  	worker := newStorageProvisioner(c, args)
  1317  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1318  	defer worker.Kill()
  1319  
  1320  	filesystemAccessor.filesystemsWatcher.changes <- []string{
  1321  		provisionedFilesystem.Id(),
  1322  		unprovisionedFilesystem.Id(),
  1323  	}
  1324  	args.environ.watcher.changes <- struct{}{}
  1325  
  1326  	// Both filesystems should be removed; the provisioned one
  1327  	// *should* be deprovisioned first, but we don't currently
  1328  	// have the ability to do so via the storage provider API.
  1329  
  1330  	var removed []names.Tag
  1331  	for len(removed) < 2 {
  1332  		tags := waitChannel(c, removedChan, "waiting for filesystems to be removed").([]names.Tag)
  1333  		removed = append(removed, tags...)
  1334  	}
  1335  	c.Assert(removed, jc.SameContents, []names.Tag{provisionedFilesystem, unprovisionedFilesystem})
  1336  	assertNoEvent(c, removedChan, "filesystems removed")
  1337  }
  1338  
  1339  func newStorageProvisioner(c *gc.C, args *workerArgs) worker.Worker {
  1340  	if args == nil {
  1341  		args = &workerArgs{}
  1342  	}
  1343  	if args.scope == nil {
  1344  		args.scope = coretesting.EnvironmentTag
  1345  	}
  1346  	if args.volumes == nil {
  1347  		args.volumes = newMockVolumeAccessor()
  1348  	}
  1349  	if args.filesystems == nil {
  1350  		args.filesystems = newMockFilesystemAccessor()
  1351  	}
  1352  	if args.life == nil {
  1353  		args.life = &mockLifecycleManager{}
  1354  	}
  1355  	if args.environ == nil {
  1356  		args.environ = newMockEnvironAccessor(c)
  1357  	}
  1358  	if args.machines == nil {
  1359  		args.machines = newMockMachineAccessor(c)
  1360  	}
  1361  	if args.clock == nil {
  1362  		args.clock = &mockClock{}
  1363  	}
  1364  	if args.statusSetter == nil {
  1365  		args.statusSetter = &mockStatusSetter{}
  1366  	}
  1367  	return storageprovisioner.NewStorageProvisioner(
  1368  		args.scope,
  1369  		"storage-dir",
  1370  		args.volumes,
  1371  		args.filesystems,
  1372  		args.life,
  1373  		args.environ,
  1374  		args.machines,
  1375  		args.statusSetter,
  1376  		args.clock,
  1377  	)
  1378  }
  1379  
  1380  type workerArgs struct {
  1381  	scope        names.Tag
  1382  	volumes      *mockVolumeAccessor
  1383  	filesystems  *mockFilesystemAccessor
  1384  	life         *mockLifecycleManager
  1385  	environ      *mockEnvironAccessor
  1386  	machines     *mockMachineAccessor
  1387  	clock        clock.Clock
  1388  	statusSetter *mockStatusSetter
  1389  }
  1390  
  1391  func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} {
  1392  	select {
  1393  	case v := <-ch:
  1394  		return v
  1395  	case <-time.After(coretesting.LongWait):
  1396  		c.Fatalf("timed out " + activity)
  1397  		panic("unreachable")
  1398  	}
  1399  }
  1400  
  1401  func assertNoEvent(c *gc.C, ch <-chan interface{}, event string) {
  1402  	select {
  1403  	case <-ch:
  1404  		c.Fatalf("unexpected " + event)
  1405  	case <-time.After(coretesting.ShortWait):
  1406  	}
  1407  }