def test_filters_clb_types(self): """ Only one CLB step is returned per CLB """ steps = pbag([ AddNodesToCLB(lb_id='5', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='5', port=80)))), RemoveNodesFromCLB(lb_id='5', node_ids=s('1')), # Unoptimizable step CreateServer(server_config=pmap({})), ]) # returned steps could be pbag of any of the 2 lists below depending # on how `one_clb_step` iterates over the steps. Since it is pbag the # order of elements is not guaranteed list1 = [ AddNodesToCLB(lb_id='5', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='5', port=80)))), CreateServer(server_config=pmap({})) ] list2 = [ RemoveNodesFromCLB(lb_id='5', node_ids=s('1')), CreateServer(server_config=pmap({})) ] self.assertEqual( matches(MatchesAny(Equals(pbag(list1)), Equals(pbag(list2)))), optimize_steps(steps))
def test_remove_nodes_from_clb_predicate(self): """ :obj:`RemoveNodesFromCLB` only succeeds on 202. """ lb_id = "12345" node_ids = [str(i) for i in range(5)] step = RemoveNodesFromCLB(lb_id=lb_id, node_ids=pset(node_ids)) request = step.as_effect() self.assertTrue(request.intent.json_response) predicate = request.intent.success_pred self.assertTrue(predicate(StubResponse(202, {}), None)) self.assertFalse(predicate(StubResponse(200, {}), None))
def test_mixed_optimization(self): """ Mixes of optimizable and unoptimizable steps still get optimized correctly. """ steps = pbag([ # CLB adds AddNodesToCLB(lb_id='5', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='5', port=80)))), AddNodesToCLB(lb_id='5', address_configs=s( ('1.1.1.2', CLBDescription(lb_id='5', port=80)))), AddNodesToCLB(lb_id='6', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='6', port=80)))), AddNodesToCLB(lb_id='6', address_configs=s( ('1.1.1.2', CLBDescription(lb_id='6', port=80)))), RemoveNodesFromCLB(lb_id='7', node_ids=s('1')), RemoveNodesFromCLB(lb_id='7', node_ids=s('2')), # Unoptimizable steps CreateServer(server_config=pmap({})), ]) self.assertEqual( optimize_steps(steps), pbag([ # Optimized CLB adds AddNodesToCLB( lb_id='5', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='5', port=80)), ('1.1.1.2', CLBDescription(lb_id='5', port=80)))), AddNodesToCLB( lb_id='6', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='6', port=80)), ('1.1.1.2', CLBDescription(lb_id='6', port=80)))), RemoveNodesFromCLB(lb_id='7', node_ids=s('1', '2')), # Unoptimizable steps CreateServer(server_config=pmap({})) ]))
def test_remove_nodes_from_clb_retry(self): """ :obj:`RemoveNodesFromCLB`, on receiving a 400, parses out the nodes that are no longer on the load balancer, and retries the bulk delete with those nodes removed. TODO: this has been left in as a regression test - this can probably be removed the next time it's touched, as this functionality happens in cloud_client now and there is a similar test there. """ lb_id = "12345" node_ids = [str(i) for i in range(5)] error_body = { "validationErrors": { "messages": [ "Node ids 1,2,3 are not a part of your loadbalancer" ] }, "message": "Validation Failure", "code": 400, "details": "The object is not valid" } expected_req = service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'DELETE', 'loadbalancers/12345/nodes', params={'id': transform_eq(sorted, node_ids)}, success_pred=ANY, json_response=True).intent expected_req2 = service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'DELETE', 'loadbalancers/12345/nodes', params={'id': transform_eq(sorted, ['0', '4'])}, success_pred=ANY, json_response=True).intent step = RemoveNodesFromCLB(lb_id=lb_id, node_ids=pset(node_ids)) seq = [ (expected_req, lambda i: raise_(APIError(400, json.dumps(error_body)))), (expected_req2, lambda i: stub_pure_response('', 202)), ] r = perform_sequence(seq, step.as_effect()) self.assertEqual(r, (StepResult.SUCCESS, []))
def test_optimize_clb_removes(self): """ Aggregation is done on a per-load-balancer basis when remove nodes from a CLB. """ steps = pbag([ RemoveNodesFromCLB(lb_id='5', node_ids=s('1')), RemoveNodesFromCLB(lb_id='5', node_ids=s('2')), RemoveNodesFromCLB(lb_id='5', node_ids=s('3')), RemoveNodesFromCLB(lb_id='5', node_ids=s('4')) ]) self.assertEqual( optimize_steps(steps), pbag([ RemoveNodesFromCLB(lb_id='5', node_ids=s('1', '2', '3', '4')) ]))
def test_remove_nodes_from_clb(self): """ :obj:`RemoveNodesFromCLB` produces a request for deleting any number of nodes from a cloud load balancer. """ lb_id = "12345" node_ids = [str(i) for i in range(5)] step = RemoveNodesFromCLB(lb_id=lb_id, node_ids=pset(node_ids)) request = step.as_effect() self.assertEqual( request.intent, service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'DELETE', "loadbalancers/12345/nodes", params={'id': transform_eq(sorted, node_ids)}, json_response=True, success_pred=ANY).intent)
def test_mixed(self): """ When there are multiple steps of same CLB then first step of each CLB is returned """ steps = [ CreateServer(server_config=pmap({"name": "server"})), DeleteServer(server_id="abc"), AddNodesToCLB(lb_id='5', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='5', port=80)))), RemoveNodesFromCLB(lb_id='5', node_ids=s('1')), RemoveNodesFromCLB(lb_id='6', node_ids=s('3')), AddNodesToCLB(lb_id='6', address_configs=s( ('2.1.1.1', CLBDescription(lb_id='6', port=80)))), ChangeCLBNode(lb_id='7', node_id='9', condition=CLBNodeCondition.ENABLED, weight=10, type=CLBNodeType.PRIMARY), RemoveNodesFromCLB(lb_id='7', node_ids=s('4')), AddNodesToCLB(lb_id='7', address_configs=s( ('3.1.1.1', CLBDescription(lb_id='9', port=80)))), ChangeCLBNode(lb_id='5', node_id='11', condition=CLBNodeCondition.ENABLED, weight=10, type=CLBNodeType.PRIMARY) ] self.assertEqual( list(one_clb_step(steps)), ( steps[:3] + # Non-CLB steps and 1 step for CLB 5 [steps[4]] + # One step for CLB 6 [steps[6]]) # One step for CLB 7 )
def test_remove_nodes_from_clbs(self): """Logs :obj:`RemoveNodesFromCLB`.""" removes = pbag([ RemoveNodesFromCLB(lb_id='lbid1', node_ids=pset(['a', 'b', 'c'])), RemoveNodesFromCLB(lb_id='lbid2', node_ids=pset(['d', 'e', 'f'])) ]) self.assert_logs(removes, [ Log('convergence-remove-clb-nodes', fields={ 'lb_id': 'lbid1', 'nodes': ['a', 'b', 'c'], 'cloud_feed': True }), Log('convergence-remove-clb-nodes', fields={ 'lb_id': 'lbid2', 'nodes': ['d', 'e', 'f'], 'cloud_feed': True }), ])
def remove_node_from_lb(node): """ Remove a node from the load balancing entity. :ivar node: The node to be removed. :type node: :class:`ILBNode` provider """ if isinstance(node, CLBNode): return RemoveNodesFromCLB(lb_id=node.description.lb_id, node_ids=pset([node.node_id])) elif isinstance(node, RCv3Node): return BulkRemoveFromRCv3(lb_node_pairs=pset( [(node.description.lb_id, node.cloud_server_id)]))
def test_remove_nodes_from_clb_success_failures(self): """ :obj:`AddNodesToCLB` succeeds if the CLB is not in existence (has been deleted or is not found). """ successes = [CLBNotFoundError(lb_id=u'12345'), CLBDeletedError(lb_id=u'12345'), NoSuchCLBError(lb_id=u'12345')] eff = RemoveNodesFromCLB(lb_id='12345', node_ids=pset(['1', '2'])).as_effect() for exc in successes: seq = SequenceDispatcher([(eff.intent, lambda i: raise_(exc))]) with seq.consume(): self.assertEquals(sync_perform(seq, eff), (StepResult.SUCCESS, []))
def test_remove_nodes_from_clb_terminal_failures(self): """ :obj:`AddNodesToCLB` fails if there are any 4xx errors, then the error is propagated up and the result is a failure. """ terminals = (APIError(code=403, body="You're out of luck."), APIError(code=422, body="Oh look another 422.")) eff = RemoveNodesFromCLB(lb_id='12345', node_ids=pset(['1', '2'])).as_effect() for exc in terminals: seq = SequenceDispatcher([(eff.intent, lambda i: raise_(exc))]) with seq.consume(): self.assertEquals( sync_perform(seq, eff), (StepResult.FAILURE, [ErrorReason.Exception( matches(ContainsAll([type(exc), exc])))]))
def test_optimize_leaves_other_steps(self): """ Unoptimizable steps pass the optimizer unchanged. """ steps = pbag([ AddNodesToCLB(lb_id='5', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='5', port=80)))), RemoveNodesFromCLB(lb_id='6', node_ids=s('1')), CreateServer(server_config=pmap({})), BulkRemoveFromRCv3(lb_node_pairs=pset([("lb-1", "node-a")])), BulkAddToRCv3(lb_node_pairs=pset([("lb-2", "node-b")])) # Note that the add & remove pair should not be the same; # the optimizer might reasonably optimize opposite # operations away in the future. ]) self.assertEqual(optimize_steps(steps), steps)
def test_remove_nodes_from_clb_non_terminal_failures_to_retry(self): """ :obj:`RemoveNodesFromCLB` retries if the CLB is temporarily locked, or if the request was rate-limited, or if there was an API error and the error is unknown but not a 4xx. """ non_terminals = (CLBImmutableError(lb_id=u"12345"), CLBRateLimitError(lb_id=u"12345"), APIError(code=500, body="oops!"), TypeError("You did something wrong in your code.")) eff = RemoveNodesFromCLB(lb_id='12345', node_ids=pset(['1', '2'])).as_effect() for exc in non_terminals: seq = SequenceDispatcher([(eff.intent, lambda i: raise_(exc))]) with seq.consume(): self.assertEquals( sync_perform(seq, eff), (StepResult.RETRY, [ErrorReason.Exception( matches(ContainsAll([type(exc), exc])))]))
def _create_some_steps(self, counts={}): """ Creates some steps for testing. :param counts: A mapping of supported step classes to the number of those steps to create. If unspecified, assumed to be zero. :return: A pbag of steps. """ create_servers = [ CreateServer(server_config=pmap({"sentinel": i})) for i in xrange(counts.get(CreateServer, 0)) ] delete_servers = [ DeleteServer(server_id='abc-' + str(i)) for i in xrange(counts.get(DeleteServer, 0)) ] remove_from_clbs = [ RemoveNodesFromCLB(lb_id='1', node_ids=(str(i), )) for i in xrange(counts.get(RemoveNodesFromCLB, 0)) ] return pbag(create_servers + delete_servers + remove_from_clbs)
def test_clb_remove_multiple_load_balancers(self): """ Multiple :class:`RemoveNodesFromCLB` steps for the same LB are merged into one. """ steps = pbag([ RemoveNodesFromCLB(lb_id='5', node_ids=s('1')), RemoveNodesFromCLB(lb_id='5', node_ids=s('2')), RemoveNodesFromCLB(lb_id='6', node_ids=s('3')), RemoveNodesFromCLB(lb_id='6', node_ids=s('4')) ]) self.assertEqual( optimize_steps(steps), pbag([ RemoveNodesFromCLB(lb_id='5', node_ids=s('1', '2')), RemoveNodesFromCLB(lb_id='6', node_ids=s('3', '4')) ]))