github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/nomad/volumewatcher/volumes_watcher_test.go (about)

     1  package volumewatcher
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	memdb "github.com/hashicorp/go-memdb"
     9  	"github.com/hashicorp/nomad/helper/testlog"
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/hashicorp/nomad/nomad/state"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  // TestVolumeWatch_EnableDisable tests the watcher registration logic that needs
    17  // to happen during leader step-up/step-down
    18  func TestVolumeWatch_EnableDisable(t *testing.T) {
    19  	t.Parallel()
    20  	require := require.New(t)
    21  
    22  	srv := &MockRPCServer{}
    23  	srv.state = state.TestStateStore(t)
    24  	index := uint64(100)
    25  
    26  	watcher := NewVolumesWatcher(testlog.HCLogger(t),
    27  		srv, srv,
    28  		LimitStateQueriesPerSecond,
    29  		CrossVolumeUpdateBatchDuration)
    30  
    31  	watcher.SetEnabled(true, srv.State())
    32  
    33  	plugin := mock.CSIPlugin()
    34  	node := testNode(nil, plugin, srv.State())
    35  	alloc := mock.Alloc()
    36  	alloc.ClientStatus = structs.AllocClientStatusComplete
    37  	vol := testVolume(nil, plugin, alloc, node.ID)
    38  
    39  	index++
    40  	err := srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol})
    41  	require.NoError(err)
    42  
    43  	claim := &structs.CSIVolumeClaim{Mode: structs.CSIVolumeClaimRelease}
    44  	index++
    45  	err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim)
    46  	require.NoError(err)
    47  	require.Eventually(func() bool {
    48  		return 1 == len(watcher.watchers)
    49  	}, time.Second, 10*time.Millisecond)
    50  
    51  	watcher.SetEnabled(false, srv.State())
    52  	require.Equal(0, len(watcher.watchers))
    53  }
    54  
    55  // TestVolumeWatch_Checkpoint tests the checkpointing of progress across
    56  // leader leader step-up/step-down
    57  func TestVolumeWatch_Checkpoint(t *testing.T) {
    58  	t.Parallel()
    59  	require := require.New(t)
    60  
    61  	srv := &MockRPCServer{}
    62  	srv.state = state.TestStateStore(t)
    63  	index := uint64(100)
    64  
    65  	watcher := NewVolumesWatcher(testlog.HCLogger(t),
    66  		srv, srv,
    67  		LimitStateQueriesPerSecond,
    68  		CrossVolumeUpdateBatchDuration)
    69  
    70  	plugin := mock.CSIPlugin()
    71  	node := testNode(nil, plugin, srv.State())
    72  	alloc := mock.Alloc()
    73  	alloc.ClientStatus = structs.AllocClientStatusComplete
    74  	vol := testVolume(nil, plugin, alloc, node.ID)
    75  
    76  	watcher.SetEnabled(true, srv.State())
    77  
    78  	index++
    79  	err := srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol})
    80  	require.NoError(err)
    81  
    82  	// we should get or start up a watcher when we get an update for
    83  	// the volume from the state store
    84  	require.Eventually(func() bool {
    85  		return 1 == len(watcher.watchers)
    86  	}, time.Second, 10*time.Millisecond)
    87  
    88  	// step-down (this is sync, but step-up is async)
    89  	watcher.SetEnabled(false, srv.State())
    90  	require.Equal(0, len(watcher.watchers))
    91  
    92  	// step-up again
    93  	watcher.SetEnabled(true, srv.State())
    94  	require.Eventually(func() bool {
    95  		return 1 == len(watcher.watchers) &&
    96  			!watcher.watchers[vol.ID+vol.Namespace].isRunning()
    97  	}, time.Second, 10*time.Millisecond)
    98  }
    99  
   100  // TestVolumeWatch_StartStop tests the start and stop of the watcher when
   101  // it receives notifcations and has completed its work
   102  func TestVolumeWatch_StartStop(t *testing.T) {
   103  	t.Parallel()
   104  	require := require.New(t)
   105  
   106  	ctx, exitFn := context.WithCancel(context.Background())
   107  	defer exitFn()
   108  
   109  	srv := &MockStatefulRPCServer{}
   110  	srv.state = state.TestStateStore(t)
   111  	index := uint64(100)
   112  	srv.volumeUpdateBatcher = NewVolumeUpdateBatcher(
   113  		CrossVolumeUpdateBatchDuration, srv, ctx)
   114  
   115  	watcher := NewVolumesWatcher(testlog.HCLogger(t),
   116  		srv, srv,
   117  		LimitStateQueriesPerSecond,
   118  		CrossVolumeUpdateBatchDuration)
   119  
   120  	watcher.SetEnabled(true, srv.State())
   121  	require.Equal(0, len(watcher.watchers))
   122  
   123  	plugin := mock.CSIPlugin()
   124  	node := testNode(nil, plugin, srv.State())
   125  	alloc1 := mock.Alloc()
   126  	alloc1.ClientStatus = structs.AllocClientStatusRunning
   127  	alloc2 := mock.Alloc()
   128  	alloc2.Job = alloc1.Job
   129  	alloc2.ClientStatus = structs.AllocClientStatusRunning
   130  	index++
   131  	err := srv.State().UpsertJob(index, alloc1.Job)
   132  	require.NoError(err)
   133  	index++
   134  	err = srv.State().UpsertAllocs(index, []*structs.Allocation{alloc1, alloc2})
   135  	require.NoError(err)
   136  
   137  	// register a volume
   138  	vol := testVolume(nil, plugin, alloc1, node.ID)
   139  	index++
   140  	err = srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol})
   141  	require.NoError(err)
   142  
   143  	// assert we get a watcher; there are no claims so it should immediately stop
   144  	require.Eventually(func() bool {
   145  		return 1 == len(watcher.watchers) &&
   146  			!watcher.watchers[vol.ID+vol.Namespace].isRunning()
   147  	}, time.Second*2, 10*time.Millisecond)
   148  
   149  	// claim the volume for both allocs
   150  	claim := &structs.CSIVolumeClaim{
   151  		AllocationID: alloc1.ID,
   152  		NodeID:       node.ID,
   153  		Mode:         structs.CSIVolumeClaimRead,
   154  	}
   155  	index++
   156  	err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim)
   157  	require.NoError(err)
   158  	claim.AllocationID = alloc2.ID
   159  	index++
   160  	err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim)
   161  	require.NoError(err)
   162  
   163  	// reap the volume and assert nothing has happened
   164  	claim = &structs.CSIVolumeClaim{
   165  		AllocationID: alloc1.ID,
   166  		NodeID:       node.ID,
   167  		Mode:         structs.CSIVolumeClaimRelease,
   168  	}
   169  	index++
   170  	err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim)
   171  	require.NoError(err)
   172  
   173  	ws := memdb.NewWatchSet()
   174  	vol, _ = srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID)
   175  	require.Equal(2, len(vol.ReadAllocs))
   176  
   177  	// alloc becomes terminal
   178  	alloc1.ClientStatus = structs.AllocClientStatusComplete
   179  	index++
   180  	err = srv.State().UpsertAllocs(index, []*structs.Allocation{alloc1})
   181  	require.NoError(err)
   182  	index++
   183  	claim.State = structs.CSIVolumeClaimStateReadyToFree
   184  	err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim)
   185  	require.NoError(err)
   186  
   187  	// 1 claim has been released and watcher stops
   188  	require.Eventually(func() bool {
   189  		ws := memdb.NewWatchSet()
   190  		vol, _ := srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID)
   191  		return len(vol.ReadAllocs) == 1 && len(vol.PastClaims) == 0
   192  	}, time.Second*2, 10*time.Millisecond)
   193  
   194  	require.Eventually(func() bool {
   195  		return !watcher.watchers[vol.ID+vol.Namespace].isRunning()
   196  	}, time.Second*5, 10*time.Millisecond)
   197  
   198  	// the watcher will have incremented the index so we need to make sure
   199  	// our inserts will trigger new events
   200  	index, _ = srv.State().LatestIndex()
   201  
   202  	// remaining alloc's job is stopped (alloc is not marked terminal)
   203  	alloc2.Job.Stop = true
   204  	index++
   205  	err = srv.State().UpsertJob(index, alloc2.Job)
   206  	require.NoError(err)
   207  
   208  	// job deregistration write a claim with no allocations or nodes
   209  	claim = &structs.CSIVolumeClaim{
   210  		Mode: structs.CSIVolumeClaimRelease,
   211  	}
   212  	index++
   213  	err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim)
   214  	require.NoError(err)
   215  
   216  	// all claims have been released and watcher has stopped again
   217  	require.Eventually(func() bool {
   218  		ws := memdb.NewWatchSet()
   219  		vol, _ := srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID)
   220  		return len(vol.ReadAllocs) == 0 && len(vol.PastClaims) == 0
   221  	}, time.Second*2, 10*time.Millisecond)
   222  
   223  	require.Eventually(func() bool {
   224  		return !watcher.watchers[vol.ID+vol.Namespace].isRunning()
   225  	}, time.Second*5, 10*time.Millisecond)
   226  
   227  	// the watcher will have incremented the index so we need to make sure
   228  	// our inserts will trigger new events
   229  	index, _ = srv.State().LatestIndex()
   230  
   231  	// create a new claim
   232  	alloc3 := mock.Alloc()
   233  	alloc3.ClientStatus = structs.AllocClientStatusRunning
   234  	index++
   235  	err = srv.State().UpsertAllocs(index, []*structs.Allocation{alloc3})
   236  	require.NoError(err)
   237  	claim3 := &structs.CSIVolumeClaim{
   238  		AllocationID: alloc3.ID,
   239  		NodeID:       node.ID,
   240  		Mode:         structs.CSIVolumeClaimRelease,
   241  	}
   242  	index++
   243  	err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim3)
   244  	require.NoError(err)
   245  
   246  	// a stopped watcher should restore itself on notification
   247  	require.Eventually(func() bool {
   248  		return watcher.watchers[vol.ID+vol.Namespace].isRunning()
   249  	}, time.Second*5, 10*time.Millisecond)
   250  }
   251  
   252  // TestVolumeWatch_RegisterDeregister tests the start and stop of
   253  // watchers around registration
   254  func TestVolumeWatch_RegisterDeregister(t *testing.T) {
   255  	t.Parallel()
   256  	require := require.New(t)
   257  
   258  	ctx, exitFn := context.WithCancel(context.Background())
   259  	defer exitFn()
   260  
   261  	srv := &MockStatefulRPCServer{}
   262  	srv.state = state.TestStateStore(t)
   263  	srv.volumeUpdateBatcher = NewVolumeUpdateBatcher(
   264  		CrossVolumeUpdateBatchDuration, srv, ctx)
   265  
   266  	index := uint64(100)
   267  
   268  	watcher := NewVolumesWatcher(testlog.HCLogger(t),
   269  		srv, srv,
   270  		LimitStateQueriesPerSecond,
   271  		CrossVolumeUpdateBatchDuration)
   272  
   273  	watcher.SetEnabled(true, srv.State())
   274  	require.Equal(0, len(watcher.watchers))
   275  
   276  	plugin := mock.CSIPlugin()
   277  	node := testNode(nil, plugin, srv.State())
   278  	alloc := mock.Alloc()
   279  	alloc.ClientStatus = structs.AllocClientStatusComplete
   280  
   281  	// register a volume
   282  	vol := testVolume(nil, plugin, alloc, node.ID)
   283  	index++
   284  	err := srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol})
   285  	require.NoError(err)
   286  
   287  	require.Eventually(func() bool {
   288  		return 1 == len(watcher.watchers)
   289  	}, time.Second, 10*time.Millisecond)
   290  
   291  	// reap the volume and assert we've cleaned up
   292  	w := watcher.watchers[vol.ID+vol.Namespace]
   293  	w.Notify(vol)
   294  
   295  	require.Eventually(func() bool {
   296  		ws := memdb.NewWatchSet()
   297  		vol, _ := srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID)
   298  		return len(vol.ReadAllocs) == 0 && len(vol.PastClaims) == 0
   299  	}, time.Second*2, 10*time.Millisecond)
   300  
   301  	require.Eventually(func() bool {
   302  		return !watcher.watchers[vol.ID+vol.Namespace].isRunning()
   303  	}, time.Second*1, 10*time.Millisecond)
   304  
   305  	require.Equal(1, srv.countCSINodeDetachVolume, "node detach RPC count")
   306  	require.Equal(1, srv.countCSIControllerDetachVolume, "controller detach RPC count")
   307  	require.Equal(2, srv.countUpsertVolumeClaims, "upsert claims count")
   308  
   309  	// deregistering the volume doesn't cause an update that triggers
   310  	// a watcher; we'll clean up this watcher in a GC later
   311  	err = srv.State().CSIVolumeDeregister(index, vol.Namespace, []string{vol.ID})
   312  	require.NoError(err)
   313  	require.Equal(1, len(watcher.watchers))
   314  	require.False(watcher.watchers[vol.ID+vol.Namespace].isRunning())
   315  }