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

     1  package volumewatcher
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/nomad/helper/testlog"
     9  	"github.com/hashicorp/nomad/nomad/mock"
    10  	"github.com/hashicorp/nomad/nomad/state"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  // TestVolumeWatch_OneReap tests one pass through the reaper
    16  func TestVolumeWatch_OneReap(t *testing.T) {
    17  	t.Parallel()
    18  	require := require.New(t)
    19  
    20  	cases := []struct {
    21  		Name                          string
    22  		Volume                        *structs.CSIVolume
    23  		Node                          *structs.Node
    24  		ControllerRequired            bool
    25  		ExpectedErr                   string
    26  		ExpectedClaimsCount           int
    27  		ExpectedNodeDetachCount       int
    28  		ExpectedControllerDetachCount int
    29  		ExpectedUpdateClaimsCount     int
    30  		srv                           *MockRPCServer
    31  	}{
    32  		{
    33  			Name:               "No terminal allocs",
    34  			Volume:             mock.CSIVolume(mock.CSIPlugin()),
    35  			ControllerRequired: true,
    36  			srv: &MockRPCServer{
    37  				state:                  state.TestStateStore(t),
    38  				nextCSINodeDetachError: fmt.Errorf("should never see this"),
    39  			},
    40  		},
    41  		{
    42  			Name:                    "NodeDetachVolume fails",
    43  			ControllerRequired:      true,
    44  			ExpectedErr:             "some node plugin error",
    45  			ExpectedNodeDetachCount: 1,
    46  			srv: &MockRPCServer{
    47  				state:                  state.TestStateStore(t),
    48  				nextCSINodeDetachError: fmt.Errorf("some node plugin error"),
    49  			},
    50  		},
    51  		{
    52  			Name:                      "NodeDetachVolume node-only happy path",
    53  			ControllerRequired:        false,
    54  			ExpectedNodeDetachCount:   1,
    55  			ExpectedUpdateClaimsCount: 2,
    56  			srv: &MockRPCServer{
    57  				state: state.TestStateStore(t),
    58  			},
    59  		},
    60  		{
    61  			Name:                      "ControllerDetachVolume no controllers available",
    62  			Node:                      mock.Node(),
    63  			ControllerRequired:        true,
    64  			ExpectedErr:               "Unknown node",
    65  			ExpectedNodeDetachCount:   1,
    66  			ExpectedUpdateClaimsCount: 1,
    67  			srv: &MockRPCServer{
    68  				state: state.TestStateStore(t),
    69  			},
    70  		},
    71  		{
    72  			Name:                          "ControllerDetachVolume controller error",
    73  			ControllerRequired:            true,
    74  			ExpectedErr:                   "some controller error",
    75  			ExpectedNodeDetachCount:       1,
    76  			ExpectedControllerDetachCount: 1,
    77  			ExpectedUpdateClaimsCount:     1,
    78  			srv: &MockRPCServer{
    79  				state:                        state.TestStateStore(t),
    80  				nextCSIControllerDetachError: fmt.Errorf("some controller error"),
    81  			},
    82  		},
    83  		{
    84  			Name:                          "ControllerDetachVolume happy path",
    85  			ControllerRequired:            true,
    86  			ExpectedNodeDetachCount:       1,
    87  			ExpectedControllerDetachCount: 1,
    88  			ExpectedUpdateClaimsCount:     2,
    89  			srv: &MockRPCServer{
    90  				state: state.TestStateStore(t),
    91  			},
    92  		},
    93  	}
    94  
    95  	for _, tc := range cases {
    96  		t.Run(tc.Name, func(t *testing.T) {
    97  
    98  			plugin := mock.CSIPlugin()
    99  			plugin.ControllerRequired = tc.ControllerRequired
   100  			node := testNode(tc.Node, plugin, tc.srv.State())
   101  			alloc := mock.Alloc()
   102  			alloc.NodeID = node.ID
   103  			alloc.ClientStatus = structs.AllocClientStatusComplete
   104  			vol := testVolume(tc.Volume, plugin, alloc, node.ID)
   105  			ctx, exitFn := context.WithCancel(context.Background())
   106  			w := &volumeWatcher{
   107  				v:            vol,
   108  				rpc:          tc.srv,
   109  				state:        tc.srv.State(),
   110  				updateClaims: tc.srv.UpdateClaims,
   111  				ctx:          ctx,
   112  				exitFn:       exitFn,
   113  				logger:       testlog.HCLogger(t),
   114  			}
   115  
   116  			err := w.volumeReapImpl(vol)
   117  			if tc.ExpectedErr != "" {
   118  				require.Error(err, fmt.Sprintf("expected: %q", tc.ExpectedErr))
   119  				require.Contains(err.Error(), tc.ExpectedErr)
   120  			} else {
   121  				require.NoError(err)
   122  			}
   123  			require.Equal(tc.ExpectedNodeDetachCount,
   124  				tc.srv.countCSINodeDetachVolume, "node detach RPC count")
   125  			require.Equal(tc.ExpectedControllerDetachCount,
   126  				tc.srv.countCSIControllerDetachVolume, "controller detach RPC count")
   127  			require.Equal(tc.ExpectedUpdateClaimsCount,
   128  				tc.srv.countUpdateClaims, "update claims count")
   129  		})
   130  	}
   131  }
   132  
   133  // TestVolumeWatch_OldVolume_OneReap tests one pass through the reaper
   134  // COMPAT(1.0): the claim fields were added after 0.11.1; this test
   135  // can be removed for 1.0
   136  func TestVolumeWatch_OldVolume_OneReap(t *testing.T) {
   137  	t.Parallel()
   138  	require := require.New(t)
   139  
   140  	cases := []struct {
   141  		Name                          string
   142  		Volume                        *structs.CSIVolume
   143  		Node                          *structs.Node
   144  		ControllerRequired            bool
   145  		ExpectedErr                   string
   146  		ExpectedClaimsCount           int
   147  		ExpectedNodeDetachCount       int
   148  		ExpectedControllerDetachCount int
   149  		ExpectedUpdateClaimsCount     int
   150  		srv                           *MockRPCServer
   151  	}{
   152  		{
   153  			Name:               "No terminal allocs",
   154  			Volume:             mock.CSIVolume(mock.CSIPlugin()),
   155  			ControllerRequired: true,
   156  			srv: &MockRPCServer{
   157  				state:                  state.TestStateStore(t),
   158  				nextCSINodeDetachError: fmt.Errorf("should never see this"),
   159  			},
   160  		},
   161  		{
   162  			Name:                    "NodeDetachVolume fails",
   163  			ControllerRequired:      true,
   164  			ExpectedErr:             "some node plugin error",
   165  			ExpectedNodeDetachCount: 1,
   166  			srv: &MockRPCServer{
   167  				state:                  state.TestStateStore(t),
   168  				nextCSINodeDetachError: fmt.Errorf("some node plugin error"),
   169  			},
   170  		},
   171  		{
   172  			Name:                      "NodeDetachVolume node-only happy path",
   173  			ControllerRequired:        false,
   174  			ExpectedNodeDetachCount:   1,
   175  			ExpectedUpdateClaimsCount: 2,
   176  			srv: &MockRPCServer{
   177  				state: state.TestStateStore(t),
   178  			},
   179  		},
   180  		{
   181  			Name:                      "ControllerDetachVolume no controllers available",
   182  			Node:                      mock.Node(),
   183  			ControllerRequired:        true,
   184  			ExpectedErr:               "Unknown node",
   185  			ExpectedNodeDetachCount:   1,
   186  			ExpectedUpdateClaimsCount: 1,
   187  			srv: &MockRPCServer{
   188  				state: state.TestStateStore(t),
   189  			},
   190  		},
   191  		{
   192  			Name:                          "ControllerDetachVolume controller error",
   193  			ControllerRequired:            true,
   194  			ExpectedErr:                   "some controller error",
   195  			ExpectedNodeDetachCount:       1,
   196  			ExpectedControllerDetachCount: 1,
   197  			ExpectedUpdateClaimsCount:     1,
   198  			srv: &MockRPCServer{
   199  				state:                        state.TestStateStore(t),
   200  				nextCSIControllerDetachError: fmt.Errorf("some controller error"),
   201  			},
   202  		},
   203  		{
   204  			Name:                          "ControllerDetachVolume happy path",
   205  			ControllerRequired:            true,
   206  			ExpectedNodeDetachCount:       1,
   207  			ExpectedControllerDetachCount: 1,
   208  			ExpectedUpdateClaimsCount:     2,
   209  			srv: &MockRPCServer{
   210  				state: state.TestStateStore(t),
   211  			},
   212  		},
   213  	}
   214  
   215  	for _, tc := range cases {
   216  		t.Run(tc.Name, func(t *testing.T) {
   217  
   218  			plugin := mock.CSIPlugin()
   219  			plugin.ControllerRequired = tc.ControllerRequired
   220  			node := testNode(tc.Node, plugin, tc.srv.State())
   221  			alloc := mock.Alloc()
   222  			alloc.ClientStatus = structs.AllocClientStatusComplete
   223  			alloc.NodeID = node.ID
   224  			vol := testOldVolume(tc.Volume, plugin, alloc, node.ID)
   225  			ctx, exitFn := context.WithCancel(context.Background())
   226  			w := &volumeWatcher{
   227  				v:            vol,
   228  				rpc:          tc.srv,
   229  				state:        tc.srv.State(),
   230  				updateClaims: tc.srv.UpdateClaims,
   231  				ctx:          ctx,
   232  				exitFn:       exitFn,
   233  				logger:       testlog.HCLogger(t),
   234  			}
   235  
   236  			err := w.volumeReapImpl(vol)
   237  			if tc.ExpectedErr != "" {
   238  				require.Error(err, fmt.Sprintf("expected: %q", tc.ExpectedErr))
   239  				require.Contains(err.Error(), tc.ExpectedErr)
   240  			} else {
   241  				require.NoError(err)
   242  			}
   243  			require.Equal(tc.ExpectedNodeDetachCount,
   244  				tc.srv.countCSINodeDetachVolume, "node detach RPC count")
   245  			require.Equal(tc.ExpectedControllerDetachCount,
   246  				tc.srv.countCSIControllerDetachVolume, "controller detach RPC count")
   247  			require.Equal(tc.ExpectedUpdateClaimsCount,
   248  				tc.srv.countUpdateClaims, "update claims count")
   249  		})
   250  	}
   251  }
   252  
   253  // TestVolumeWatch_OneReap tests multiple passes through the reaper,
   254  // updating state after each one
   255  func TestVolumeWatch_ReapStates(t *testing.T) {
   256  	t.Parallel()
   257  	require := require.New(t)
   258  
   259  	srv := &MockRPCServer{state: state.TestStateStore(t)}
   260  	plugin := mock.CSIPlugin()
   261  	node := testNode(nil, plugin, srv.State())
   262  	alloc := mock.Alloc()
   263  	alloc.ClientStatus = structs.AllocClientStatusComplete
   264  	vol := testVolume(nil, plugin, alloc, node.ID)
   265  
   266  	w := &volumeWatcher{
   267  		v:            vol,
   268  		rpc:          srv,
   269  		state:        srv.State(),
   270  		updateClaims: srv.UpdateClaims,
   271  		logger:       testlog.HCLogger(t),
   272  	}
   273  
   274  	srv.nextCSINodeDetachError = fmt.Errorf("some node plugin error")
   275  	err := w.volumeReapImpl(vol)
   276  	require.Error(err)
   277  	require.Equal(structs.CSIVolumeClaimStateTaken, vol.PastClaims[alloc.ID].State)
   278  	require.Equal(1, srv.countCSINodeDetachVolume)
   279  	require.Equal(0, srv.countCSIControllerDetachVolume)
   280  	require.Equal(0, srv.countUpdateClaims)
   281  
   282  	srv.nextCSINodeDetachError = nil
   283  	srv.nextCSIControllerDetachError = fmt.Errorf("some controller plugin error")
   284  	err = w.volumeReapImpl(vol)
   285  	require.Error(err)
   286  	require.Equal(structs.CSIVolumeClaimStateNodeDetached, vol.PastClaims[alloc.ID].State)
   287  	require.Equal(1, srv.countUpdateClaims)
   288  
   289  	srv.nextCSIControllerDetachError = nil
   290  	err = w.volumeReapImpl(vol)
   291  	require.NoError(err)
   292  	require.Equal(0, len(vol.PastClaims))
   293  	require.Equal(2, srv.countUpdateClaims)
   294  }