"Fossies" - the Fresh Open Source Software Archive

Member "senlin-8.0.0/senlin/tests/unit/policies/test_deletion_policy.py" (16 Oct 2019, 20645 Bytes) of package /linux/misc/openstack/senlin-8.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "test_deletion_policy.py": 6.0.0_vs_7.0.0.

    1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 # not use this file except in compliance with the License. You may obtain
    3 # a copy of the License at
    4 #
    5 #         http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 # Unless required by applicable law or agreed to in writing, software
    8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 # License for the specific language governing permissions and limitations
   11 # under the License.
   12 
   13 import copy
   14 import mock
   15 
   16 from senlin.common import consts
   17 from senlin.common import scaleutils as su
   18 from senlin.policies import deletion_policy as dp
   19 from senlin.tests.unit.common import base
   20 from senlin.tests.unit.common import utils
   21 
   22 
   23 class TestDeletionPolicy(base.SenlinTestCase):
   24 
   25     def setUp(self):
   26         super(TestDeletionPolicy, self).setUp()
   27         self.context = utils.dummy_context()
   28         self.spec = {
   29             'type': 'senlin.policy.deletion',
   30             'version': '1.0',
   31             'properties': {
   32                 'criteria': 'OLDEST_FIRST',
   33                 'destroy_after_deletion': True,
   34                 'grace_period': 60,
   35                 'reduce_desired_capacity': False
   36             }
   37         }
   38 
   39     def test_policy_init(self):
   40         policy = dp.DeletionPolicy('test-policy', self.spec)
   41 
   42         self.assertIsNone(policy.id)
   43         self.assertEqual('test-policy', policy.name)
   44         self.assertEqual('senlin.policy.deletion-1.0', policy.type)
   45         self.assertEqual('OLDEST_FIRST', policy.criteria)
   46         self.assertTrue(policy.destroy_after_deletion)
   47         self.assertEqual(60, policy.grace_period)
   48         self.assertFalse(policy.reduce_desired_capacity)
   49 
   50     @mock.patch.object(su, 'nodes_by_random')
   51     def test_victims_by_regions_random(self, mock_select):
   52         cluster = mock.Mock()
   53         node1 = mock.Mock(id=1)
   54         node2 = mock.Mock(id=2)
   55         node3 = mock.Mock(id=3)
   56         cluster.nodes_by_region.side_effect = [
   57             [node1], [node2, node3]
   58         ]
   59 
   60         mock_select.side_effect = [['1'], ['2', '3']]
   61 
   62         self.spec['properties']['criteria'] = 'RANDOM'
   63         policy = dp.DeletionPolicy('test-policy', self.spec)
   64 
   65         res = policy._victims_by_regions(cluster, {'R1': 1, 'R2': 2})
   66         self.assertEqual(['1', '2', '3'], res)
   67         mock_select.assert_has_calls([
   68             mock.call([node1], 1),
   69             mock.call([node2, node3], 2)
   70         ])
   71         cluster.nodes_by_region.assert_has_calls([
   72             mock.call('R1'), mock.call('R2')])
   73 
   74     @mock.patch.object(su, 'nodes_by_profile_age')
   75     def test_victims_by_regions_profile_age(self, mock_select):
   76         cluster = mock.Mock()
   77         node1 = mock.Mock(id=1)
   78         node2 = mock.Mock(id=2)
   79         node3 = mock.Mock(id=3)
   80         cluster.nodes_by_region.side_effect = [
   81             [node1], [node2, node3]
   82         ]
   83 
   84         mock_select.side_effect = [['1'], ['2', '3']]
   85 
   86         self.spec['properties']['criteria'] = 'OLDEST_PROFILE_FIRST'
   87         policy = dp.DeletionPolicy('test-policy', self.spec)
   88 
   89         res = policy._victims_by_regions(cluster, {'R1': 1, 'R2': 2})
   90         self.assertEqual(['1', '2', '3'], res)
   91         mock_select.assert_has_calls([
   92             mock.call([node1], 1),
   93             mock.call([node2, node3], 2)
   94         ])
   95         cluster.nodes_by_region.assert_has_calls([
   96             mock.call('R1'), mock.call('R2')])
   97 
   98     @mock.patch.object(su, 'nodes_by_age')
   99     def test_victims_by_regions_age_oldest(self, mock_select):
  100         cluster = mock.Mock()
  101         node1 = mock.Mock(id=1)
  102         node2 = mock.Mock(id=2)
  103         node3 = mock.Mock(id=3)
  104         cluster.nodes_by_region.side_effect = [
  105             [node1], [node2, node3]
  106         ]
  107 
  108         mock_select.side_effect = [['1'], ['2', '3']]
  109 
  110         self.spec['properties']['criteria'] = 'OLDEST_FIRST'
  111         policy = dp.DeletionPolicy('test-policy', self.spec)
  112 
  113         res = policy._victims_by_regions(cluster, {'R1': 1, 'R2': 2})
  114         self.assertEqual(['1', '2', '3'], res)
  115         mock_select.assert_has_calls([
  116             mock.call([node1], 1, True),
  117             mock.call([node2, node3], 2, True)
  118         ])
  119         cluster.nodes_by_region.assert_has_calls([
  120             mock.call('R1'), mock.call('R2')])
  121 
  122     @mock.patch.object(su, 'nodes_by_age')
  123     def test_victims_by_regions_age_youngest(self, mock_select):
  124         cluster = mock.Mock()
  125         node1 = mock.Mock(id=1)
  126         node2 = mock.Mock(id=2)
  127         node3 = mock.Mock(id=3)
  128         cluster.nodes_by_region.side_effect = [
  129             [node1], [node2, node3]
  130         ]
  131 
  132         mock_select.side_effect = [['1'], ['2', '3']]
  133 
  134         self.spec['properties']['criteria'] = 'YOUNGEST_FIRST'
  135         policy = dp.DeletionPolicy('test-policy', self.spec)
  136 
  137         res = policy._victims_by_regions(cluster, {'R1': 1, 'R2': 2})
  138         self.assertEqual(['1', '2', '3'], res)
  139         mock_select.assert_has_calls([
  140             mock.call([node1], 1, False),
  141             mock.call([node2, node3], 2, False)
  142         ])
  143         cluster.nodes_by_region.assert_has_calls([
  144             mock.call('R1'), mock.call('R2')])
  145 
  146     @mock.patch.object(su, 'nodes_by_random')
  147     def test_victims_by_zones_random(self, mock_select):
  148         cluster = mock.Mock()
  149         node1 = mock.Mock(id=1)
  150         node2 = mock.Mock(id=2)
  151         node3 = mock.Mock(id=3)
  152         cluster.nodes_by_zone.side_effect = [
  153             [node1], [node2, node3]
  154         ]
  155 
  156         mock_select.side_effect = [['1'], ['3']]
  157 
  158         self.spec['properties']['criteria'] = 'RANDOM'
  159         policy = dp.DeletionPolicy('test-policy', self.spec)
  160 
  161         res = policy._victims_by_zones(cluster, {'AZ1': 1, 'AZ2': 1})
  162         self.assertEqual(['1', '3'], res)
  163         mock_select.assert_has_calls([
  164             mock.call([node1], 1),
  165             mock.call([node2, node3], 1)
  166         ])
  167         cluster.nodes_by_zone.assert_has_calls(
  168             [mock.call('AZ1'), mock.call('AZ2')],
  169         )
  170 
  171     @mock.patch.object(su, 'nodes_by_profile_age')
  172     def test_victims_by_zones_profile_age(self, mock_select):
  173         cluster = mock.Mock()
  174         node1 = mock.Mock(id=1)
  175         node2 = mock.Mock(id=2)
  176         node3 = mock.Mock(id=3)
  177         cluster.nodes_by_zone.side_effect = [
  178             [node1], [node2, node3]
  179         ]
  180 
  181         mock_select.side_effect = [['1'], ['2']]
  182 
  183         self.spec['properties']['criteria'] = 'OLDEST_PROFILE_FIRST'
  184         policy = dp.DeletionPolicy('test-policy', self.spec)
  185 
  186         res = policy._victims_by_zones(cluster, {'AZ1': 1, 'AZ2': 1})
  187         self.assertEqual(['1', '2'], res)
  188         mock_select.assert_has_calls(
  189             [
  190                 mock.call([node1], 1),
  191                 mock.call([node2, node3], 1)
  192             ],
  193         )
  194         cluster.nodes_by_zone.assert_has_calls(
  195             [mock.call('AZ1'), mock.call('AZ2')],
  196         )
  197 
  198     @mock.patch.object(su, 'nodes_by_age')
  199     def test_victims_by_zones_age_oldest(self, mock_select):
  200         cluster = mock.Mock()
  201         node1 = mock.Mock(id=1)
  202         node2 = mock.Mock(id=2)
  203         node3 = mock.Mock(id=3)
  204         cluster.nodes_by_zone.side_effect = [
  205             [node1], [node2, node3]
  206         ]
  207 
  208         mock_select.side_effect = [['1'], ['3']]
  209 
  210         self.spec['properties']['criteria'] = 'OLDEST_FIRST'
  211         policy = dp.DeletionPolicy('test-policy', self.spec)
  212 
  213         res = policy._victims_by_zones(cluster, {'AZ1': 1, 'AZ8': 1})
  214         self.assertEqual(['1', '3'], res)
  215         mock_select.assert_has_calls([
  216             mock.call([node1], 1, True),
  217             mock.call([node2, node3], 1, True)
  218         ])
  219         cluster.nodes_by_zone.assert_has_calls(
  220             [mock.call('AZ1'), mock.call('AZ8')],
  221         )
  222 
  223     @mock.patch.object(su, 'nodes_by_age')
  224     def test_victims_by_zones_age_youngest(self, mock_select):
  225         cluster = mock.Mock()
  226         node1 = mock.Mock(id=1)
  227         node2 = mock.Mock(id=3)
  228         node3 = mock.Mock(id=5)
  229         cluster.nodes_by_zone.side_effect = [
  230             [node1], [node2, node3]
  231         ]
  232 
  233         mock_select.side_effect = [['1'], ['3', '5']]
  234 
  235         self.spec['properties']['criteria'] = 'YOUNGEST_FIRST'
  236         policy = dp.DeletionPolicy('test-policy', self.spec)
  237 
  238         res = policy._victims_by_zones(cluster, {'AZ5': 1, 'AZ6': 2})
  239         self.assertEqual(['1', '3', '5'], res)
  240         mock_select.assert_has_calls(
  241             [
  242                 mock.call([node1], 1, False),
  243                 mock.call([node2, node3], 2, False)
  244             ],
  245         )
  246         cluster.nodes_by_zone.assert_has_calls(
  247             [mock.call('AZ5'), mock.call('AZ6')],
  248         )
  249 
  250     def test_update_action_clean(self):
  251         action = mock.Mock()
  252         action.data = {}
  253 
  254         policy = dp.DeletionPolicy('test-policy', self.spec)
  255 
  256         policy._update_action(action, ['N1', 'N2'])
  257 
  258         pd = {
  259             'status': 'OK',
  260             'reason': 'Candidates generated',
  261             'deletion': {
  262                 'count': 2,
  263                 'candidates': ['N1', 'N2'],
  264                 'destroy_after_deletion': True,
  265                 'grace_period': 60,
  266                 'reduce_desired_capacity': False,
  267             }
  268         }
  269         self.assertEqual(pd, action.data)
  270         action.store.assert_called_with(action.context)
  271 
  272     def test_update_action_override(self):
  273         action = mock.Mock()
  274         action.data = {
  275             'deletion': {
  276                 'count': 3,
  277             }
  278         }
  279 
  280         policy = dp.DeletionPolicy('test-policy', self.spec)
  281 
  282         policy._update_action(action, ['N1', 'N2'])
  283 
  284         pd = {
  285             'status': 'OK',
  286             'reason': 'Candidates generated',
  287             'deletion': {
  288                 'count': 2,
  289                 'candidates': ['N1', 'N2'],
  290                 'destroy_after_deletion': True,
  291                 'grace_period': 60,
  292                 'reduce_desired_capacity': False,
  293             }
  294         }
  295         self.assertEqual(pd, action.data)
  296         action.store.assert_called_with(action.context)
  297 
  298     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  299     def test_pre_op_del_nodes(self, mock_update):
  300         action = mock.Mock()
  301         action.context = self.context
  302         action.inputs = {
  303             'count': 2,
  304             'candidates': ['N1', 'N2'],
  305         }
  306         action.data = {}
  307         policy = dp.DeletionPolicy('test-policy', self.spec)
  308 
  309         policy.pre_op('FAKE_ID', action)
  310 
  311         mock_update.assert_called_once_with(action, ['N1', 'N2'])
  312 
  313     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  314     def test_pre_op_node_delete(self, mock_update):
  315         action = mock.Mock(action=consts.NODE_DELETE, context=self.context,
  316                            inputs={}, data={}, entity=mock.Mock(id='NODE_ID'))
  317         policy = dp.DeletionPolicy('test-policy', self.spec)
  318 
  319         policy.pre_op('FAKE_ID', action)
  320 
  321         mock_update.assert_called_once_with(action, ['NODE_ID'])
  322 
  323     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  324     @mock.patch.object(su, 'nodes_by_age')
  325     def test_pre_op_with_count_decisions(self, mock_select, mock_update):
  326         action = mock.Mock(context=self.context, inputs={},
  327                            data={'deletion': {'count': 2}})
  328         cluster = mock.Mock(nodes=['a', 'b', 'c'])
  329         action.entity = cluster
  330         mock_select.return_value = ['NODE1', 'NODE2']
  331         policy = dp.DeletionPolicy('test-policy', self.spec)
  332 
  333         policy.pre_op('FAKE_ID', action)
  334 
  335         mock_update.assert_called_once_with(action, ['NODE1', 'NODE2'])
  336         mock_select.assert_called_once_with(cluster.nodes, 2, True)
  337 
  338     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  339     @mock.patch.object(dp.DeletionPolicy, '_victims_by_regions')
  340     def test_pre_op_with_region_decisions(self, mock_select, mock_update):
  341         action = mock.Mock(context=self.context, inputs={})
  342         action.data = {
  343             'deletion': {
  344                 'count': 2,
  345                 'regions': {
  346                     'R1': 1,
  347                     'R2': 1
  348                 }
  349             }
  350         }
  351         cluster = mock.Mock(nodes=['a', 'b', 'c'])
  352         action.entity = cluster
  353         mock_select.return_value = ['NODE1', 'NODE2']
  354         policy = dp.DeletionPolicy('test-policy', self.spec)
  355 
  356         policy.pre_op('FAKE_ID', action)
  357 
  358         mock_update.assert_called_once_with(action, ['NODE1', 'NODE2'])
  359         mock_select.assert_called_once_with(cluster, {'R1': 1, 'R2': 1})
  360 
  361     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  362     @mock.patch.object(dp.DeletionPolicy, '_victims_by_zones')
  363     def test_pre_op_with_zone_decisions(self, mock_select, mock_update):
  364         action = mock.Mock(context=self.context, inputs={})
  365         action.data = {
  366             'deletion': {
  367                 'count': 2,
  368                 'zones': {
  369                     'AZ1': 1,
  370                     'AZ2': 1
  371                 }
  372             }
  373         }
  374         cluster = mock.Mock(nodes=['a', 'b', 'c'])
  375         action.entity = cluster
  376         mock_select.return_value = ['NODE1', 'NODE2']
  377         policy = dp.DeletionPolicy('test-policy', self.spec)
  378 
  379         policy.pre_op('FAKE_ID', action)
  380 
  381         mock_update.assert_called_once_with(action, ['NODE1', 'NODE2'])
  382         mock_select.assert_called_once_with(cluster, {'AZ1': 1, 'AZ2': 1})
  383 
  384     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  385     @mock.patch.object(su, 'nodes_by_age')
  386     def test_pre_op_scale_in_with_count(self, mock_select, mock_update):
  387         action = mock.Mock(context=self.context, data={}, inputs={'count': 2},
  388                            action=consts.CLUSTER_SCALE_IN)
  389         cluster = mock.Mock(nodes=[mock.Mock()])
  390         action.entity = cluster
  391         mock_select.return_value = ['NODE_ID']
  392         policy = dp.DeletionPolicy('test-policy', self.spec)
  393 
  394         policy.pre_op('FAKE_ID', action)
  395 
  396         mock_update.assert_called_once_with(action, ['NODE_ID'])
  397         # the following was invoked with 1 because the input count is
  398         # greater than the cluster size
  399         mock_select.assert_called_once_with(cluster.nodes, 1, True)
  400 
  401     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  402     @mock.patch.object(su, 'nodes_by_age')
  403     def test_pre_op_scale_in_without_count(self, mock_select, mock_update):
  404         action = mock.Mock(context=self.context, data={}, inputs={},
  405                            action=consts.CLUSTER_SCALE_IN)
  406         cluster = mock.Mock(nodes=[mock.Mock()])
  407         action.entity = cluster
  408         mock_select.return_value = ['NODE_ID']
  409         policy = dp.DeletionPolicy('test-policy', self.spec)
  410 
  411         policy.pre_op('FAKE_ID', action)
  412 
  413         mock_update.assert_called_once_with(action, ['NODE_ID'])
  414         # the following was invoked with 1 because the input count is
  415         # not specified so 1 becomes the default
  416         mock_select.assert_called_once_with(cluster.nodes, 1, True)
  417 
  418     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  419     @mock.patch.object(su, 'parse_resize_params')
  420     def test_pre_op_resize_failed_parse(self, mock_parse, mock_update):
  421         action = mock.Mock(context=self.context, inputs={}, data={},
  422                            action=consts.CLUSTER_RESIZE)
  423         cluster = mock.Mock(nodes=[mock.Mock(), mock.Mock()])
  424         action.entity = cluster
  425         mock_parse.return_value = 'ERROR', 'Failed parsing.'
  426         policy = dp.DeletionPolicy('test-policy', self.spec)
  427 
  428         policy.pre_op('FAKE_ID', action)
  429 
  430         self.assertEqual('ERROR', action.data['status'])
  431         self.assertEqual('Failed parsing.', action.data['reason'])
  432         mock_parse.assert_called_once_with(action, cluster, 2)
  433         self.assertEqual(0, mock_update.call_count)
  434 
  435     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  436     @mock.patch.object(su, 'parse_resize_params')
  437     def test_pre_op_resize_not_deletion(self, mock_parse, mock_update):
  438         def fake_parse(action, cluster, current):
  439             action.data = {}
  440             return 'OK', 'cool'
  441 
  442         action = mock.Mock(context=self.context, inputs={},
  443                            action=consts.CLUSTER_RESIZE)
  444         cluster = mock.Mock(nodes=[mock.Mock(), mock.Mock()])
  445         action.entity = cluster
  446         mock_parse.side_effect = fake_parse
  447         policy = dp.DeletionPolicy('test-policy', self.spec)
  448         # a simulation of non-deletion RESZIE
  449         action.data = {}
  450 
  451         policy.pre_op('FAKE_ID', action)
  452 
  453         mock_parse.assert_called_once_with(action, cluster, 2)
  454         self.assertEqual(0, mock_update.call_count)
  455 
  456     @mock.patch.object(su, 'parse_resize_params')
  457     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  458     @mock.patch.object(su, 'nodes_by_age')
  459     def test_pre_op_resize_with_count(self, mock_select, mock_update,
  460                                       mock_parse):
  461         def fake_parse(a, cluster, current):
  462             a.data = {
  463                 'deletion': {
  464                     'count': 2
  465                 }
  466             }
  467             return 'OK', 'cool'
  468 
  469         action = mock.Mock(context=self.context, inputs={}, data={},
  470                            action=consts.CLUSTER_RESIZE)
  471         cluster = mock.Mock(nodes=[mock.Mock(), mock.Mock()])
  472         action.entity = cluster
  473         mock_parse.side_effect = fake_parse
  474         mock_select.return_value = ['NID']
  475         policy = dp.DeletionPolicy('test-policy', self.spec)
  476 
  477         policy.pre_op('FAKE_ID', action)
  478 
  479         mock_parse.assert_called_once_with(action, cluster, 2)
  480         mock_update.assert_called_once_with(action, ['NID'])
  481 
  482     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  483     @mock.patch.object(su, 'nodes_by_random')
  484     def test_pre_op_do_random(self, mock_select, mock_update):
  485         action = mock.Mock(context=self.context, inputs={},
  486                            data={'deletion': {'count': 2}})
  487         cluster = mock.Mock(nodes=['a', 'b', 'c'])
  488         action.entity = cluster
  489         mock_select.return_value = ['NODE1', 'NODE2']
  490         spec = copy.deepcopy(self.spec)
  491         spec['properties']['criteria'] = 'RANDOM'
  492         policy = dp.DeletionPolicy('test-policy', spec)
  493 
  494         policy.pre_op('FAKE_ID', action)
  495 
  496         mock_select.assert_called_once_with(cluster.nodes, 2)
  497         mock_update.assert_called_once_with(action, ['NODE1', 'NODE2'])
  498 
  499     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  500     @mock.patch.object(su, 'nodes_by_profile_age')
  501     def test_pre_op_do_oldest_profile(self, mock_select, mock_update):
  502         action = mock.Mock(context=self.context, inputs={},
  503                            data={'deletion': {'count': 2}})
  504         mock_select.return_value = ['NODE1', 'NODE2']
  505         cluster = mock.Mock(nodes=['a', 'b', 'c'])
  506         action.entity = cluster
  507         spec = copy.deepcopy(self.spec)
  508         spec['properties']['criteria'] = 'OLDEST_PROFILE_FIRST'
  509         policy = dp.DeletionPolicy('test-policy', spec)
  510 
  511         policy.pre_op('FAKE_ID', action)
  512 
  513         mock_select.assert_called_once_with(cluster.nodes, 2)
  514         mock_update.assert_called_once_with(action, ['NODE1', 'NODE2'])
  515 
  516     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  517     @mock.patch.object(su, 'nodes_by_age')
  518     def test_pre_op_do_oldest_first(self, mock_select, mock_update):
  519         action = mock.Mock(context=self.context, inputs={},
  520                            data={'deletion': {'count': 2}})
  521         cluster = mock.Mock(nodes=['a', 'b', 'c'])
  522         action.entity = cluster
  523         mock_select.return_value = ['NODE1', 'NODE2']
  524         spec = copy.deepcopy(self.spec)
  525         spec['properties']['criteria'] = 'OLDEST_FIRST'
  526         policy = dp.DeletionPolicy('test-policy', spec)
  527 
  528         policy.pre_op('FAKE_ID', action)
  529 
  530         mock_select.assert_called_once_with(cluster.nodes, 2, True)
  531         mock_update.assert_called_once_with(action, ['NODE1', 'NODE2'])
  532 
  533     @mock.patch.object(dp.DeletionPolicy, '_update_action')
  534     @mock.patch.object(su, 'nodes_by_age')
  535     def test_pre_op_do_youngest_first(self, mock_select, mock_update):
  536         action = mock.Mock(context=self.context, inputs={},
  537                            data={'deletion': {'count': 2}})
  538         cluster = mock.Mock(nodes=['a', 'b', 'c'])
  539         action.entity = cluster
  540         mock_select.return_value = ['NODE1', 'NODE2']
  541         spec = copy.deepcopy(self.spec)
  542         spec['properties']['criteria'] = 'YOUNGEST_FIRST'
  543         policy = dp.DeletionPolicy('test-policy', spec)
  544 
  545         policy.pre_op('FAKE_ID', action)
  546 
  547         mock_select.assert_called_once_with(cluster.nodes, 2, False)
  548         mock_update.assert_called_once_with(action, ['NODE1', 'NODE2'])