def test_equivalent_definition_and_eq(self): """ If every attribute is the same, both :func:`CLBDescription.__eq__` and :func:`CLBDescription.equivalent_definition` will return `True`. """ desc1 = CLBDescription(lb_id='12345', port=80) desc2 = CLBDescription(lb_id='12345', port=80) self.assertTrue(desc1.equivalent_definition(desc2)) self.assertEquals(desc1, desc2)
def test_neither_equivalent_definition_or_eq(self): """ If ``lb_id`` and ``port`` are different, even if everything else are the same, neither :func:`CLBDescription.equivalent_definition` nor :func:`CLBDescription.__eq__` will return `True`. """ desc1 = CLBDescription(lb_id='12345', port=80) desc2 = CLBDescription(lb_id='12345', port=8080) self.assertFalse(desc1.equivalent_definition(desc2)) self.assertNotEqual(desc1, desc2)
def test_only_eq_and_equivalent_definition_to_other_CLBDescriptions(self): """ :func:`CLBDescriptionTest.__eq__` and :func:`CLBDescriptionTest.equivalent_definition` return false if compared to a non-:class:`CLBDescription` :class:`ILBDescription` provider. """ desc = CLBDescription(lb_id='12345', port=80) fake = DummyLBDescription() self.assertNotEqual(desc, fake) self.assertFalse(desc.equivalent_definition(fake))
def test_equivalent_definition_but_not_eq(self): """ Even if weight, etc. are different, so long as the ``lb_id`` and ``port`` are identical, :func:`CLBDescription.equivalent_definition` will return `True`. :func:`CLBDescription.__eq__` will not, however. """ desc1 = CLBDescription(lb_id='12345', port=80) desc2 = CLBDescription(lb_id='12345', port=80, weight=2, condition=CLBNodeCondition.DISABLED, type=CLBNodeType.SECONDARY) self.assertTrue(desc1.equivalent_definition(desc2)) self.assertNotEqual(desc1, desc2)
def test_inactive_if_node_is_disabled(self): """ If the node is DRAINING, :func:`CLBNode.is_active` returns `True`. """ node = CLBNode(node_id='1234', description=CLBDescription( lb_id='12345', port=80, condition=CLBNodeCondition.DISABLED), address='10.1.1.1', drained_at=0.0, connections=1) self.assertFalse(node.is_active())
def test_add_nodes_to_clb(self): """ :obj:`AddNodesToCLB` produces a request for adding any number of nodes to a cloud load balancer. """ lb_id = "12345" lb_nodes = pset([ ('1.2.3.4', CLBDescription(lb_id=lb_id, port=80)), ('1.2.3.4', CLBDescription(lb_id=lb_id, port=8080)), ('2.3.4.5', CLBDescription(lb_id=lb_id, port=80)) ]) step = AddNodesToCLB(lb_id=lb_id, address_configs=lb_nodes) eff = step.as_effect() self.assertEqual( eff.intent, service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'POST', "loadbalancers/12345/nodes", json_response=True, success_pred=ANY, data={"nodes": ANY}).intent) node_data = sorted(eff.intent.data['nodes'], key=lambda n: (n['address'], n['port'])) self.assertEqual(node_data, [ {'address': '1.2.3.4', 'port': 80, 'condition': 'ENABLED', 'type': 'PRIMARY', 'weight': 1}, {'address': '1.2.3.4', 'port': 8080, 'condition': 'ENABLED', 'type': 'PRIMARY', 'weight': 1}, {'address': '2.3.4.5', 'port': 80, 'condition': 'ENABLED', 'type': 'PRIMARY', 'weight': 1} ])
def test_success(self): """ The data is returned as a tuple of ([NovaServer], [CLBNode/RCv3Node]). """ clb_nodes = [ CLBNode(node_id='node1', address='ip1', description=CLBDescription(lb_id='lb1', port=80)) ] rcv3_nodes = [ RCv3Node(node_id='node2', cloud_server_id='a', description=RCv3Description(lb_id='lb2')) ] eff = get_all_launch_server_data( 'tid', 'gid', self.now, get_scaling_group_servers=_constant_as_eff( ('tid', 'gid', self.now), self.servers), get_clb_contents=_constant_as_eff((), (clb_nodes, { 'lb1': CLB(True), 'lb2': CLB(False) })), get_rcv3_contents=_constant_as_eff((), rcv3_nodes)) expected_servers = [ server('a', ServerState.ACTIVE, servicenet_address='10.0.0.1', links=freeze([{ 'href': 'link1', 'rel': 'self' }]), json=freeze(self.servers[0])), server('b', ServerState.ACTIVE, created=1, servicenet_address='10.0.0.2', links=freeze([{ 'href': 'link2', 'rel': 'self' }]), json=freeze(self.servers[1])) ] self.assertEqual( resolve_stubs(eff), { 'servers': expected_servers, 'lb_nodes': clb_nodes + rcv3_nodes, 'lbs': { 'lb1': CLB(True), 'lb2': CLB(False) } })
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='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')), # 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='5', node_ids=s('1', '2')), RemoveNodesFromCLB(lb_id='6', node_ids=s('3', '4')), # Unoptimizable steps CreateServer(server_config=pmap({})) ]))
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_with_clb_and_rackconnect(self): """ LB config with both CLBs and rackconnect. """ self.assertEqual( json_to_LBConfigs( [{'loadBalancerId': 20, 'port': 80}, {'loadBalancerId': 20, 'port': 800}, {'loadBalancerId': 20, 'type': 'RackConnectV3'}, {'loadBalancerId': 200, 'type': 'RackConnectV3'}, {'loadBalancerId': 21, 'port': 81}, {'loadBalancerId': 'cebdc220-172f-4b10-9f29-9c7e980ba41d', 'type': 'RackConnectV3'}]), pset([ CLBDescription(lb_id='20', port=80), CLBDescription(lb_id='20', port=800), CLBDescription(lb_id='21', port=81), RCv3Description(lb_id='20'), RCv3Description(lb_id='200'), RCv3Description(lb_id='cebdc220-172f-4b10-9f29-9c7e980ba41d') ]))
def test_optimize_clb_adds(self): """ Multiple :class:`AddNodesToCLB` steps for the same LB are merged into one. """ steps = pbag([ 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.2.3.4', CLBDescription(lb_id='5', port=80)))) ]) self.assertEqual( optimize_steps(steps), pbag([ AddNodesToCLB( lb_id='5', address_configs=s( ('1.1.1.1', CLBDescription(lb_id='5', port=80)), ('1.2.3.4', CLBDescription(lb_id='5', port=80)))) ]))
def json_to_LBConfigs(lbs_json): """ Convert load balancer config from JSON to :obj:`CLBDescription` :param lbs_json: Sequence of load balancer configs :return: Sequence of :class:`ILBDescription` providers """ by_type = groupby(lambda lb: lb.get('type', 'CloudLoadBalancer'), lbs_json) return pset( [CLBDescription(lb_id=str(lb['loadBalancerId']), port=lb['port']) for lb in by_type.get('CloudLoadBalancer', [])] + [RCv3Description(lb_id=str(lb['loadBalancerId'])) for lb in by_type.get('RackConnectV3', [])] )
def test_with_lb_metadata(self): """ Create a server that has load balancer config metadata in it. The only desired load balancers created are the ones with valid data. """ self.servers[0]['metadata'] = { # correct clb config 'rax:autoscale:lb:CloudLoadBalancer:1': '[{"port":80},{"port":90}]', # invalid because there is no port "rax:autoscale:lb:CloudLoadBalancer:2": '[{}]', # two correct lbconfigs and one incorrect one 'rax:autoscale:lb:CloudLoadBalancer:3': '[{"port":80},{"bad":"1"},{"port":90}]', # a dictionary instead of a list 'rax:autoscale:lb:CloudLoadBalancer:4': '{"port": 80}', # not even valid json 'rax:autoscale:lb:CloudLoadBalancer:5': 'invalid json string', # RCv3 with same LB id as CLB 'rax:autoscale:lb:RackConnectV3:1': '' } self.assertEqual( NovaServer.from_server_details_json(self.servers[0]), NovaServer(id='a', state=ServerState.ACTIVE, image_id='valid_image', flavor_id='valid_flavor', created=self.createds[0], desired_lbs=pset([ CLBDescription(lb_id='1', port=80), CLBDescription(lb_id='1', port=90), RCv3Description(lb_id='1')]), servicenet_address='', links=freeze(self.servers[0]['links']), json=freeze(self.servers[0])))
def test_generate_metadata(self): """ :func:`NovaServer.lbs_from_metadata` produces a dictionary with metadata for the group ID and any load balancers provided. """ lbs = [ CLBDescription(port=80, lb_id='123'), RCv3Description(lb_id='123'), CLBDescription(port=8080, lb_id='123'), CLBDescription(port=80, lb_id='234'), RCv3Description(lb_id='3232'), ] expected = { 'rax:autoscale:group:id': 'group_id', 'rax:auto_scaling_group_id': 'group_id', 'rax:autoscale:lb:CloudLoadBalancer:123': ( '[{"port": 80}, {"port": 8080}]'), 'rax:autoscale:lb:CloudLoadBalancer:234': '[{"port": 80}]', 'rax:autoscale:lb:RackConnectV3:123': '', 'rax:autoscale:lb:RackConnectV3:3232': '', } self.assertEqual(generate_metadata('group_id', lbs), expected)
def test_from_node_json_no_weight(self): """ A node's JSON representation can be parsed to a :obj:`CLBNode` object with a `CLBDescription`. When weight is not specified it defaults to 1. """ node_json = {'id': 'node1', 'address': '1.2.3.4', 'port': 20, 'condition': 'DRAINING', 'type': 'SECONDARY'} node = CLBNode.from_node_json(123, node_json) self.assertEqual( node, CLBNode(node_id='node1', address='1.2.3.4', connections=None, drained_at=0.0, description=CLBDescription( lb_id='123', port=20, weight=1, condition=CLBNodeCondition.DRAINING, type=CLBNodeType.SECONDARY)))
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_clb_adds_multiple_load_balancers(self): """ Aggregation is done on a per-load-balancer basis when adding to a CLB. """ steps = pbag([ 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)))), ]) self.assertEqual( optimize_steps(steps), pbag([ 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)))), ]))
class CLBNodeTests(SynchronousTestCase): """ Tests for :class:`CLBNode`. """ desc = CLBDescription(lb_id='12345', port=80) drain_desc = CLBDescription(lb_id='12345', port=80, condition=CLBNodeCondition.DRAINING) def test_provides_ILBDescription_and_IDrainable(self): """ An instance of :class:`CLBNode` provides :class:`ILBNode` and :class:`IDrainable`. """ node = CLBNode(node_id='1234', description=self.desc, address='10.1.1.1') self.assertTrue(ILBNode.providedBy(node)) self.assertTrue(IDrainable.providedBy(node)) def test_matches_only_works_with_NovaServers(self): """ :func:`CLBNode.matches` returns false if the server is not an instance of :class:`NovaServer`. """ node = CLBNode(node_id='1234', description=self.desc, address='10.1.1.1') self.assertFalse( node.matches(DummyServer(servicenet_address="10.1.1.1"))) def test_matches_only_if_server_address_matches_node_address(self): """ :func:`CLBNode.matches` returns True only if the :class:`NovaServer` has the same ServiceNet address as the node address """ node = CLBNode(node_id='1234', description=self.desc, address='10.1.1.1') self.assertFalse(node.matches( NovaServer(id='1', state=ServerState.ACTIVE, created=0.0, servicenet_address="10.1.1.2", image_id='image', flavor_id='flavor'))) self.assertTrue(node.matches( NovaServer(id='1', state=ServerState.ACTIVE, created=0.0, servicenet_address="10.1.1.1", image_id='image', flavor_id='flavor'))) def test_current_draining_true_if_description_is_draining(self): """ :func:`CLBNode.currently_draining` returns `True` if `CLBNode.description.condition` is :obj:`CLBNodeCondition.DRAINING` """ node = CLBNode(node_id='1234', description=self.drain_desc, address='10.1.1.1') self.assertTrue(node.currently_draining()) def test_current_draining_false_if_description_not_draining(self): """ :func:`CLBNode.currently_draining` returns `False` if `CLBNode.description.condition` is not :obj:`CLBNodeCondition.DRAINING` """ node = CLBNode(node_id='1234', description=self.desc, address='10.1.1.1') self.assertFalse(node.currently_draining()) def test_done_draining_past_timeout_even_if_there_are_connections(self): """ If there are still connections, but the node has been in draining past the timeout, :func:`CLBNode.is_done_draining` returns `True`. """ node = CLBNode(node_id='1234', description=self.drain_desc, address='10.1.1.1', drained_at=0.0, connections=1) self.assertTrue(node.is_done_draining(now=30, timeout=15)) def test_done_draining_past_timeout_even_if_no_connection_info(self): """ If connection information is not provided, and the node has been in draining past the timeout, :func:`CLBNode.is_done_draining` returns `True`. """ node = CLBNode(node_id='1234', description=self.drain_desc, address='10.1.1.1', drained_at=0.0) self.assertTrue(node.is_done_draining(now=30, timeout=15)) def test_done_draining_before_timeout_if_there_are_no_connections(self): """ If there are zero connections, but the node has been in draining less than the timeout, :func:`CLBNode.is_done_draining` returns `True`. """ node = CLBNode(node_id='1234', description=self.drain_desc, address='10.1.1.1', drained_at=0.0, connections=0) self.assertTrue(node.is_done_draining(now=15, timeout=30)) def test_not_done_draining_before_timeout_if_no_connection_info(self): """ If connection information is not provided, and the node has been in draining less than the timeout, :func:`CLBNode.is_done_draining` returns `False`. """ node = CLBNode(node_id='1234', description=self.drain_desc, address='10.1.1.1', drained_at=0.0) self.assertFalse(node.is_done_draining(now=15, timeout=30)) def test_active_if_node_is_enabled(self): """ If the node is ENABLED, :func:`CLBNode.is_active` returns `True`. """ node = CLBNode(node_id='1234', description=self.desc, address='10.1.1.1', drained_at=0.0, connections=1) self.assertTrue(node.is_active()) def test_active_if_node_is_draining(self): """ If the node is DRAINING, :func:`CLBNode.is_active` returns `True`. """ node = CLBNode(node_id='1234', description=self.drain_desc, address='10.1.1.1', drained_at=0.0, connections=1) self.assertTrue(node.is_active()) def test_inactive_if_node_is_disabled(self): """ If the node is DRAINING, :func:`CLBNode.is_active` returns `True`. """ node = CLBNode(node_id='1234', description=CLBDescription( lb_id='12345', port=80, condition=CLBNodeCondition.DISABLED), address='10.1.1.1', drained_at=0.0, connections=1) self.assertFalse(node.is_active()) def test_from_node_json_no_weight(self): """ A node's JSON representation can be parsed to a :obj:`CLBNode` object with a `CLBDescription`. When weight is not specified it defaults to 1. """ node_json = {'id': 'node1', 'address': '1.2.3.4', 'port': 20, 'condition': 'DRAINING', 'type': 'SECONDARY'} node = CLBNode.from_node_json(123, node_json) self.assertEqual( node, CLBNode(node_id='node1', address='1.2.3.4', connections=None, drained_at=0.0, description=CLBDescription( lb_id='123', port=20, weight=1, condition=CLBNodeCondition.DRAINING, type=CLBNodeType.SECONDARY))) def test_from_node_json_with_weight(self): """ A node's JSON representation can be parsed to a :obj:`CLBNode` object with a `CLBDescription`. When weight is not specified it defaults to 1. """ node_json = {'id': 'node1', 'address': '1.2.3.4', 'port': 20, 'condition': 'DRAINING', 'type': 'SECONDARY', 'weight': 50} node = CLBNode.from_node_json(123, node_json) self.assertEqual( node, CLBNode(node_id='node1', address='1.2.3.4', connections=None, drained_at=0.0, description=CLBDescription( lb_id='123', port=20, weight=50, condition=CLBNodeCondition.DRAINING, type=CLBNodeType.SECONDARY)))
def _clbd(lbid, port): return CLBDescription(lb_id=lbid, port=port)
def test_provides_ILBDescription(self): """ An instance of :class:`CLBDescription` provides :class:`ILBDescription`. """ desc = CLBDescription(lb_id='12345', port=80) self.assertTrue(ILBDescription.providedBy(desc))