github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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  	stdcontext "context"
     8  	"time"
     9  
    10  	"github.com/juju/clock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/worker/v3"
    16  	"github.com/juju/worker/v3/workertest"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/core/life"
    20  	"github.com/juju/juju/core/watcher"
    21  	"github.com/juju/juju/environs/context"
    22  	"github.com/juju/juju/rpc/params"
    23  	"github.com/juju/juju/storage"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/worker/storageprovisioner"
    26  )
    27  
    28  type storageProvisionerSuite struct {
    29  	coretesting.BaseSuite
    30  	provider                *dummyProvider
    31  	registry                storage.ProviderRegistry
    32  	managedFilesystemSource *mockManagedFilesystemSource
    33  }
    34  
    35  var _ = gc.Suite(&storageProvisionerSuite{})
    36  
    37  func (s *storageProvisionerSuite) SetUpTest(c *gc.C) {
    38  	s.BaseSuite.SetUpTest(c)
    39  	s.provider = &dummyProvider{dynamic: true}
    40  	s.registry = storage.StaticProviderRegistry{
    41  		map[storage.ProviderType]storage.Provider{
    42  			"dummy": s.provider,
    43  		},
    44  	}
    45  
    46  	s.managedFilesystemSource = nil
    47  	s.PatchValue(
    48  		storageprovisioner.NewManagedFilesystemSource,
    49  		func(
    50  			blockDevices map[names.VolumeTag]storage.BlockDevice,
    51  			filesystems map[names.FilesystemTag]storage.Filesystem,
    52  		) storage.FilesystemSource {
    53  			s.managedFilesystemSource = &mockManagedFilesystemSource{
    54  				blockDevices: blockDevices,
    55  				filesystems:  filesystems,
    56  			}
    57  			return s.managedFilesystemSource
    58  		},
    59  	)
    60  	s.PatchValue(storageprovisioner.DefaultDependentChangesTimeout, 10*time.Millisecond)
    61  }
    62  
    63  func (s *storageProvisionerSuite) TestStartStop(c *gc.C) {
    64  	worker, err := storageprovisioner.NewStorageProvisioner(storageprovisioner.Config{
    65  		Scope:       coretesting.ModelTag,
    66  		Volumes:     newMockVolumeAccessor(),
    67  		Filesystems: newMockFilesystemAccessor(),
    68  		Life:        &mockLifecycleManager{},
    69  		Registry:    s.registry,
    70  		Machines:    newMockMachineAccessor(c),
    71  		Status:      &mockStatusSetter{},
    72  		Clock:       &mockClock{},
    73  		Logger:      loggo.GetLogger("test"),
    74  		CloudCallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
    75  			return context.NewEmptyCloudCallContext()
    76  		},
    77  	})
    78  	c.Assert(err, jc.ErrorIsNil)
    79  
    80  	worker.Kill()
    81  	c.Assert(worker.Wait(), gc.IsNil)
    82  }
    83  
    84  func (s *storageProvisionerSuite) TestInvalidConfig(c *gc.C) {
    85  	_, err := storageprovisioner.NewStorageProvisioner(almostValidConfig())
    86  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    87  }
    88  
    89  func (s *storageProvisionerSuite) TestVolumeAdded(c *gc.C) {
    90  	expectedVolumes := []params.Volume{{
    91  		VolumeTag: "volume-1",
    92  		Info: params.VolumeInfo{
    93  			VolumeId:   "id-1",
    94  			HardwareId: "serial-1",
    95  			Size:       1024,
    96  			Persistent: true,
    97  		},
    98  	}, {
    99  		VolumeTag: "volume-2",
   100  		Info: params.VolumeInfo{
   101  			VolumeId:   "id-2",
   102  			HardwareId: "serial-2",
   103  			Size:       1024,
   104  		},
   105  	}}
   106  	expectedVolumeAttachments := []params.VolumeAttachment{{
   107  		VolumeTag:  "volume-1",
   108  		MachineTag: "machine-1",
   109  		Info: params.VolumeAttachmentInfo{
   110  			DeviceName: "/dev/sda1",
   111  			ReadOnly:   true,
   112  		},
   113  	}, {
   114  		VolumeTag:  "volume-2",
   115  		MachineTag: "machine-1",
   116  		Info: params.VolumeAttachmentInfo{
   117  			DeviceName: "/dev/sda2",
   118  		},
   119  	}}
   120  
   121  	volumeInfoSet := make(chan interface{})
   122  	volumeAccessor := newMockVolumeAccessor()
   123  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   124  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   125  		defer close(volumeInfoSet)
   126  		c.Assert(volumes, jc.SameContents, expectedVolumes)
   127  		return nil, nil
   128  	}
   129  
   130  	volumeAttachmentInfoSet := make(chan interface{})
   131  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   132  		defer close(volumeAttachmentInfoSet)
   133  		c.Assert(volumeAttachments, jc.SameContents, expectedVolumeAttachments)
   134  		return nil, nil
   135  	}
   136  	volumeAttachmentPlansCreate := make(chan interface{})
   137  	volumeAccessor.createVolumeAttachmentPlans = func(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error) {
   138  		defer close(volumeAttachmentPlansCreate)
   139  		return make([]params.ErrorResult, len(volumeAttachmentPlans)), nil
   140  	}
   141  
   142  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
   143  	worker := newStorageProvisioner(c, args)
   144  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   145  	defer worker.Kill()
   146  
   147  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   148  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   149  	}, {
   150  		MachineTag: "machine-1", AttachmentTag: "volume-2",
   151  	}}
   152  	assertNoEvent(c, volumeAttachmentPlansCreate, "volume attachment plans set")
   153  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set")
   154  	// The worker should create volumes according to ids "1" and "2".
   155  	volumeAccessor.volumesWatcher.changes <- []string{"1", "2"}
   156  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   157  	waitChannel(c, volumeAttachmentPlansCreate, "waiting for volume attachment plans to be set")
   158  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   159  }
   160  
   161  func (s *storageProvisionerSuite) TestCreateVolumeCreatesAttachment(c *gc.C) {
   162  	volumeAccessor := newMockVolumeAccessor()
   163  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   164  
   165  	volumeAttachmentInfoSet := make(chan interface{})
   166  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   167  		defer close(volumeAttachmentInfoSet)
   168  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   169  	}
   170  	volumeAttachmentPlansCreate := make(chan interface{})
   171  	volumeAccessor.createVolumeAttachmentPlans = func(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error) {
   172  		defer close(volumeAttachmentPlansCreate)
   173  		return make([]params.ErrorResult, len(volumeAttachmentPlans)), nil
   174  	}
   175  
   176  	s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   177  		volumeAccessor.provisionedAttachments[params.MachineStorageId{
   178  			MachineTag:    args[0].Attachment.Machine.String(),
   179  			AttachmentTag: args[0].Attachment.Volume.String(),
   180  		}] = params.VolumeAttachment{
   181  			VolumeTag:  args[0].Attachment.Volume.String(),
   182  			MachineTag: args[0].Attachment.Machine.String(),
   183  		}
   184  		return []storage.CreateVolumesResult{{
   185  			Volume: &storage.Volume{
   186  				Tag: args[0].Tag,
   187  				VolumeInfo: storage.VolumeInfo{
   188  					VolumeId: "vol-ume",
   189  				},
   190  			},
   191  			VolumeAttachment: &storage.VolumeAttachment{
   192  				Volume:  args[0].Attachment.Volume,
   193  				Machine: args[0].Attachment.Machine,
   194  			},
   195  		}}, nil
   196  	}
   197  
   198  	attachVolumesCalled := make(chan interface{})
   199  	s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   200  		defer close(attachVolumesCalled)
   201  		return nil, errors.New("should not be called")
   202  	}
   203  
   204  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
   205  	worker := newStorageProvisioner(c, args)
   206  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   207  	defer worker.Kill()
   208  
   209  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   210  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   211  	}}
   212  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set")
   213  
   214  	// The worker should create volumes according to ids "1".
   215  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   216  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   217  	assertNoEvent(c, attachVolumesCalled, "AttachVolumes called")
   218  }
   219  
   220  func (s *storageProvisionerSuite) TestCreateVolumeRetry(c *gc.C) {
   221  	volumeInfoSet := make(chan interface{})
   222  	volumeAccessor := newMockVolumeAccessor()
   223  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   224  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   225  		defer close(volumeInfoSet)
   226  		return make([]params.ErrorResult, len(volumes)), nil
   227  	}
   228  
   229  	// mockFunc's After will progress the current time by the specified
   230  	// duration and signal the channel immediately.
   231  	clock := &mockClock{}
   232  	var createVolumeTimes []time.Time
   233  
   234  	s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   235  		createVolumeTimes = append(createVolumeTimes, clock.Now())
   236  		if len(createVolumeTimes) < 10 {
   237  			return []storage.CreateVolumesResult{{Error: errors.New("badness")}}, nil
   238  		}
   239  		return []storage.CreateVolumesResult{{
   240  			Volume: &storage.Volume{Tag: args[0].Tag},
   241  		}}, nil
   242  	}
   243  
   244  	args := &workerArgs{volumes: volumeAccessor, clock: clock, registry: s.registry}
   245  	worker := newStorageProvisioner(c, args)
   246  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   247  	defer worker.Kill()
   248  
   249  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   250  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   251  	}}
   252  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   253  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   254  	c.Assert(createVolumeTimes, gc.HasLen, 10)
   255  
   256  	// The first attempt should have been immediate: T0.
   257  	c.Assert(createVolumeTimes[0], gc.Equals, time.Time{})
   258  
   259  	delays := make([]time.Duration, len(createVolumeTimes)-1)
   260  	for i := range createVolumeTimes[1:] {
   261  		delays[i] = createVolumeTimes[i+1].Sub(createVolumeTimes[i])
   262  	}
   263  	c.Assert(delays, jc.DeepEquals, []time.Duration{
   264  		30 * time.Second,
   265  		1 * time.Minute,
   266  		2 * time.Minute,
   267  		4 * time.Minute,
   268  		8 * time.Minute,
   269  		16 * time.Minute,
   270  		30 * time.Minute, // ceiling reached
   271  		30 * time.Minute,
   272  		30 * time.Minute,
   273  	})
   274  
   275  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   276  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   277  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   278  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   279  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   280  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   281  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   282  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   283  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   284  		{Tag: "volume-1", Status: "pending", Info: "badness"},
   285  		{Tag: "volume-1", Status: "attaching", Info: ""},
   286  	})
   287  }
   288  
   289  func (s *storageProvisionerSuite) TestCreateFilesystemRetry(c *gc.C) {
   290  	filesystemInfoSet := make(chan interface{})
   291  	filesystemAccessor := newMockFilesystemAccessor()
   292  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   293  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   294  		defer close(filesystemInfoSet)
   295  		return make([]params.ErrorResult, len(filesystems)), nil
   296  	}
   297  
   298  	// mockFunc's After will progress the current time by the specified
   299  	// duration and signal the channel immediately.
   300  	clock := &mockClock{}
   301  	var createFilesystemTimes []time.Time
   302  
   303  	s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) {
   304  		createFilesystemTimes = append(createFilesystemTimes, clock.Now())
   305  		if len(createFilesystemTimes) < 10 {
   306  			return []storage.CreateFilesystemsResult{{Error: errors.New("badness")}}, nil
   307  		}
   308  		return []storage.CreateFilesystemsResult{{
   309  			Filesystem: &storage.Filesystem{Tag: args[0].Tag},
   310  		}}, nil
   311  	}
   312  
   313  	args := &workerArgs{filesystems: filesystemAccessor, clock: clock, registry: s.registry}
   314  	worker := newStorageProvisioner(c, args)
   315  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   316  	defer worker.Kill()
   317  
   318  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   319  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   320  	}}
   321  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   322  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
   323  	c.Assert(createFilesystemTimes, gc.HasLen, 10)
   324  
   325  	// The first attempt should have been immediate: T0.
   326  	c.Assert(createFilesystemTimes[0], gc.Equals, time.Time{})
   327  
   328  	delays := make([]time.Duration, len(createFilesystemTimes)-1)
   329  	for i := range createFilesystemTimes[1:] {
   330  		delays[i] = createFilesystemTimes[i+1].Sub(createFilesystemTimes[i])
   331  	}
   332  	c.Assert(delays, jc.DeepEquals, []time.Duration{
   333  		30 * time.Second,
   334  		1 * time.Minute,
   335  		2 * time.Minute,
   336  		4 * time.Minute,
   337  		8 * time.Minute,
   338  		16 * time.Minute,
   339  		30 * time.Minute, // ceiling reached
   340  		30 * time.Minute,
   341  		30 * time.Minute,
   342  	})
   343  
   344  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   345  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   346  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   347  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   348  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   349  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   350  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   351  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   352  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   353  		{Tag: "filesystem-1", Status: "pending", Info: "badness"},
   354  		{Tag: "filesystem-1", Status: "attaching", Info: ""},
   355  	})
   356  }
   357  
   358  func (s *storageProvisionerSuite) TestFilesystemChannelReceivedOrder(c *gc.C) {
   359  	alreadyAttached := params.MachineStorageId{
   360  		MachineTag:    "machine-1",
   361  		AttachmentTag: "filesystem-1",
   362  	}
   363  	fileSystem := params.Filesystem{
   364  		FilesystemTag: "filesystem-1",
   365  		Info: params.FilesystemInfo{
   366  			FilesystemId: "1/1",
   367  		},
   368  	}
   369  
   370  	filesystemAttachInfoSet := make(chan interface{})
   371  	filesystemAccessor := newMockFilesystemAccessor()
   372  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   373  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = fileSystem
   374  	filesystemAccessor.provisionedMachinesFilesystems["filesystem-1"] = fileSystem
   375  	filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{
   376  		MachineTag:    "machine-1",
   377  		FilesystemTag: "filesystem-1",
   378  		Info:          params.FilesystemAttachmentInfo{MountPoint: "/dev/sda1"},
   379  	}
   380  	filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
   381  		defer close(filesystemAttachInfoSet)
   382  		return make([]params.ErrorResult, len(attachments)), nil
   383  	}
   384  
   385  	// mockFunc's After will progress the current time by the specified
   386  	// duration and signal the channel immediately.
   387  	clock := &mockClock{}
   388  	s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) {
   389  		return []storage.CreateFilesystemsResult{{
   390  			Filesystem: &storage.Filesystem{Tag: args[0].Tag},
   391  		}}, nil
   392  	}
   393  
   394  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
   395  		results := make([]params.LifeResult, len(tags))
   396  		for i := range results {
   397  			results[i].Life = life.Alive
   398  		}
   399  		return results, nil
   400  	}
   401  
   402  	args := &workerArgs{
   403  		filesystems: filesystemAccessor,
   404  		life: &mockLifecycleManager{
   405  			life: life,
   406  		},
   407  		clock:    clock,
   408  		registry: s.registry,
   409  	}
   410  	worker := newStorageProvisioner(c, args)
   411  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   412  	defer worker.Kill()
   413  
   414  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   415  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   416  	}}
   417  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   418  	waitChannel(c, filesystemAttachInfoSet, "waiting for filesystem attach info to be set")
   419  
   420  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   421  		{Tag: "filesystem-1", Status: "attached", Info: ""},
   422  	})
   423  }
   424  
   425  func (s *storageProvisionerSuite) TestAttachVolumeRetry(c *gc.C) {
   426  	volumeInfoSet := make(chan interface{})
   427  	volumeAccessor := newMockVolumeAccessor()
   428  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   429  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
   430  		defer close(volumeInfoSet)
   431  		return make([]params.ErrorResult, len(volumes)), nil
   432  	}
   433  	volumeAttachmentInfoSet := make(chan interface{})
   434  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   435  		defer close(volumeAttachmentInfoSet)
   436  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   437  	}
   438  
   439  	// mockFunc's After will progress the current time by the specified
   440  	// duration and signal the channel immediately.
   441  	clock := &mockClock{}
   442  	var attachVolumeTimes []time.Time
   443  
   444  	s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   445  		attachVolumeTimes = append(attachVolumeTimes, clock.Now())
   446  		if len(attachVolumeTimes) < 10 {
   447  			return []storage.AttachVolumesResult{{Error: errors.New("badness")}}, nil
   448  		}
   449  		return []storage.AttachVolumesResult{{
   450  			VolumeAttachment: &storage.VolumeAttachment{
   451  				args[0].Volume,
   452  				args[0].Machine,
   453  				storage.VolumeAttachmentInfo{
   454  					DeviceName: "/dev/sda1",
   455  				},
   456  			},
   457  		}}, nil
   458  	}
   459  
   460  	args := &workerArgs{volumes: volumeAccessor, clock: clock, registry: s.registry}
   461  	worker := newStorageProvisioner(c, args)
   462  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   463  	defer worker.Kill()
   464  
   465  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   466  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   467  	}}
   468  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   469  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   470  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   471  	c.Assert(attachVolumeTimes, gc.HasLen, 10)
   472  
   473  	// The first attempt should have been immediate: T0.
   474  	c.Assert(attachVolumeTimes[0], gc.Equals, time.Time{})
   475  
   476  	delays := make([]time.Duration, len(attachVolumeTimes)-1)
   477  	for i := range attachVolumeTimes[1:] {
   478  		delays[i] = attachVolumeTimes[i+1].Sub(attachVolumeTimes[i])
   479  	}
   480  	c.Assert(delays, jc.DeepEquals, []time.Duration{
   481  		30 * time.Second,
   482  		1 * time.Minute,
   483  		2 * time.Minute,
   484  		4 * time.Minute,
   485  		8 * time.Minute,
   486  		16 * time.Minute,
   487  		30 * time.Minute, // ceiling reached
   488  		30 * time.Minute,
   489  		30 * time.Minute,
   490  	})
   491  
   492  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   493  		{Tag: "volume-1", Status: "attaching", Info: ""},        // CreateVolumes
   494  		{Tag: "volume-1", Status: "attaching", Info: "badness"}, // AttachVolumes
   495  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   496  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   497  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   498  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   499  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   500  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   501  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   502  		{Tag: "volume-1", Status: "attaching", Info: "badness"},
   503  		{Tag: "volume-1", Status: "attached", Info: ""},
   504  	})
   505  }
   506  
   507  func (s *storageProvisionerSuite) TestAttachFilesystemRetry(c *gc.C) {
   508  	filesystemInfoSet := make(chan interface{})
   509  	filesystemAccessor := newMockFilesystemAccessor()
   510  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   511  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   512  		defer close(filesystemInfoSet)
   513  		return make([]params.ErrorResult, len(filesystems)), nil
   514  	}
   515  	filesystemAttachmentInfoSet := make(chan interface{})
   516  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
   517  		defer close(filesystemAttachmentInfoSet)
   518  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
   519  	}
   520  
   521  	// mockFunc's After will progress the current time by the specified
   522  	// duration and signal the channel immediately.
   523  	clock := &mockClock{}
   524  	var attachFilesystemTimes []time.Time
   525  
   526  	s.provider.attachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) {
   527  		attachFilesystemTimes = append(attachFilesystemTimes, clock.Now())
   528  		if len(attachFilesystemTimes) < 10 {
   529  			return []storage.AttachFilesystemsResult{{Error: errors.New("badness")}}, nil
   530  		}
   531  		return []storage.AttachFilesystemsResult{{
   532  			FilesystemAttachment: &storage.FilesystemAttachment{
   533  				args[0].Filesystem,
   534  				args[0].Machine,
   535  				storage.FilesystemAttachmentInfo{
   536  					Path: "/oh/over/there",
   537  				},
   538  			},
   539  		}}, nil
   540  	}
   541  
   542  	args := &workerArgs{filesystems: filesystemAccessor, clock: clock, registry: s.registry}
   543  	worker := newStorageProvisioner(c, args)
   544  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   545  	defer worker.Kill()
   546  
   547  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   548  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   549  	}}
   550  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   551  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
   552  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
   553  	c.Assert(attachFilesystemTimes, gc.HasLen, 10)
   554  
   555  	// The first attempt should have been immediate: T0.
   556  	c.Assert(attachFilesystemTimes[0], gc.Equals, time.Time{})
   557  
   558  	delays := make([]time.Duration, len(attachFilesystemTimes)-1)
   559  	for i := range attachFilesystemTimes[1:] {
   560  		delays[i] = attachFilesystemTimes[i+1].Sub(attachFilesystemTimes[i])
   561  	}
   562  	c.Assert(delays, jc.DeepEquals, []time.Duration{
   563  		30 * time.Second,
   564  		1 * time.Minute,
   565  		2 * time.Minute,
   566  		4 * time.Minute,
   567  		8 * time.Minute,
   568  		16 * time.Minute,
   569  		30 * time.Minute, // ceiling reached
   570  		30 * time.Minute,
   571  		30 * time.Minute,
   572  	})
   573  
   574  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   575  		{Tag: "filesystem-1", Status: "attaching", Info: ""},        // CreateFilesystems
   576  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"}, // AttachFilesystems
   577  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   578  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   579  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   580  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   581  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   582  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   583  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   584  		{Tag: "filesystem-1", Status: "attaching", Info: "badness"},
   585  		{Tag: "filesystem-1", Status: "attached", Info: ""},
   586  	})
   587  }
   588  
   589  func (s *storageProvisionerSuite) TestValidateVolumeParams(c *gc.C) {
   590  	volumeAccessor := newMockVolumeAccessor()
   591  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   592  	volumeAccessor.provisionedVolumes["volume-3"] = params.Volume{
   593  		VolumeTag: "volume-3",
   594  		Info:      params.VolumeInfo{VolumeId: "vol-ume"},
   595  	}
   596  
   597  	var validateCalls int
   598  	validated := make(chan interface{}, 1)
   599  	s.provider.validateVolumeParamsFunc = func(p storage.VolumeParams) error {
   600  		validateCalls++
   601  		validated <- p
   602  		switch p.Tag.String() {
   603  		case "volume-1", "volume-3":
   604  			return errors.New("something is wrong")
   605  		}
   606  		return nil
   607  	}
   608  
   609  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
   610  		results := make([]params.LifeResult, len(tags))
   611  		for i := range results {
   612  			switch tags[i].String() {
   613  			case "volume-3":
   614  				results[i].Life = life.Dead
   615  			default:
   616  				results[i].Life = life.Alive
   617  			}
   618  		}
   619  		return results, nil
   620  	}
   621  
   622  	createdVolumes := make(chan interface{}, 1)
   623  	s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   624  		createdVolumes <- args
   625  		if len(args) != 1 {
   626  			return nil, errors.New("expected one argument")
   627  		}
   628  		return []storage.CreateVolumesResult{{
   629  			Volume: &storage.Volume{Tag: args[0].Tag},
   630  		}}, nil
   631  	}
   632  
   633  	destroyedVolumes := make(chan interface{}, 1)
   634  	s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) {
   635  		destroyedVolumes <- volumeIds
   636  		return make([]error, len(volumeIds)), nil
   637  	}
   638  
   639  	args := &workerArgs{
   640  		volumes: volumeAccessor,
   641  		life: &mockLifecycleManager{
   642  			life: life,
   643  		},
   644  		registry: s.registry,
   645  	}
   646  	worker := newStorageProvisioner(c, args)
   647  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   648  	defer worker.Kill()
   649  
   650  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   651  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   652  	}, {
   653  		MachineTag: "machine-1", AttachmentTag: "volume-2",
   654  	}}
   655  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   656  	waitChannel(c, validated, "waiting for volume parameter validation")
   657  	assertNoEvent(c, createdVolumes, "volume created")
   658  	c.Assert(validateCalls, gc.Equals, 1)
   659  
   660  	// Failure to create volume-1 should not block creation volume-2.
   661  	volumeAccessor.volumesWatcher.changes <- []string{"2"}
   662  	waitChannel(c, validated, "waiting for volume parameter validation")
   663  	createVolumeParams := waitChannel(c, createdVolumes, "volume created").([]storage.VolumeParams)
   664  	c.Assert(createVolumeParams, gc.HasLen, 1)
   665  	c.Assert(createVolumeParams[0].Tag.String(), gc.Equals, "volume-2")
   666  	c.Assert(validateCalls, gc.Equals, 2)
   667  
   668  	// destroying filesystems does not validate parameters
   669  	volumeAccessor.volumesWatcher.changes <- []string{"3"}
   670  	assertNoEvent(c, validated, "volume destruction params validated")
   671  	destroyVolumeParams := waitChannel(c, destroyedVolumes, "volume destroyed").([]string)
   672  	c.Assert(destroyVolumeParams, jc.DeepEquals, []string{"vol-ume"})
   673  	c.Assert(validateCalls, gc.Equals, 2) // no change
   674  
   675  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   676  		{Tag: "volume-1", Status: "error", Info: "something is wrong"},
   677  		{Tag: "volume-2", Status: "attaching"},
   678  		// destroyed volumes are removed immediately,
   679  		// so there is no status update.
   680  	})
   681  }
   682  
   683  func (s *storageProvisionerSuite) TestValidateFilesystemParams(c *gc.C) {
   684  	filesystemAccessor := newMockFilesystemAccessor()
   685  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   686  	filesystemAccessor.provisionedFilesystems["filesystem-3"] = params.Filesystem{
   687  		FilesystemTag: "filesystem-3",
   688  		Info:          params.FilesystemInfo{FilesystemId: "fs-id"},
   689  	}
   690  
   691  	var validateCalls int
   692  	validated := make(chan interface{}, 1)
   693  	s.provider.validateFilesystemParamsFunc = func(p storage.FilesystemParams) error {
   694  		validateCalls++
   695  		validated <- p
   696  		switch p.Tag.String() {
   697  		case "filesystem-1", "filesystem-3":
   698  			return errors.New("something is wrong")
   699  		}
   700  		return nil
   701  	}
   702  
   703  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
   704  		results := make([]params.LifeResult, len(tags))
   705  		for i := range results {
   706  			switch tags[i].String() {
   707  			case "filesystem-3":
   708  				results[i].Life = life.Dead
   709  			default:
   710  				results[i].Life = life.Alive
   711  			}
   712  		}
   713  		return results, nil
   714  	}
   715  
   716  	createdFilesystems := make(chan interface{}, 1)
   717  	s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) {
   718  		createdFilesystems <- args
   719  		if len(args) != 1 {
   720  			return nil, errors.New("expected one argument")
   721  		}
   722  		return []storage.CreateFilesystemsResult{{
   723  			Filesystem: &storage.Filesystem{Tag: args[0].Tag},
   724  		}}, nil
   725  	}
   726  
   727  	destroyedFilesystems := make(chan interface{}, 1)
   728  	s.provider.destroyFilesystemsFunc = func(filesystemIds []string) ([]error, error) {
   729  		destroyedFilesystems <- filesystemIds
   730  		return make([]error, len(filesystemIds)), nil
   731  	}
   732  
   733  	args := &workerArgs{
   734  		filesystems: filesystemAccessor,
   735  		life: &mockLifecycleManager{
   736  			life: life,
   737  		},
   738  		registry: s.registry,
   739  	}
   740  	worker := newStorageProvisioner(c, args)
   741  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   742  	defer worker.Kill()
   743  
   744  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   745  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
   746  	}, {
   747  		MachineTag: "machine-1", AttachmentTag: "filesystem-2",
   748  	}}
   749  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
   750  	waitChannel(c, validated, "waiting for filesystem parameter validation")
   751  	assertNoEvent(c, createdFilesystems, "filesystem created")
   752  	c.Assert(validateCalls, gc.Equals, 1)
   753  
   754  	// Failure to create filesystem-1 should not block creation filesystem-2.
   755  	filesystemAccessor.filesystemsWatcher.changes <- []string{"2"}
   756  	waitChannel(c, validated, "waiting for filesystem parameter validation")
   757  	createFilesystemParams := waitChannel(c, createdFilesystems, "filesystem created").([]storage.FilesystemParams)
   758  	c.Assert(createFilesystemParams, gc.HasLen, 1)
   759  	c.Assert(createFilesystemParams[0].Tag.String(), gc.Equals, "filesystem-2")
   760  	c.Assert(validateCalls, gc.Equals, 2)
   761  
   762  	// destroying filesystems does not validate parameters
   763  	filesystemAccessor.filesystemsWatcher.changes <- []string{"3"}
   764  	assertNoEvent(c, validated, "filesystem destruction params validated")
   765  	destroyFilesystemParams := waitChannel(c, destroyedFilesystems, "filesystem destroyed").([]string)
   766  	c.Assert(destroyFilesystemParams, jc.DeepEquals, []string{"fs-id"})
   767  	c.Assert(validateCalls, gc.Equals, 2) // no change
   768  
   769  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
   770  		{Tag: "filesystem-1", Status: "error", Info: "something is wrong"},
   771  		{Tag: "filesystem-2", Status: "attaching"},
   772  		// destroyed filesystems are removed immediately,
   773  		// so there is no status update.
   774  	})
   775  }
   776  
   777  func (s *storageProvisionerSuite) TestFilesystemAdded(c *gc.C) {
   778  	expectedFilesystems := []params.Filesystem{{
   779  		FilesystemTag: "filesystem-1",
   780  		Info: params.FilesystemInfo{
   781  			FilesystemId: "id-1",
   782  			Size:         1024,
   783  		},
   784  	}, {
   785  		FilesystemTag: "filesystem-2",
   786  		Info: params.FilesystemInfo{
   787  			FilesystemId: "id-2",
   788  			Size:         1024,
   789  		},
   790  	}}
   791  
   792  	filesystemInfoSet := make(chan interface{})
   793  	filesystemAccessor := newMockFilesystemAccessor()
   794  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
   795  		defer close(filesystemInfoSet)
   796  		c.Assert(filesystems, jc.SameContents, expectedFilesystems)
   797  		return nil, nil
   798  	}
   799  
   800  	args := &workerArgs{filesystems: filesystemAccessor, registry: s.registry}
   801  	worker := newStorageProvisioner(c, args)
   802  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   803  	defer worker.Kill()
   804  
   805  	// The worker should create filesystems according to ids "1" and "2".
   806  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1", "2"}
   807  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
   808  }
   809  
   810  func (s *storageProvisionerSuite) TestVolumeNeedsInstance(c *gc.C) {
   811  	volumeInfoSet := make(chan interface{})
   812  	volumeAccessor := newMockVolumeAccessor()
   813  	volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) {
   814  		defer close(volumeInfoSet)
   815  		return nil, nil
   816  	}
   817  	volumeAccessor.setVolumeAttachmentInfo = func([]params.VolumeAttachment) ([]params.ErrorResult, error) {
   818  		return nil, nil
   819  	}
   820  
   821  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
   822  	worker := newStorageProvisioner(c, args)
   823  	defer worker.Wait()
   824  	defer worker.Kill()
   825  
   826  	volumeAccessor.volumesWatcher.changes <- []string{needsInstanceVolumeId}
   827  	assertNoEvent(c, volumeInfoSet, "volume info set")
   828  	args.machines.instanceIds[names.NewMachineTag("1")] = "inst-id"
   829  	args.machines.watcher.changes <- struct{}{}
   830  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
   831  }
   832  
   833  // TestVolumeIncoherent tests that we do not panic when observing
   834  // a pending volume that has no attachments. We send a volume
   835  // update for a volume that is alive and unprovisioned, but has
   836  // no machine attachment. Such volumes are ignored by the storage
   837  // provisioner.
   838  //
   839  // See: https://bugs.launchpad.net/juju/+bug/1732616
   840  func (s *storageProvisionerSuite) TestVolumeIncoherent(c *gc.C) {
   841  	volumeAccessor := newMockVolumeAccessor()
   842  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
   843  	worker := newStorageProvisioner(c, args)
   844  	defer workertest.CleanKill(c, worker)
   845  
   846  	// Send 3 times, because the channel has a buffer size of 1.
   847  	// The third send guarantees we've sent at least the 2nd one
   848  	// through, which means at least the 1st has been processed
   849  	// (and ignored).
   850  	for i := 0; i < 3; i++ {
   851  		volumeAccessor.volumesWatcher.changes <- []string{noAttachmentVolumeId}
   852  	}
   853  }
   854  
   855  func (s *storageProvisionerSuite) TestVolumeNonDynamic(c *gc.C) {
   856  	volumeInfoSet := make(chan interface{})
   857  	volumeAccessor := newMockVolumeAccessor()
   858  	volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) {
   859  		defer close(volumeInfoSet)
   860  		return nil, nil
   861  	}
   862  
   863  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
   864  	worker := newStorageProvisioner(c, args)
   865  	defer worker.Wait()
   866  	defer worker.Kill()
   867  
   868  	// Volumes for non-dynamic providers should not be created.
   869  	s.provider.dynamic = false
   870  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   871  	assertNoEvent(c, volumeInfoSet, "volume info set")
   872  }
   873  
   874  func (s *storageProvisionerSuite) TestVolumeAttachmentAdded(c *gc.C) {
   875  	// We should get two volume attachments:
   876  	//   - volume-1 to machine-1, because the volume and
   877  	//     machine are provisioned, but the attachment is not.
   878  	//   - volume-1 to machine-0, because the volume,
   879  	//     machine, and attachment are provisioned, but
   880  	//     in a previous session, so a reattachment is
   881  	//     requested.
   882  	expectedVolumeAttachments := []params.VolumeAttachment{{
   883  		VolumeTag:  "volume-1",
   884  		MachineTag: "machine-1",
   885  		Info: params.VolumeAttachmentInfo{
   886  			DeviceName: "/dev/sda1",
   887  			ReadOnly:   true,
   888  		},
   889  	}, {
   890  		VolumeTag:  "volume-1",
   891  		MachineTag: "machine-0",
   892  		Info: params.VolumeAttachmentInfo{
   893  			DeviceName: "/dev/sda1",
   894  			ReadOnly:   true,
   895  		},
   896  	}}
   897  
   898  	var allVolumeAttachments []params.VolumeAttachment
   899  	volumeAttachmentInfoSet := make(chan interface{})
   900  	volumeAccessor := newMockVolumeAccessor()
   901  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   902  		allVolumeAttachments = append(allVolumeAttachments, volumeAttachments...)
   903  		volumeAttachmentInfoSet <- nil
   904  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   905  	}
   906  
   907  	// volume-1, machine-0, and machine-1 are provisioned.
   908  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
   909  		VolumeTag: "volume-1",
   910  		Info: params.VolumeInfo{
   911  			VolumeId: "vol-123",
   912  		},
   913  	}
   914  	volumeAccessor.provisionedMachines["machine-0"] = "already-provisioned-0"
   915  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   916  
   917  	// machine-0/volume-1 attachment is already created.
   918  	// We should see a reattachment.
   919  	alreadyAttached := params.MachineStorageId{
   920  		MachineTag:    "machine-0",
   921  		AttachmentTag: "volume-1",
   922  	}
   923  	volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{
   924  		MachineTag: "machine-0",
   925  		VolumeTag:  "volume-1",
   926  	}
   927  
   928  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
   929  	worker := newStorageProvisioner(c, args)
   930  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   931  	defer worker.Kill()
   932  
   933  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   934  		MachineTag: "machine-1", AttachmentTag: "volume-1",
   935  	}, {
   936  		MachineTag: "machine-1", AttachmentTag: "volume-2",
   937  	}, {
   938  		MachineTag: "machine-2", AttachmentTag: "volume-1",
   939  	}, {
   940  		MachineTag: "machine-0", AttachmentTag: "volume-1",
   941  	}}
   942  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   943  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   944  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
   945  	c.Assert(allVolumeAttachments, jc.SameContents, expectedVolumeAttachments)
   946  
   947  	// Reattachment should only happen once per session.
   948  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   949  		MachineTag:    "machine-0",
   950  		AttachmentTag: "volume-1",
   951  	}}
   952  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   953  }
   954  
   955  func (s *storageProvisionerSuite) TestVolumeAttachmentNoStaticReattachment(c *gc.C) {
   956  	// Static storage should never be reattached.
   957  	s.provider.dynamic = false
   958  
   959  	volumeAttachmentInfoSet := make(chan interface{})
   960  	volumeAccessor := newMockVolumeAccessor()
   961  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
   962  		volumeAttachmentInfoSet <- nil
   963  		return make([]params.ErrorResult, len(volumeAttachments)), nil
   964  	}
   965  
   966  	// volume-1, machine-0, and machine-1 are provisioned.
   967  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
   968  		VolumeTag: "volume-1",
   969  		Info: params.VolumeInfo{
   970  			VolumeId: "vol-123",
   971  		},
   972  	}
   973  	volumeAccessor.provisionedMachines["machine-0"] = "already-provisioned-0"
   974  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
   975  
   976  	alreadyAttached := params.MachineStorageId{
   977  		MachineTag:    "machine-0",
   978  		AttachmentTag: "volume-1",
   979  	}
   980  	volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{
   981  		MachineTag: "machine-0",
   982  		VolumeTag:  "volume-1",
   983  	}
   984  
   985  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
   986  	worker := newStorageProvisioner(c, args)
   987  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   988  	defer worker.Kill()
   989  
   990  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
   991  		MachineTag: "machine-0", AttachmentTag: "volume-1",
   992  	}}
   993  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
   994  	assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set")
   995  }
   996  
   997  func (s *storageProvisionerSuite) TestFilesystemAttachmentAdded(c *gc.C) {
   998  	// We should only get a single filesystem attachment, because it is the
   999  	// only combination where both machine and filesystem are already
  1000  	// provisioned, and the attachmenti s not.
  1001  	// We should get two filesystem attachments:
  1002  	//   - filesystem-1 to machine-1, because the filesystem and
  1003  	//     machine are provisioned, but the attachment is not.
  1004  	//   - filesystem-1 to machine-0, because the filesystem,
  1005  	//     machine, and attachment are provisioned, but in a
  1006  	//     previous session, so a reattachment is requested.
  1007  	expectedFilesystemAttachments := []params.FilesystemAttachment{{
  1008  		FilesystemTag: "filesystem-1",
  1009  		MachineTag:    "machine-1",
  1010  		Info: params.FilesystemAttachmentInfo{
  1011  			MountPoint: "/srv/fs-123",
  1012  		},
  1013  	}, {
  1014  		FilesystemTag: "filesystem-1",
  1015  		MachineTag:    "machine-0",
  1016  		Info: params.FilesystemAttachmentInfo{
  1017  			MountPoint: "/srv/fs-123",
  1018  		},
  1019  	}}
  1020  
  1021  	var allFilesystemAttachments []params.FilesystemAttachment
  1022  	filesystemAttachmentInfoSet := make(chan interface{})
  1023  	filesystemAccessor := newMockFilesystemAccessor()
  1024  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
  1025  		allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...)
  1026  		filesystemAttachmentInfoSet <- nil
  1027  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
  1028  	}
  1029  
  1030  	// filesystem-1 and machine-1 are provisioned.
  1031  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{
  1032  		FilesystemTag: "filesystem-1",
  1033  		Info: params.FilesystemInfo{
  1034  			FilesystemId: "fs-123",
  1035  		},
  1036  	}
  1037  	filesystemAccessor.provisionedMachines["machine-0"] = "already-provisioned-0"
  1038  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1039  
  1040  	// machine-0/filesystem-1 attachment is already created.
  1041  	// We should see a reattachment.
  1042  	alreadyAttached := params.MachineStorageId{
  1043  		MachineTag:    "machine-0",
  1044  		AttachmentTag: "filesystem-1",
  1045  	}
  1046  	filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{
  1047  		MachineTag:    "machine-0",
  1048  		FilesystemTag: "filesystem-1",
  1049  	}
  1050  
  1051  	args := &workerArgs{filesystems: filesystemAccessor, registry: s.registry}
  1052  	worker := newStorageProvisioner(c, args)
  1053  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1054  	defer worker.Kill()
  1055  
  1056  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1057  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1058  	}, {
  1059  		MachineTag: "machine-1", AttachmentTag: "filesystem-2",
  1060  	}, {
  1061  		MachineTag: "machine-2", AttachmentTag: "filesystem-1",
  1062  	}, {
  1063  		MachineTag: "machine-0", AttachmentTag: "filesystem-1",
  1064  	}}
  1065  	assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set")
  1066  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
  1067  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
  1068  	c.Assert(allFilesystemAttachments, jc.SameContents, expectedFilesystemAttachments)
  1069  
  1070  	// Reattachment should only happen once per session.
  1071  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1072  		MachineTag:    "machine-0",
  1073  		AttachmentTag: "filesystem-1",
  1074  	}}
  1075  	assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set")
  1076  }
  1077  
  1078  func (s *storageProvisionerSuite) TestCreateVolumeBackedFilesystem(c *gc.C) {
  1079  	filesystemInfoSet := make(chan interface{})
  1080  	filesystemAccessor := newMockFilesystemAccessor()
  1081  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
  1082  		filesystemInfoSet <- filesystems
  1083  		return nil, nil
  1084  	}
  1085  
  1086  	args := &workerArgs{
  1087  		scope:       names.NewMachineTag("0"),
  1088  		filesystems: filesystemAccessor,
  1089  		registry:    s.registry,
  1090  	}
  1091  	worker := newStorageProvisioner(c, args)
  1092  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1093  	defer worker.Kill()
  1094  
  1095  	args.volumes.blockDevices[params.MachineStorageId{
  1096  		MachineTag:    "machine-0",
  1097  		AttachmentTag: "volume-0-0",
  1098  	}] = storage.BlockDevice{
  1099  		DeviceName: "xvdf1",
  1100  		Size:       123,
  1101  	}
  1102  	filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0", "0/1"}
  1103  
  1104  	// Only the block device for volume 0/0 is attached at the moment,
  1105  	// so only the corresponding filesystem will be created.
  1106  	filesystemInfo := waitChannel(
  1107  		c, filesystemInfoSet,
  1108  		"waiting for filesystem info to be set",
  1109  	).([]params.Filesystem)
  1110  	c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{
  1111  		FilesystemTag: "filesystem-0-0",
  1112  		Info: params.FilesystemInfo{
  1113  			FilesystemId: "xvdf1",
  1114  			Size:         123,
  1115  		},
  1116  	}})
  1117  
  1118  	// If we now attach the block device for volume 0/1 and trigger the
  1119  	// notification, then the storage provisioner will wake up and create
  1120  	// the filesystem.
  1121  	args.volumes.blockDevices[params.MachineStorageId{
  1122  		MachineTag:    "machine-0",
  1123  		AttachmentTag: "volume-0-1",
  1124  	}] = storage.BlockDevice{
  1125  		DeviceName: "xvdf2",
  1126  		Size:       246,
  1127  	}
  1128  	args.volumes.blockDevicesWatcher.changes <- struct{}{}
  1129  	filesystemInfo = waitChannel(
  1130  		c, filesystemInfoSet,
  1131  		"waiting for filesystem info to be set",
  1132  	).([]params.Filesystem)
  1133  	c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{
  1134  		FilesystemTag: "filesystem-0-1",
  1135  		Info: params.FilesystemInfo{
  1136  			FilesystemId: "xvdf2",
  1137  			Size:         246,
  1138  		},
  1139  	}})
  1140  }
  1141  
  1142  func (s *storageProvisionerSuite) TestAttachVolumeBackedFilesystem(c *gc.C) {
  1143  	infoSet := make(chan interface{})
  1144  	filesystemAccessor := newMockFilesystemAccessor()
  1145  	filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
  1146  		infoSet <- attachments
  1147  		return make([]params.ErrorResult, len(attachments)), nil
  1148  	}
  1149  
  1150  	args := &workerArgs{
  1151  		scope:       names.NewMachineTag("0"),
  1152  		filesystems: filesystemAccessor,
  1153  		registry:    s.registry,
  1154  	}
  1155  	worker := newStorageProvisioner(c, args)
  1156  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1157  	defer worker.Kill()
  1158  
  1159  	filesystemAccessor.provisionedFilesystems["filesystem-0-0"] = params.Filesystem{
  1160  		FilesystemTag: "filesystem-0-0",
  1161  		VolumeTag:     "volume-0-0",
  1162  		Info: params.FilesystemInfo{
  1163  			FilesystemId: "whatever",
  1164  			Size:         123,
  1165  		},
  1166  	}
  1167  	filesystemAccessor.provisionedMachines["machine-0"] = "already-provisioned-0"
  1168  
  1169  	args.volumes.blockDevices[params.MachineStorageId{
  1170  		MachineTag:    "machine-0",
  1171  		AttachmentTag: "volume-0-0",
  1172  	}] = storage.BlockDevice{
  1173  		DeviceName: "xvdf1",
  1174  		Size:       123,
  1175  	}
  1176  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1177  		MachineTag:    "machine-0",
  1178  		AttachmentTag: "filesystem-0-0",
  1179  	}}
  1180  	filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0"}
  1181  
  1182  	info := waitChannel(
  1183  		c, infoSet, "waiting for filesystem attachment info to be set",
  1184  	).([]params.FilesystemAttachment)
  1185  	c.Assert(info, jc.DeepEquals, []params.FilesystemAttachment{{
  1186  		FilesystemTag: "filesystem-0-0",
  1187  		MachineTag:    "machine-0",
  1188  		Info: params.FilesystemAttachmentInfo{
  1189  			MountPoint: "/mnt/xvdf1",
  1190  			ReadOnly:   true,
  1191  		},
  1192  	}})
  1193  
  1194  	// Update the UUID of the block device and check attachment update.
  1195  	args.volumes.blockDevices[params.MachineStorageId{
  1196  		MachineTag:    "machine-0",
  1197  		AttachmentTag: "volume-0-0",
  1198  	}] = storage.BlockDevice{
  1199  		DeviceName: "xvdf1",
  1200  		Size:       123,
  1201  		UUID:       "deadbeaf",
  1202  	}
  1203  	s.managedFilesystemSource.attachedFilesystems = make(chan interface{}, 1)
  1204  	args.volumes.blockDevicesWatcher.changes <- struct{}{}
  1205  	attachInfo := waitChannel(
  1206  		c, s.managedFilesystemSource.attachedFilesystems,
  1207  		"waiting for filesystem attachements",
  1208  	).([]storage.AttachFilesystemsResult)
  1209  	c.Assert(attachInfo, jc.DeepEquals, []storage.AttachFilesystemsResult{{
  1210  		FilesystemAttachment: &storage.FilesystemAttachment{
  1211  			Filesystem: names.NewFilesystemTag("0/0"),
  1212  			FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{
  1213  				Path:     "/mnt/xvdf1",
  1214  				ReadOnly: true,
  1215  			},
  1216  		},
  1217  	}})
  1218  
  1219  }
  1220  
  1221  func (s *storageProvisionerSuite) TestResourceTags(c *gc.C) {
  1222  	volumeInfoSet := make(chan interface{})
  1223  	volumeAccessor := newMockVolumeAccessor()
  1224  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1225  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
  1226  		defer close(volumeInfoSet)
  1227  		return nil, nil
  1228  	}
  1229  
  1230  	filesystemInfoSet := make(chan interface{})
  1231  	filesystemAccessor := newMockFilesystemAccessor()
  1232  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1233  	filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) {
  1234  		defer close(filesystemInfoSet)
  1235  		return nil, nil
  1236  	}
  1237  
  1238  	var volumeSource dummyVolumeSource
  1239  	s.provider.volumeSourceFunc = func(sourceConfig *storage.Config) (storage.VolumeSource, error) {
  1240  		return &volumeSource, nil
  1241  	}
  1242  
  1243  	var filesystemSource dummyFilesystemSource
  1244  	s.provider.filesystemSourceFunc = func(sourceConfig *storage.Config) (storage.FilesystemSource, error) {
  1245  		return &filesystemSource, nil
  1246  	}
  1247  
  1248  	args := &workerArgs{
  1249  		volumes:     volumeAccessor,
  1250  		filesystems: filesystemAccessor,
  1251  		registry:    s.registry,
  1252  	}
  1253  	worker := newStorageProvisioner(c, args)
  1254  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1255  	defer worker.Kill()
  1256  
  1257  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
  1258  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
  1259  	waitChannel(c, volumeInfoSet, "waiting for volume info to be set")
  1260  	waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set")
  1261  	c.Assert(volumeSource.createVolumesArgs, jc.DeepEquals, [][]storage.VolumeParams{{{
  1262  		Tag:          names.NewVolumeTag("1"),
  1263  		Size:         1024,
  1264  		Provider:     "dummy",
  1265  		Attributes:   map[string]interface{}{"persistent": true},
  1266  		ResourceTags: map[string]string{"very": "fancy"},
  1267  		Attachment: &storage.VolumeAttachmentParams{
  1268  			Volume: names.NewVolumeTag("1"),
  1269  			AttachmentParams: storage.AttachmentParams{
  1270  				Machine:    names.NewMachineTag("1"),
  1271  				Provider:   "dummy",
  1272  				InstanceId: "already-provisioned-1",
  1273  				ReadOnly:   true,
  1274  			},
  1275  		},
  1276  	}}})
  1277  	c.Assert(filesystemSource.createFilesystemsArgs, jc.DeepEquals, [][]storage.FilesystemParams{{{
  1278  		Tag:          names.NewFilesystemTag("1"),
  1279  		Size:         1024,
  1280  		Provider:     "dummy",
  1281  		ResourceTags: map[string]string{"very": "fancy"},
  1282  	}}})
  1283  }
  1284  
  1285  func (s *storageProvisionerSuite) TestSetVolumeInfoErrorStopsWorker(c *gc.C) {
  1286  	volumeAccessor := newMockVolumeAccessor()
  1287  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1288  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
  1289  		return nil, errors.New("belly up")
  1290  	}
  1291  
  1292  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
  1293  	worker := newStorageProvisioner(c, args)
  1294  	defer worker.Wait()
  1295  	defer worker.Kill()
  1296  
  1297  	done := make(chan interface{})
  1298  	go func() {
  1299  		defer close(done)
  1300  		err := worker.Wait()
  1301  		c.Assert(err, gc.ErrorMatches, "creating volumes: publishing volumes to state: belly up")
  1302  	}()
  1303  
  1304  	args.volumes.volumesWatcher.changes <- []string{"1"}
  1305  	waitChannel(c, done, "waiting for worker to exit")
  1306  }
  1307  
  1308  func (s *storageProvisionerSuite) TestSetVolumeInfoErrorResultDoesNotStopWorker(c *gc.C) {
  1309  	volumeAccessor := newMockVolumeAccessor()
  1310  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1311  	volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) {
  1312  		return []params.ErrorResult{{Error: &params.Error{Message: "message", Code: "code"}}}, nil
  1313  	}
  1314  
  1315  	args := &workerArgs{volumes: volumeAccessor, registry: s.registry}
  1316  	worker := newStorageProvisioner(c, args)
  1317  	defer func() {
  1318  		err := worker.Wait()
  1319  		c.Assert(err, jc.ErrorIsNil)
  1320  	}()
  1321  	defer worker.Kill()
  1322  
  1323  	done := make(chan interface{})
  1324  	go func() {
  1325  		defer close(done)
  1326  		worker.Wait()
  1327  	}()
  1328  
  1329  	args.volumes.volumesWatcher.changes <- []string{"1"}
  1330  	assertNoEvent(c, done, "worker exited")
  1331  }
  1332  
  1333  func (s *storageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) {
  1334  	removed := make(chan interface{})
  1335  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1336  		defer close(removed)
  1337  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
  1338  			MachineTag:    "machine-0",
  1339  			AttachmentTag: "volume-0",
  1340  		}})
  1341  		return make([]params.ErrorResult, len(ids)), nil
  1342  	}
  1343  
  1344  	args := &workerArgs{
  1345  		life:     &mockLifecycleManager{removeAttachments: removeAttachments},
  1346  		registry: s.registry,
  1347  	}
  1348  	worker := newStorageProvisioner(c, args)
  1349  	defer worker.Wait()
  1350  	defer worker.Kill()
  1351  
  1352  	args.volumes.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1353  		MachineTag: "machine-0", AttachmentTag: "volume-0",
  1354  	}}
  1355  	waitChannel(c, removed, "waiting for attachment to be removed")
  1356  }
  1357  
  1358  func (s *storageProvisionerSuite) TestDetachVolumes(c *gc.C) {
  1359  	var attached bool
  1360  	volumeAttachmentInfoSet := make(chan interface{})
  1361  	volumeAccessor := newMockVolumeAccessor()
  1362  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
  1363  		close(volumeAttachmentInfoSet)
  1364  		attached = true
  1365  		for _, a := range volumeAttachments {
  1366  			id := params.MachineStorageId{
  1367  				MachineTag:    a.MachineTag,
  1368  				AttachmentTag: a.VolumeTag,
  1369  			}
  1370  			volumeAccessor.provisionedAttachments[id] = a
  1371  		}
  1372  		return make([]params.ErrorResult, len(volumeAttachments)), nil
  1373  	}
  1374  
  1375  	expectedAttachmentIds := []params.MachineStorageId{{
  1376  		MachineTag: "machine-1", AttachmentTag: "volume-1",
  1377  	}}
  1378  
  1379  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  1380  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1381  		value := life.Alive
  1382  		if attached {
  1383  			value = life.Dying
  1384  		}
  1385  		return []params.LifeResult{{Life: value}}, nil
  1386  	}
  1387  
  1388  	detached := make(chan interface{})
  1389  	s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) {
  1390  		c.Assert(args, gc.HasLen, 1)
  1391  		c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag)
  1392  		c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag)
  1393  		defer close(detached)
  1394  		return make([]error, len(args)), nil
  1395  	}
  1396  
  1397  	removed := make(chan interface{})
  1398  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1399  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1400  		close(removed)
  1401  		return make([]params.ErrorResult, len(ids)), nil
  1402  	}
  1403  
  1404  	// volume-1 and machine-1 are provisioned.
  1405  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
  1406  		VolumeTag: "volume-1",
  1407  		Info: params.VolumeInfo{
  1408  			VolumeId: "vol-123",
  1409  		},
  1410  	}
  1411  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1412  
  1413  	args := &workerArgs{
  1414  		volumes: volumeAccessor,
  1415  		life: &mockLifecycleManager{
  1416  			attachmentLife:    attachmentLife,
  1417  			removeAttachments: removeAttachments,
  1418  		},
  1419  		registry: s.registry,
  1420  	}
  1421  	worker := newStorageProvisioner(c, args)
  1422  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1423  	defer worker.Kill()
  1424  
  1425  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1426  		MachineTag: "machine-1", AttachmentTag: "volume-1",
  1427  	}}
  1428  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
  1429  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
  1430  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1431  		MachineTag: "machine-1", AttachmentTag: "volume-1",
  1432  	}}
  1433  	waitChannel(c, detached, "waiting for volume to be detached")
  1434  	waitChannel(c, removed, "waiting for attachment to be removed")
  1435  }
  1436  
  1437  func (s *storageProvisionerSuite) TestDetachVolumesRetry(c *gc.C) {
  1438  	machine := names.NewMachineTag("1")
  1439  	volume := names.NewVolumeTag("1")
  1440  	attachmentId := params.MachineStorageId{
  1441  		MachineTag:    machine.String(),
  1442  		AttachmentTag: volume.String(),
  1443  	}
  1444  	volumeAccessor := newMockVolumeAccessor()
  1445  	volumeAccessor.provisionedAttachments[attachmentId] = params.VolumeAttachment{
  1446  		MachineTag: machine.String(),
  1447  		VolumeTag:  volume.String(),
  1448  	}
  1449  	volumeAccessor.provisionedVolumes[volume.String()] = params.Volume{
  1450  		VolumeTag: volume.String(),
  1451  		Info: params.VolumeInfo{
  1452  			VolumeId: "vol-123",
  1453  		},
  1454  	}
  1455  	volumeAccessor.provisionedMachines[machine.String()] = "already-provisioned-1"
  1456  
  1457  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  1458  		return []params.LifeResult{{Life: life.Dying}}, nil
  1459  	}
  1460  
  1461  	// mockFunc's After will progress the current time by the specified
  1462  	// duration and signal the channel immediately.
  1463  	clock := &mockClock{}
  1464  	var detachVolumeTimes []time.Time
  1465  
  1466  	s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) {
  1467  		detachVolumeTimes = append(detachVolumeTimes, clock.Now())
  1468  		if len(detachVolumeTimes) < 10 {
  1469  			return []error{errors.New("badness")}, nil
  1470  		}
  1471  		return []error{nil}, nil
  1472  	}
  1473  
  1474  	removed := make(chan interface{})
  1475  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1476  		close(removed)
  1477  		return make([]params.ErrorResult, len(ids)), nil
  1478  	}
  1479  
  1480  	args := &workerArgs{
  1481  		volumes: volumeAccessor,
  1482  		clock:   clock,
  1483  		life: &mockLifecycleManager{
  1484  			attachmentLife:    attachmentLife,
  1485  			removeAttachments: removeAttachments,
  1486  		},
  1487  		registry: s.registry,
  1488  	}
  1489  	worker := newStorageProvisioner(c, args)
  1490  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1491  	defer worker.Kill()
  1492  
  1493  	volumeAccessor.volumesWatcher.changes <- []string{volume.Id()}
  1494  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1495  		MachineTag:    machine.String(),
  1496  		AttachmentTag: volume.String(),
  1497  	}}
  1498  	waitChannel(c, removed, "waiting for attachment to be removed")
  1499  	c.Assert(detachVolumeTimes, gc.HasLen, 10)
  1500  
  1501  	// The first attempt should have been immediate: T0.
  1502  	c.Assert(detachVolumeTimes[0], gc.Equals, time.Time{})
  1503  
  1504  	delays := make([]time.Duration, len(detachVolumeTimes)-1)
  1505  	for i := range detachVolumeTimes[1:] {
  1506  		delays[i] = detachVolumeTimes[i+1].Sub(detachVolumeTimes[i])
  1507  	}
  1508  	c.Assert(delays, jc.DeepEquals, []time.Duration{
  1509  		30 * time.Second,
  1510  		1 * time.Minute,
  1511  		2 * time.Minute,
  1512  		4 * time.Minute,
  1513  		8 * time.Minute,
  1514  		16 * time.Minute,
  1515  		30 * time.Minute, // ceiling reached
  1516  		30 * time.Minute,
  1517  		30 * time.Minute,
  1518  	})
  1519  
  1520  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
  1521  		{Tag: "volume-1", Status: "detaching", Info: "badness"}, // DetachVolumes
  1522  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1523  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1524  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1525  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1526  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1527  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1528  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1529  		{Tag: "volume-1", Status: "detaching", Info: "badness"},
  1530  		{Tag: "volume-1", Status: "detached", Info: ""},
  1531  	})
  1532  }
  1533  
  1534  func (s *storageProvisionerSuite) TestDetachVolumesNotFound(c *gc.C) {
  1535  	// This test just checks that there are no unexpected api calls
  1536  	// if a volume attachment is deleted from state.
  1537  	var attached bool
  1538  	volumeAttachmentInfoSet := make(chan interface{})
  1539  	volumeAccessor := newMockVolumeAccessor()
  1540  	volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) {
  1541  		close(volumeAttachmentInfoSet)
  1542  		attached = true
  1543  		for _, a := range volumeAttachments {
  1544  			id := params.MachineStorageId{
  1545  				MachineTag:    a.MachineTag,
  1546  				AttachmentTag: a.VolumeTag,
  1547  			}
  1548  			volumeAccessor.provisionedAttachments[id] = a
  1549  		}
  1550  		return make([]params.ErrorResult, len(volumeAttachments)), nil
  1551  	}
  1552  
  1553  	expectedAttachmentIds := []params.MachineStorageId{{
  1554  		MachineTag: "machine-1", AttachmentTag: "volume-1",
  1555  	}}
  1556  
  1557  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  1558  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1559  		value := life.Alive
  1560  		var lifeErr *params.Error
  1561  		if attached {
  1562  			lifeErr = &params.Error{Code: params.CodeNotFound}
  1563  		}
  1564  		return []params.LifeResult{{Life: value, Error: lifeErr}}, nil
  1565  	}
  1566  
  1567  	s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) {
  1568  		c.Fatalf("unexpected call to detachVolumes")
  1569  		return nil, nil
  1570  	}
  1571  	s.provider.destroyVolumesFunc = func(ids []string) ([]error, error) {
  1572  		c.Fatalf("unexpected call to destroyVolumes")
  1573  		return nil, nil
  1574  	}
  1575  
  1576  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1577  		c.Fatalf("unexpected call to removeAttachments")
  1578  		return nil, nil
  1579  	}
  1580  
  1581  	// volume-1 and machine-1 are provisioned.
  1582  	volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{
  1583  		VolumeTag: "volume-1",
  1584  		Info: params.VolumeInfo{
  1585  			VolumeId: "vol-123",
  1586  		},
  1587  	}
  1588  	volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1589  
  1590  	args := &workerArgs{
  1591  		volumes: volumeAccessor,
  1592  		life: &mockLifecycleManager{
  1593  			attachmentLife:    attachmentLife,
  1594  			removeAttachments: removeAttachments,
  1595  		},
  1596  		registry: s.registry,
  1597  	}
  1598  	worker := newStorageProvisioner(c, args)
  1599  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1600  
  1601  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1602  		MachineTag: "machine-1", AttachmentTag: "volume-1",
  1603  	}}
  1604  	volumeAccessor.volumesWatcher.changes <- []string{"1"}
  1605  	waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set")
  1606  
  1607  	// This results in a not found attachment.
  1608  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1609  		MachineTag: "machine-1", AttachmentTag: "volume-1",
  1610  	}}
  1611  	workertest.CleanKill(c, worker)
  1612  }
  1613  
  1614  func (s *storageProvisionerSuite) TestDetachFilesystemsUnattached(c *gc.C) {
  1615  	removed := make(chan interface{})
  1616  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1617  		defer close(removed)
  1618  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
  1619  			MachineTag:    "machine-0",
  1620  			AttachmentTag: "filesystem-0",
  1621  		}})
  1622  		return make([]params.ErrorResult, len(ids)), nil
  1623  	}
  1624  
  1625  	args := &workerArgs{
  1626  		life:     &mockLifecycleManager{removeAttachments: removeAttachments},
  1627  		registry: s.registry,
  1628  	}
  1629  	worker := newStorageProvisioner(c, args)
  1630  	defer worker.Wait()
  1631  	defer worker.Kill()
  1632  
  1633  	args.filesystems.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1634  		MachineTag: "machine-0", AttachmentTag: "filesystem-0",
  1635  	}}
  1636  	waitChannel(c, removed, "waiting for attachment to be removed")
  1637  }
  1638  
  1639  func (s *storageProvisionerSuite) TestDetachFilesystems(c *gc.C) {
  1640  	var attached bool
  1641  	filesystemAttachmentInfoSet := make(chan interface{})
  1642  	filesystemAccessor := newMockFilesystemAccessor()
  1643  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
  1644  		close(filesystemAttachmentInfoSet)
  1645  		attached = true
  1646  		for _, a := range filesystemAttachments {
  1647  			id := params.MachineStorageId{
  1648  				MachineTag:    a.MachineTag,
  1649  				AttachmentTag: a.FilesystemTag,
  1650  			}
  1651  			filesystemAccessor.provisionedAttachments[id] = a
  1652  		}
  1653  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
  1654  	}
  1655  
  1656  	expectedAttachmentIds := []params.MachineStorageId{{
  1657  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1658  	}}
  1659  
  1660  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  1661  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1662  		value := life.Alive
  1663  		if attached {
  1664  			value = life.Dying
  1665  		}
  1666  		return []params.LifeResult{{Life: value}}, nil
  1667  	}
  1668  
  1669  	detached := make(chan interface{})
  1670  	s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]error, error) {
  1671  		c.Assert(args, gc.HasLen, 1)
  1672  		c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag)
  1673  		c.Assert(args[0].Filesystem.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag)
  1674  		defer close(detached)
  1675  		return make([]error, len(args)), nil
  1676  	}
  1677  
  1678  	removed := make(chan interface{})
  1679  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1680  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1681  		close(removed)
  1682  		return make([]params.ErrorResult, len(ids)), nil
  1683  	}
  1684  
  1685  	// filesystem-1 and machine-1 are provisioned.
  1686  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{
  1687  		FilesystemTag: "filesystem-1",
  1688  		Info: params.FilesystemInfo{
  1689  			FilesystemId: "fs-id",
  1690  		},
  1691  	}
  1692  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1693  
  1694  	args := &workerArgs{
  1695  		filesystems: filesystemAccessor,
  1696  		life: &mockLifecycleManager{
  1697  			attachmentLife:    attachmentLife,
  1698  			removeAttachments: removeAttachments,
  1699  		},
  1700  		registry: s.registry,
  1701  	}
  1702  	worker := newStorageProvisioner(c, args)
  1703  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1704  	defer worker.Kill()
  1705  
  1706  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1707  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1708  	}}
  1709  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
  1710  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
  1711  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1712  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1713  	}}
  1714  	waitChannel(c, detached, "waiting for filesystem to be detached")
  1715  	waitChannel(c, removed, "waiting for attachment to be removed")
  1716  }
  1717  
  1718  func (s *storageProvisionerSuite) TestDetachFilesystemsNotFound(c *gc.C) {
  1719  	// This test just checks that there are no unexpected api calls
  1720  	// if a volume attachment is deleted from state.
  1721  	var attached bool
  1722  	filesystemAttachmentInfoSet := make(chan interface{})
  1723  	filesystemAccessor := newMockFilesystemAccessor()
  1724  	filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) {
  1725  		close(filesystemAttachmentInfoSet)
  1726  		attached = true
  1727  		for _, a := range filesystemAttachments {
  1728  			id := params.MachineStorageId{
  1729  				MachineTag:    a.MachineTag,
  1730  				AttachmentTag: a.FilesystemTag,
  1731  			}
  1732  			filesystemAccessor.provisionedAttachments[id] = a
  1733  		}
  1734  		return make([]params.ErrorResult, len(filesystemAttachments)), nil
  1735  	}
  1736  
  1737  	expectedAttachmentIds := []params.MachineStorageId{{
  1738  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1739  	}}
  1740  
  1741  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  1742  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  1743  		value := life.Alive
  1744  		var lifeErr *params.Error
  1745  		if attached {
  1746  			lifeErr = &params.Error{Code: params.CodeNotFound}
  1747  		}
  1748  		return []params.LifeResult{{Life: value, Error: lifeErr}}, nil
  1749  	}
  1750  
  1751  	s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]error, error) {
  1752  		c.Fatalf("unexpected call to detachFilesystems")
  1753  		return nil, nil
  1754  	}
  1755  	s.provider.destroyFilesystemsFunc = func(ids []string) ([]error, error) {
  1756  		c.Fatalf("unexpected call to destroyFilesystems")
  1757  		return nil, nil
  1758  	}
  1759  
  1760  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  1761  		c.Fatalf("unexpected call to removeAttachments")
  1762  		return nil, nil
  1763  	}
  1764  
  1765  	// filesystem-1 and machine-1 are provisioned.
  1766  	filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{
  1767  		FilesystemTag: "filesystem-1",
  1768  		Info: params.FilesystemInfo{
  1769  			FilesystemId: "fs-id",
  1770  		},
  1771  	}
  1772  	filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1"
  1773  
  1774  	args := &workerArgs{
  1775  		filesystems: filesystemAccessor,
  1776  		life: &mockLifecycleManager{
  1777  			attachmentLife:    attachmentLife,
  1778  			removeAttachments: removeAttachments,
  1779  		},
  1780  		registry: s.registry,
  1781  	}
  1782  	worker := newStorageProvisioner(c, args)
  1783  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1784  
  1785  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1786  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1787  	}}
  1788  	filesystemAccessor.filesystemsWatcher.changes <- []string{"1"}
  1789  	waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set")
  1790  
  1791  	// This results in a not found attachment.
  1792  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  1793  		MachineTag: "machine-1", AttachmentTag: "filesystem-1",
  1794  	}}
  1795  	workertest.CleanKill(c, worker)
  1796  }
  1797  
  1798  func (s *storageProvisionerSuite) TestDestroyVolumes(c *gc.C) {
  1799  	unprovisionedVolume := names.NewVolumeTag("0")
  1800  	provisionedDestroyVolume := names.NewVolumeTag("1")
  1801  	provisionedReleaseVolume := names.NewVolumeTag("2")
  1802  
  1803  	volumeAccessor := newMockVolumeAccessor()
  1804  	volumeAccessor.provisionVolume(provisionedDestroyVolume)
  1805  	volumeAccessor.provisionVolume(provisionedReleaseVolume)
  1806  
  1807  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
  1808  		results := make([]params.LifeResult, len(tags))
  1809  		for i := range results {
  1810  			results[i].Life = life.Dead
  1811  		}
  1812  		return results, nil
  1813  	}
  1814  
  1815  	destroyedChan := make(chan interface{}, 1)
  1816  	s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) {
  1817  		destroyedChan <- volumeIds
  1818  		return make([]error, len(volumeIds)), nil
  1819  	}
  1820  
  1821  	releasedChan := make(chan interface{}, 1)
  1822  	s.provider.releaseVolumesFunc = func(volumeIds []string) ([]error, error) {
  1823  		releasedChan <- volumeIds
  1824  		return make([]error, len(volumeIds)), nil
  1825  	}
  1826  
  1827  	removedChan := make(chan interface{}, 1)
  1828  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
  1829  		removedChan <- tags
  1830  		return make([]params.ErrorResult, len(tags)), nil
  1831  	}
  1832  
  1833  	args := &workerArgs{
  1834  		volumes: volumeAccessor,
  1835  		life: &mockLifecycleManager{
  1836  			life:   life,
  1837  			remove: remove,
  1838  		},
  1839  		registry: s.registry,
  1840  	}
  1841  	worker := newStorageProvisioner(c, args)
  1842  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1843  	defer worker.Kill()
  1844  
  1845  	volumeAccessor.volumesWatcher.changes <- []string{
  1846  		unprovisionedVolume.Id(),
  1847  		provisionedDestroyVolume.Id(),
  1848  		provisionedReleaseVolume.Id(),
  1849  	}
  1850  
  1851  	// All volumes should be removed; the provisioned ones
  1852  	// should be destroyed/released first.
  1853  
  1854  	destroyed := waitChannel(c, destroyedChan, "waiting for volume to be destroyed")
  1855  	assertNoEvent(c, destroyedChan, "volumes destroyed")
  1856  	c.Assert(destroyed, jc.DeepEquals, []string{"vol-1"})
  1857  
  1858  	released := waitChannel(c, releasedChan, "waiting for volume to be released")
  1859  	assertNoEvent(c, releasedChan, "volumes released")
  1860  	c.Assert(released, jc.DeepEquals, []string{"vol-2"})
  1861  
  1862  	var removed []names.Tag
  1863  	for len(removed) < 3 {
  1864  		tags := waitChannel(c, removedChan, "waiting for volumes to be removed").([]names.Tag)
  1865  		removed = append(removed, tags...)
  1866  	}
  1867  	c.Assert(removed, jc.SameContents, []names.Tag{
  1868  		unprovisionedVolume,
  1869  		provisionedDestroyVolume,
  1870  		provisionedReleaseVolume,
  1871  	})
  1872  	assertNoEvent(c, removedChan, "volumes removed")
  1873  }
  1874  
  1875  func (s *storageProvisionerSuite) TestDestroyVolumesRetry(c *gc.C) {
  1876  	volume := names.NewVolumeTag("1")
  1877  	volumeAccessor := newMockVolumeAccessor()
  1878  	volumeAccessor.provisionVolume(volume)
  1879  
  1880  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
  1881  		return []params.LifeResult{{Life: life.Dead}}, nil
  1882  	}
  1883  
  1884  	// mockFunc's After will progress the current time by the specified
  1885  	// duration and signal the channel immediately.
  1886  	clock := &mockClock{}
  1887  	var destroyVolumeTimes []time.Time
  1888  
  1889  	s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) {
  1890  		destroyVolumeTimes = append(destroyVolumeTimes, clock.Now())
  1891  		if len(destroyVolumeTimes) < 10 {
  1892  			return []error{errors.New("badness")}, nil
  1893  		}
  1894  		return []error{nil}, nil
  1895  	}
  1896  
  1897  	removedChan := make(chan interface{}, 1)
  1898  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
  1899  		removedChan <- tags
  1900  		return make([]params.ErrorResult, len(tags)), nil
  1901  	}
  1902  
  1903  	args := &workerArgs{
  1904  		volumes: volumeAccessor,
  1905  		clock:   clock,
  1906  		life: &mockLifecycleManager{
  1907  			life:   life,
  1908  			remove: remove,
  1909  		},
  1910  		registry: s.registry,
  1911  	}
  1912  	worker := newStorageProvisioner(c, args)
  1913  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1914  	defer worker.Kill()
  1915  
  1916  	volumeAccessor.volumesWatcher.changes <- []string{volume.Id()}
  1917  	waitChannel(c, removedChan, "waiting for volume to be removed")
  1918  	c.Assert(destroyVolumeTimes, gc.HasLen, 10)
  1919  
  1920  	// The first attempt should have been immediate: T0.
  1921  	c.Assert(destroyVolumeTimes[0], gc.Equals, time.Time{})
  1922  
  1923  	delays := make([]time.Duration, len(destroyVolumeTimes)-1)
  1924  	for i := range destroyVolumeTimes[1:] {
  1925  		delays[i] = destroyVolumeTimes[i+1].Sub(destroyVolumeTimes[i])
  1926  	}
  1927  	c.Assert(delays, jc.DeepEquals, []time.Duration{
  1928  		30 * time.Second,
  1929  		1 * time.Minute,
  1930  		2 * time.Minute,
  1931  		4 * time.Minute,
  1932  		8 * time.Minute,
  1933  		16 * time.Minute,
  1934  		30 * time.Minute, // ceiling reached
  1935  		30 * time.Minute,
  1936  		30 * time.Minute,
  1937  	})
  1938  
  1939  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
  1940  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1941  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1942  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1943  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1944  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1945  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1946  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1947  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1948  		{Tag: "volume-1", Status: "error", Info: "destroying volume: badness"},
  1949  	})
  1950  }
  1951  
  1952  func (s *storageProvisionerSuite) TestDestroyFilesystems(c *gc.C) {
  1953  	unprovisionedFilesystem := names.NewFilesystemTag("0")
  1954  	provisionedDestroyFilesystem := names.NewFilesystemTag("1")
  1955  	provisionedReleaseFilesystem := names.NewFilesystemTag("2")
  1956  
  1957  	filesystemAccessor := newMockFilesystemAccessor()
  1958  	filesystemAccessor.provisionFilesystem(provisionedDestroyFilesystem)
  1959  	filesystemAccessor.provisionFilesystem(provisionedReleaseFilesystem)
  1960  
  1961  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
  1962  		results := make([]params.LifeResult, len(tags))
  1963  		for i := range results {
  1964  			results[i].Life = life.Dead
  1965  		}
  1966  		return results, nil
  1967  	}
  1968  
  1969  	destroyedChan := make(chan interface{}, 1)
  1970  	s.provider.destroyFilesystemsFunc = func(filesystemIds []string) ([]error, error) {
  1971  		destroyedChan <- filesystemIds
  1972  		return make([]error, len(filesystemIds)), nil
  1973  	}
  1974  
  1975  	releasedChan := make(chan interface{}, 1)
  1976  	s.provider.releaseFilesystemsFunc = func(filesystemIds []string) ([]error, error) {
  1977  		releasedChan <- filesystemIds
  1978  		return make([]error, len(filesystemIds)), nil
  1979  	}
  1980  
  1981  	removedChan := make(chan interface{}, 1)
  1982  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
  1983  		removedChan <- tags
  1984  		return make([]params.ErrorResult, len(tags)), nil
  1985  	}
  1986  
  1987  	args := &workerArgs{
  1988  		filesystems: filesystemAccessor,
  1989  		life: &mockLifecycleManager{
  1990  			life:   life,
  1991  			remove: remove,
  1992  		},
  1993  		registry: s.registry,
  1994  	}
  1995  	worker := newStorageProvisioner(c, args)
  1996  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  1997  	defer worker.Kill()
  1998  
  1999  	filesystemAccessor.filesystemsWatcher.changes <- []string{
  2000  		unprovisionedFilesystem.Id(),
  2001  		provisionedDestroyFilesystem.Id(),
  2002  		provisionedReleaseFilesystem.Id(),
  2003  	}
  2004  
  2005  	// Both filesystems should be removed; the provisioned ones
  2006  	// should be destroyed/released first.
  2007  
  2008  	destroyed := waitChannel(c, destroyedChan, "waiting for filesystem to be destroyed")
  2009  	assertNoEvent(c, destroyedChan, "filesystems destroyed")
  2010  	c.Assert(destroyed, jc.DeepEquals, []string{"fs-1"})
  2011  
  2012  	released := waitChannel(c, releasedChan, "waiting for filesystem to be released")
  2013  	assertNoEvent(c, releasedChan, "filesystems released")
  2014  	c.Assert(released, jc.DeepEquals, []string{"fs-2"})
  2015  
  2016  	var removed []names.Tag
  2017  	for len(removed) < 3 {
  2018  		tags := waitChannel(c, removedChan, "waiting for filesystems to be removed").([]names.Tag)
  2019  		removed = append(removed, tags...)
  2020  	}
  2021  	c.Assert(removed, jc.SameContents, []names.Tag{
  2022  		unprovisionedFilesystem,
  2023  		provisionedDestroyFilesystem,
  2024  		provisionedReleaseFilesystem,
  2025  	})
  2026  	assertNoEvent(c, removedChan, "filesystems removed")
  2027  }
  2028  
  2029  func (s *storageProvisionerSuite) TestDestroyFilesystemsRetry(c *gc.C) {
  2030  	provisionedDestroyFilesystem := names.NewFilesystemTag("0")
  2031  
  2032  	filesystemAccessor := newMockFilesystemAccessor()
  2033  	filesystemAccessor.provisionFilesystem(provisionedDestroyFilesystem)
  2034  
  2035  	life := func(tags []names.Tag) ([]params.LifeResult, error) {
  2036  		return []params.LifeResult{{Life: life.Dead}}, nil
  2037  	}
  2038  
  2039  	// mockFunc's After will progress the current time by the specified
  2040  	// duration and signal the channel immediately.
  2041  	clock := &mockClock{}
  2042  	var destroyFilesystemTimes []time.Time
  2043  	s.provider.destroyFilesystemsFunc = func(filesystemIds []string) ([]error, error) {
  2044  		destroyFilesystemTimes = append(destroyFilesystemTimes, clock.Now())
  2045  		if len(destroyFilesystemTimes) < 10 {
  2046  			return []error{errors.New("destroyFilesystems failed, please retry later")}, nil
  2047  		}
  2048  		return []error{nil}, nil
  2049  	}
  2050  
  2051  	removedChan := make(chan interface{}, 1)
  2052  	remove := func(tags []names.Tag) ([]params.ErrorResult, error) {
  2053  		removedChan <- tags
  2054  		return make([]params.ErrorResult, len(tags)), nil
  2055  	}
  2056  
  2057  	args := &workerArgs{
  2058  		filesystems: filesystemAccessor,
  2059  		clock:       clock,
  2060  		life: &mockLifecycleManager{
  2061  			life:   life,
  2062  			remove: remove,
  2063  		},
  2064  		registry: s.registry,
  2065  	}
  2066  	worker := newStorageProvisioner(c, args)
  2067  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
  2068  	defer worker.Kill()
  2069  
  2070  	filesystemAccessor.filesystemsWatcher.changes <- []string{
  2071  		provisionedDestroyFilesystem.Id(),
  2072  	}
  2073  
  2074  	waitChannel(c, removedChan, "waiting for filesystem to be removed")
  2075  	c.Assert(destroyFilesystemTimes, gc.HasLen, 10)
  2076  
  2077  	// The first attempt should have been immediate: T0.
  2078  	c.Assert(destroyFilesystemTimes[0], gc.Equals, time.Time{})
  2079  
  2080  	delays := make([]time.Duration, len(destroyFilesystemTimes)-1)
  2081  	for i := range destroyFilesystemTimes[1:] {
  2082  		delays[i] = destroyFilesystemTimes[i+1].Sub(destroyFilesystemTimes[i])
  2083  	}
  2084  	c.Assert(delays, jc.DeepEquals, []time.Duration{
  2085  		30 * time.Second,
  2086  		1 * time.Minute,
  2087  		2 * time.Minute,
  2088  		4 * time.Minute,
  2089  		8 * time.Minute,
  2090  		16 * time.Minute,
  2091  		30 * time.Minute, // ceiling reached
  2092  		30 * time.Minute,
  2093  		30 * time.Minute,
  2094  	})
  2095  
  2096  	c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{
  2097  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2098  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2099  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2100  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2101  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2102  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2103  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2104  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2105  		{Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"},
  2106  	})
  2107  }
  2108  
  2109  type caasStorageProvisionerSuite struct {
  2110  	coretesting.BaseSuite
  2111  	provider *dummyProvider
  2112  	registry storage.ProviderRegistry
  2113  }
  2114  
  2115  var _ = gc.Suite(&caasStorageProvisionerSuite{})
  2116  
  2117  func (s *caasStorageProvisionerSuite) SetUpTest(c *gc.C) {
  2118  	s.BaseSuite.SetUpTest(c)
  2119  	s.provider = &dummyProvider{dynamic: true}
  2120  	s.registry = storage.StaticProviderRegistry{
  2121  		map[storage.ProviderType]storage.Provider{
  2122  			"dummy": s.provider,
  2123  		},
  2124  	}
  2125  	s.PatchValue(storageprovisioner.DefaultDependentChangesTimeout, 10*time.Millisecond)
  2126  }
  2127  
  2128  func (s *caasStorageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) {
  2129  	removed := make(chan interface{})
  2130  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  2131  		defer close(removed)
  2132  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
  2133  			MachineTag:    "unit-mariadb-0",
  2134  			AttachmentTag: "volume-0",
  2135  		}})
  2136  		return make([]params.ErrorResult, len(ids)), nil
  2137  	}
  2138  
  2139  	args := &workerArgs{
  2140  		life:     &mockLifecycleManager{removeAttachments: removeAttachments},
  2141  		registry: s.registry,
  2142  	}
  2143  	w := newStorageProvisioner(c, args)
  2144  	defer w.Wait()
  2145  	defer w.Kill()
  2146  
  2147  	args.volumes.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  2148  		MachineTag: "unit-mariadb-0", AttachmentTag: "volume-0",
  2149  	}}
  2150  	waitChannel(c, removed, "waiting for attachment to be removed")
  2151  }
  2152  
  2153  func (s *caasStorageProvisionerSuite) TestDetachVolumes(c *gc.C) {
  2154  	volumeAccessor := newMockVolumeAccessor()
  2155  
  2156  	expectedAttachmentIds := []params.MachineStorageId{{
  2157  		MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1",
  2158  	}}
  2159  
  2160  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  2161  		return []params.LifeResult{{Life: life.Dying}}, nil
  2162  	}
  2163  
  2164  	detached := make(chan interface{})
  2165  	s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) {
  2166  		c.Assert(args, gc.HasLen, 1)
  2167  		c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag)
  2168  		c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag)
  2169  		defer close(detached)
  2170  		return make([]error, len(args)), nil
  2171  	}
  2172  
  2173  	args := &workerArgs{
  2174  		volumes: volumeAccessor,
  2175  		life: &mockLifecycleManager{
  2176  			attachmentLife: attachmentLife,
  2177  		},
  2178  		registry: s.registry,
  2179  	}
  2180  	w := newStorageProvisioner(c, args)
  2181  	defer func() { c.Assert(w.Wait(), gc.IsNil) }()
  2182  	defer w.Kill()
  2183  
  2184  	volumeAccessor.provisionedAttachments[expectedAttachmentIds[0]] = params.VolumeAttachment{
  2185  		MachineTag: "unit-mariadb-1",
  2186  		VolumeTag:  "volume-1",
  2187  	}
  2188  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  2189  		MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1",
  2190  	}}
  2191  	waitChannel(c, detached, "waiting for volume to be detached")
  2192  }
  2193  
  2194  func (s *caasStorageProvisionerSuite) TestRemoveVolumes(c *gc.C) {
  2195  	volumeAccessor := newMockVolumeAccessor()
  2196  
  2197  	expectedAttachmentIds := []params.MachineStorageId{{
  2198  		MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1",
  2199  	}}
  2200  
  2201  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  2202  		return []params.LifeResult{{Life: life.Dying}}, nil
  2203  	}
  2204  
  2205  	removed := make(chan interface{})
  2206  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  2207  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  2208  		close(removed)
  2209  		return make([]params.ErrorResult, len(ids)), nil
  2210  	}
  2211  
  2212  	args := &workerArgs{
  2213  		volumes: volumeAccessor,
  2214  		life: &mockLifecycleManager{
  2215  			attachmentLife:    attachmentLife,
  2216  			removeAttachments: removeAttachments,
  2217  		},
  2218  		registry: s.registry,
  2219  	}
  2220  	w := newStorageProvisioner(c, args)
  2221  	defer func() { c.Assert(w.Wait(), gc.IsNil) }()
  2222  	defer w.Kill()
  2223  
  2224  	volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  2225  		MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1",
  2226  	}}
  2227  	waitChannel(c, removed, "waiting for attachment to be removed")
  2228  }
  2229  
  2230  func (s *caasStorageProvisionerSuite) TestDetachFilesystems(c *gc.C) {
  2231  	removed := make(chan interface{})
  2232  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  2233  		defer close(removed)
  2234  		c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{
  2235  			MachineTag:    "unit-mariadb-0",
  2236  			AttachmentTag: "filesystem-0",
  2237  		}})
  2238  		return make([]params.ErrorResult, len(ids)), nil
  2239  	}
  2240  
  2241  	args := &workerArgs{
  2242  		life:     &mockLifecycleManager{removeAttachments: removeAttachments},
  2243  		registry: s.registry,
  2244  	}
  2245  	w := newStorageProvisioner(c, args)
  2246  	defer w.Wait()
  2247  	defer w.Kill()
  2248  
  2249  	args.filesystems.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  2250  		MachineTag: "unit-mariadb-0", AttachmentTag: "filesystem-0",
  2251  	}}
  2252  	waitChannel(c, removed, "waiting for attachment to be removed")
  2253  }
  2254  
  2255  func (s *caasStorageProvisionerSuite) TestRemoveFilesystems(c *gc.C) {
  2256  	filesystemAccessor := newMockFilesystemAccessor()
  2257  
  2258  	expectedAttachmentIds := []params.MachineStorageId{{
  2259  		MachineTag: "unit-mariadb-1", AttachmentTag: "filesystem-1",
  2260  	}}
  2261  
  2262  	attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) {
  2263  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  2264  		return []params.LifeResult{{Life: life.Dying}}, nil
  2265  	}
  2266  
  2267  	removed := make(chan interface{})
  2268  	removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) {
  2269  		c.Assert(ids, gc.DeepEquals, expectedAttachmentIds)
  2270  		close(removed)
  2271  		return make([]params.ErrorResult, len(ids)), nil
  2272  	}
  2273  
  2274  	args := &workerArgs{
  2275  		filesystems: filesystemAccessor,
  2276  		life: &mockLifecycleManager{
  2277  			attachmentLife:    attachmentLife,
  2278  			removeAttachments: removeAttachments,
  2279  		},
  2280  		registry: s.registry,
  2281  	}
  2282  	w := newStorageProvisioner(c, args)
  2283  	defer func() { c.Assert(w.Wait(), gc.IsNil) }()
  2284  	defer w.Kill()
  2285  
  2286  	filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{
  2287  		MachineTag: "unit-mariadb-1", AttachmentTag: "filesystem-1",
  2288  	}}
  2289  	waitChannel(c, removed, "waiting for filesystem to be removed")
  2290  }
  2291  
  2292  func newStorageProvisioner(c *gc.C, args *workerArgs) worker.Worker {
  2293  	if args == nil {
  2294  		args = &workerArgs{}
  2295  	}
  2296  	var storageDir string
  2297  	switch args.scope.(type) {
  2298  	case names.MachineTag:
  2299  		storageDir = "storage-dir"
  2300  	case names.ModelTag:
  2301  	case nil:
  2302  		args.scope = coretesting.ModelTag
  2303  	}
  2304  	if args.volumes == nil {
  2305  		args.volumes = newMockVolumeAccessor()
  2306  	}
  2307  	if args.filesystems == nil {
  2308  		args.filesystems = newMockFilesystemAccessor()
  2309  	}
  2310  	if args.life == nil {
  2311  		args.life = &mockLifecycleManager{}
  2312  	}
  2313  	if args.machines == nil {
  2314  		args.machines = newMockMachineAccessor(c)
  2315  	}
  2316  	if args.clock == nil {
  2317  		args.clock = &mockClock{}
  2318  	}
  2319  	if args.statusSetter == nil {
  2320  		args.statusSetter = &mockStatusSetter{}
  2321  	}
  2322  	worker, err := storageprovisioner.NewStorageProvisioner(storageprovisioner.Config{
  2323  		Scope:       args.scope,
  2324  		StorageDir:  storageDir,
  2325  		Volumes:     args.volumes,
  2326  		Filesystems: args.filesystems,
  2327  		Life:        args.life,
  2328  		Registry:    args.registry,
  2329  		Machines:    args.machines,
  2330  		Status:      args.statusSetter,
  2331  		Clock:       args.clock,
  2332  		Logger:      loggo.GetLogger("test"),
  2333  		CloudCallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext {
  2334  			return context.NewEmptyCloudCallContext()
  2335  		},
  2336  	})
  2337  	c.Assert(err, jc.ErrorIsNil)
  2338  	return worker
  2339  }
  2340  
  2341  type workerArgs struct {
  2342  	scope        names.Tag
  2343  	volumes      *mockVolumeAccessor
  2344  	filesystems  *mockFilesystemAccessor
  2345  	life         *mockLifecycleManager
  2346  	registry     storage.ProviderRegistry
  2347  	machines     *mockMachineAccessor
  2348  	clock        clock.Clock
  2349  	statusSetter *mockStatusSetter
  2350  }
  2351  
  2352  func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} {
  2353  	select {
  2354  	case v := <-ch:
  2355  		return v
  2356  	case <-time.After(coretesting.LongWait):
  2357  		c.Fatalf("timed out " + activity)
  2358  		panic("unreachable")
  2359  	}
  2360  }
  2361  
  2362  func assertNoEvent(c *gc.C, ch <-chan interface{}, event string) {
  2363  	select {
  2364  	case <-ch:
  2365  		c.Fatalf("unexpected " + event)
  2366  	case <-time.After(coretesting.ShortWait):
  2367  	}
  2368  }