From 3b23de32b81cc476d7cb9474575fc2f789b0cb99 Mon Sep 17 00:00:00 2001 From: Brandon Logan Date: Sat, 5 Sep 2015 05:20:22 -0500 Subject: [PATCH] Plug vip and networks by port mac address Co-Authored-By: German Eichberger Change-Id: Ic6206a97ad67f6364d850e033760ee60d7161f6f --- doc/source/main/api/haproxy-amphora-api.rst | 14 ++++++-- .../backends/agent/api_server/plug.py | 28 +++++++-------- .../backends/agent/api_server/server.py | 22 ++++++++---- octavia/amphorae/drivers/driver_base.py | 4 ++- .../drivers/haproxy/rest_api_driver.py | 27 ++++++++++----- .../amphorae/drivers/haproxy/ssh_driver.py | 12 ++++--- octavia/common/constants.py | 1 + .../controller/worker/flows/member_flows.py | 4 +-- .../worker/tasks/amphora_driver_tasks.py | 12 ++++--- .../controller/worker/tasks/network_tasks.py | 13 +++++-- octavia/network/data_models.py | 7 ++-- .../backend/agent/api_server/test_server.py | 34 ++++++++++++++----- .../drivers/haproxy/test_rest_api_driver.py | 14 +++++--- .../drivers/haproxy/test_ssh_driver.py | 27 ++++++++++----- .../worker/flows/test_member_flows.py | 2 +- .../worker/tasks/test_amphora_driver_tasks.py | 5 +-- specs/version0.5/amphora-driver-interface.rst | 6 +++- 17 files changed, 158 insertions(+), 74 deletions(-) diff --git a/doc/source/main/api/haproxy-amphora-api.rst b/doc/source/main/api/haproxy-amphora-api.rst index 9cbaa25e65..0b6665be0e 100644 --- a/doc/source/main/api/haproxy-amphora-api.rst +++ b/doc/source/main/api/haproxy-amphora-api.rst @@ -1088,6 +1088,7 @@ Plug VIP * *subnet_cidr*: The vip subnet in cidr notation * *gateway*: The vip subnet gateway address + * *mac_address*: The mac address of the interface to plug * **Success Response:** @@ -1137,7 +1138,8 @@ Plug VIP JSON POST parameters: { 'subnet_cidr': '203.0.113.0/24', - 'gateway': '203.0.113.1' + 'gateway': '203.0.113.1', + 'mac_address': '78:31:c1:ce:0b:3c' } JSON Response: @@ -1176,7 +1178,10 @@ Plug Network * **Method:** POST * **URL params:** none -* **Data params:** none +* **Data params:** + + * *mac_address*: The mac address of the interface to plug + * **Success Response:** * Code: 202 @@ -1212,6 +1217,11 @@ Plug Network POST URL: https://octavia-haproxy-img-00328.local/v0.1/plug/network/ + JSON POST parameters: + { + 'mac_address': '78:31:c1:ce:0b:3c' + } + JSON Response: { 'message': 'OK', diff --git a/octavia/amphorae/backends/agent/api_server/plug.py b/octavia/amphorae/backends/agent/api_server/plug.py index 8433ba8c8d..8d3dce6316 100644 --- a/octavia/amphorae/backends/agent/api_server/plug.py +++ b/octavia/amphorae/backends/agent/api_server/plug.py @@ -38,7 +38,7 @@ template_port = j2_env.get_template(ETH_X_VIP_CONF) template_vip = j2_env.get_template(ETH_PORT_CONF) -def plug_vip(vip, subnet_cidr, gateway): +def plug_vip(vip, subnet_cidr, gateway, mac_address): # validate vip try: socket.inet_aton(vip) @@ -46,7 +46,7 @@ def plug_vip(vip, subnet_cidr, gateway): return flask.make_response(flask.jsonify(dict( message="Invalid VIP")), 400) - interface = _interface_down() + interface = _interface_by_mac(mac_address) # assume for now only a fixed subnet size sections = vip.split('.')[:3] @@ -110,8 +110,8 @@ def plug_vip(vip, subnet_cidr, gateway): vip=vip, interface=interface))), 202) -def plug_network(): - interface = _interface_down() +def plug_network(mac_address): + interface = _interface_by_mac(mac_address) # write interface file with open(util.get_network_interface_file(interface), 'w') as text_file: @@ -127,17 +127,15 @@ def plug_network(): interface=interface))), 202) -def _interface_down(): - # Find the interface which is down - down = [interface for interface in netifaces.interfaces() if - netifaces.AF_INET not in netifaces.ifaddresses(interface)] - if len(down) != 1: - # There should only be ONE interface being plugged; if there is - # none down or more than one we have a problem... - raise exceptions.HTTPException( - response=flask.make_response(flask.jsonify(dict( - details="No suitable network interface found")), 404)) - return down[0] +def _interface_by_mac(mac): + for interface in netifaces.interfaces(): + if netifaces.AF_LINK in netifaces.ifaddresses(interface): + for link in netifaces.ifaddresses(interface)[netifaces.AF_LINK]: + if link.get('addr') == mac: + return interface + raise exceptions.HTTPException( + response=flask.make_response(flask.jsonify(dict( + details="No suitable network interface found")), 404)) def _bring_if_up(params, what): diff --git a/octavia/amphorae/backends/agent/api_server/server.py b/octavia/amphorae/backends/agent/api_server/server.py index 38d72c204d..a43383359d 100644 --- a/octavia/amphorae/backends/agent/api_server/server.py +++ b/octavia/amphorae/backends/agent/api_server/server.py @@ -115,20 +115,28 @@ def delete_certificate(listener_id, filename): def plug_vip(vip): # Catch any issues with the subnet info json try: - subnet_info = flask.request.get_json() - assert type(subnet_info) is dict - assert 'subnet_cidr' in subnet_info - assert 'gateway' in subnet_info + net_info = flask.request.get_json() + assert type(net_info) is dict + assert 'subnet_cidr' in net_info + assert 'gateway' in net_info + assert 'mac_address' in net_info except Exception: raise exceptions.BadRequest(description='Invalid subnet information') return plug.plug_vip(vip, - subnet_info['subnet_cidr'], - subnet_info['gateway']) + net_info['subnet_cidr'], + net_info['gateway'], + net_info['mac_address']) @app.route('/' + api_server.VERSION + '/plug/network', methods=['POST']) def plug_network(): - return plug.plug_network() + try: + port_info = flask.request.get_json() + assert type(port_info) is dict + assert 'mac_address' in port_info + except Exception: + raise exceptions.BadRequest(description='Invalid port information') + return plug.plug_network(port_info['mac_address']) @app.route('/' + api_server.VERSION + '/certificate', methods=['PUT']) diff --git a/octavia/amphorae/drivers/driver_base.py b/octavia/amphorae/drivers/driver_base.py index 3cfdb86ce3..0309a5694f 100644 --- a/octavia/amphorae/drivers/driver_base.py +++ b/octavia/amphorae/drivers/driver_base.py @@ -149,11 +149,13 @@ class AmphoraLoadBalancerDriver(object): """ pass - def post_network_plug(self, amphora): + def post_network_plug(self, amphora, port): """Called after amphora added to network :param amphora: amphora object, needs id and network ip(s) :type amphora: object + :param port: contains information of the plugged port + :type port: octavia.network.data_models.Port This method is optional to implement. After adding an amphora to a network, there may be steps necessary on the amphora to allow it to diff --git a/octavia/amphorae/drivers/haproxy/rest_api_driver.py b/octavia/amphorae/drivers/haproxy/rest_api_driver.py index b79ca8538b..11bf4de423 100644 --- a/octavia/amphorae/drivers/haproxy/rest_api_driver.py +++ b/octavia/amphorae/drivers/haproxy/rest_api_driver.py @@ -102,14 +102,22 @@ class HaproxyAmphoraLoadBalancerDriver(driver_base.AmphoraLoadBalancerDriver): def post_vip_plug(self, load_balancer, amphorae_network_config): for amp in load_balancer.amphorae: subnet = amphorae_network_config.get(amp.id).vip_subnet - subnet_info = {'subnet_cidr': subnet.cidr, - 'gateway': subnet.gateway_ip} + # NOTE(blogan): using the vrrp port here because that is what the + # allowed address pairs network driver sets this particular port + # to. This does expose a bit of tight coupling between the network + # driver and amphora driver. We will need to revisit this to + # try and remove this tight coupling. + port = amphorae_network_config.get(amp.id).vrrp_port + net_info = {'subnet_cidr': subnet.cidr, + 'gateway': subnet.gateway_ip, + 'mac_address': port.mac_address} self.client.plug_vip(amp, load_balancer.vip.ip_address, - subnet_info) + net_info) - def post_network_plug(self, amphora): - self.client.plug_network(amphora) + def post_network_plug(self, amphora, port): + port_info = {'mac_address': port.mac_address} + self.client.plug_network(amphora, port_info) def _process_tls_certificates(self, listener): """Processes TLS data from the listener. @@ -297,12 +305,13 @@ class AmphoraAPIClient(object): listener_id=listener_id, filename=pem_filename)) return exc.check_exception(r) - def plug_network(self, amp): - r = self.post(amp, 'plug/network') + def plug_network(self, amp, port): + r = self.post(amp, 'plug/network', + json=port) return exc.check_exception(r) - def plug_vip(self, amp, vip, subnet_info): + def plug_vip(self, amp, vip, net_info): r = self.post(amp, 'plug/vip/{vip}'.format(vip=vip), - json=subnet_info) + json=net_info) return exc.check_exception(r) diff --git a/octavia/amphorae/drivers/haproxy/ssh_driver.py b/octavia/amphorae/drivers/haproxy/ssh_driver.py index f36d1b3ee1..80baa2273b 100644 --- a/octavia/amphorae/drivers/haproxy/ssh_driver.py +++ b/octavia/amphorae/drivers/haproxy/ssh_driver.py @@ -36,7 +36,8 @@ VIP_ROUTE_TABLE = 'vip' CMD_DHCLIENT = "dhclient {0}" CMD_ADD_IP_ADDR = "ip addr add {0}/24 dev {1}" CMD_SHOW_IP_ADDR = "ip addr show {0}" -CMD_GREP_DOWN_LINKS = "ip link | grep DOWN -m 1 | awk '{print $2}'" +CMD_GREP_LINK_BY_MAC = ("ip link | grep {mac_address} -m 1 -B 1 " + "| awk 'NR==1{{print $2}}'") CMD_CREATE_VIP_ROUTE_TABLE = ( "su -c 'echo \"1 {0}\" >> /etc/iproute2/rt_tables'" ) @@ -184,7 +185,9 @@ class HaproxyManager(driver_base.AmphoraLoadBalancerDriver): # Connect to amphora self._connect(hostname=amp.lb_network_ip) - stdout, _ = self._execute_command(CMD_GREP_DOWN_LINKS) + mac = amphorae_network_config.get(amp.id).vrrp_port.mac_address + stdout, _ = self._execute_command( + CMD_GREP_LINK_BY_MAC.format(mac_address=mac)) iface = stdout[:-2] if not iface: self.client.close() @@ -195,9 +198,10 @@ class HaproxyManager(driver_base.AmphoraLoadBalancerDriver): iface, amphorae_network_config.get(amp.id)) self.client.close() - def post_network_plug(self, amphora): + def post_network_plug(self, amphora, port): self._connect(hostname=amphora.lb_network_ip) - stdout, _ = self._execute_command(CMD_GREP_DOWN_LINKS) + stdout, _ = self._execute_command( + CMD_GREP_LINK_BY_MAC.format(mac_address=port.mac_address)) iface = stdout[:-2] if not iface: self.client.close() diff --git a/octavia/common/constants.py b/octavia/common/constants.py index 929dd63816..8803e0ae7d 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -98,6 +98,7 @@ OBJECT = 'object' SERVER_PEM = 'server_pem' VIP_NETWORK = 'vip_network' AMPHORAE_NETWORK_CONFIG = 'amphorae_network_config' +ADDED_PORTS = 'added_ports' CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow' CREATE_AMPHORA_FOR_LB_FLOW = 'octavia-create-amp-for-lb-flow' diff --git a/octavia/controller/worker/flows/member_flows.py b/octavia/controller/worker/flows/member_flows.py index 0b61ec2160..d9f10f325b 100644 --- a/octavia/controller/worker/flows/member_flows.py +++ b/octavia/controller/worker/flows/member_flows.py @@ -34,9 +34,9 @@ class MemberFlows(object): requires=constants.LOADBALANCER, provides=constants.DELTAS)) create_member_flow.add(network_tasks.HandleNetworkDeltas( - requires=constants.DELTAS)) + requires=constants.DELTAS, provides=constants.ADDED_PORTS)) create_member_flow.add(amphora_driver_tasks.AmphoraePostNetworkPlug( - requires=(constants.LOADBALANCER, constants.DELTAS) + requires=(constants.LOADBALANCER, constants.ADDED_PORTS) )) create_member_flow.add(amphora_driver_tasks.ListenerUpdate( requires=(constants.LISTENER, constants.VIP))) diff --git a/octavia/controller/worker/tasks/amphora_driver_tasks.py b/octavia/controller/worker/tasks/amphora_driver_tasks.py index 5e9a5aa2c2..10f072cd2e 100644 --- a/octavia/controller/worker/tasks/amphora_driver_tasks.py +++ b/octavia/controller/worker/tasks/amphora_driver_tasks.py @@ -165,12 +165,16 @@ class AmphoraPostNetworkPlug(BaseAmphoraTask): class AmphoraePostNetworkPlug(BaseAmphoraTask): """Task to notify the amphorae post network plug.""" - def execute(self, loadbalancer, deltas): + def execute(self, loadbalancer, added_ports): """Execute post_network_plug routine.""" for amphora in loadbalancer.amphorae: - if amphora.id in deltas and deltas[amphora.id].add_nics: - self.amphora_driver.post_network_plug(amphora) - LOG.debug("Posted network plug for the compute instance") + if amphora.id in added_ports: + for port in added_ports[amphora.id]: + self.amphora_driver.post_network_plug(amphora, port) + LOG.debug( + "post_network_plug called on compute instance " + "{compute_id} for port {port_id}".format( + compute_id=amphora.compute_id, port_id=port.id)) def revert(self, result, loadbalancer, deltas, *args, **kwargs): """Handle a failed post network plug.""" diff --git a/octavia/controller/worker/tasks/network_tasks.py b/octavia/controller/worker/tasks/network_tasks.py index 5d529f667e..738e93b1ab 100644 --- a/octavia/controller/worker/tasks/network_tasks.py +++ b/octavia/controller/worker/tasks/network_tasks.py @@ -200,10 +200,18 @@ class HandleNetworkDeltas(BaseNetworkTask): def execute(self, deltas): """Handle network plugging based off deltas.""" + added_ports = {} for amp_id, delta in six.iteritems(deltas): + added_ports[amp_id] = [] for nic in delta.add_nics: - self.network_driver.plug_network(delta.compute_id, - nic.network_id) + interface = self.network_driver.plug_network(delta.compute_id, + nic.network_id) + port = self.network_driver.get_port(interface.port_id) + port.network = self.network_driver.get_network(port.network_id) + for fixed_ip in port.fixed_ips: + fixed_ip.subnet = self.network_driver.get_subnet( + fixed_ip.subnet_id) + added_ports[amp_id].append(port) for nic in delta.delete_nics: try: self.network_driver.unplug_network(delta.compute_id, @@ -213,6 +221,7 @@ class HandleNetworkDeltas(BaseNetworkTask): except Exception as e: LOG.error( _LE("Unable to unplug network - exception: %s"), e) + return added_ports def revert(self, result, deltas): """Handle a network plug or unplug failures.""" diff --git a/octavia/network/data_models.py b/octavia/network/data_models.py index 71dc8eeb48..033fc3b3fa 100644 --- a/octavia/network/data_models.py +++ b/octavia/network/data_models.py @@ -73,7 +73,8 @@ class Port(data_models.BaseDataModel): def __init__(self, id=None, name=None, device_id=None, device_owner=None, mac_address=None, network_id=None, status=None, - tenant_id=None, admin_state_up=None, fixed_ips=None): + tenant_id=None, admin_state_up=None, fixed_ips=None, + network=None): self.id = id self.name = name self.device_id = device_id @@ -84,6 +85,7 @@ class Port(data_models.BaseDataModel): self.tenant_id = tenant_id self.admin_state_up = admin_state_up self.fixed_ips = fixed_ips or [] + self.network = network def get_subnet_id(self, fixed_ip_address): for fixed_ip in self.fixed_ips: @@ -93,9 +95,10 @@ class Port(data_models.BaseDataModel): class FixedIP(data_models.BaseDataModel): - def __init__(self, subnet_id=None, ip_address=None): + def __init__(self, subnet_id=None, ip_address=None, subnet=None): self.subnet_id = subnet_id self.ip_address = ip_address + self.subnet = subnet class AmphoraNetworkConfig(data_models.BaseDataModel): diff --git a/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py b/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py index f4442e358b..76727fc0cd 100644 --- a/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py +++ b/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py @@ -474,9 +474,13 @@ class ServerTestCase(base.TestCase): @mock.patch('subprocess.check_output') def test_plug_network(self, mock_check_output, mock_ifaddress, mock_interfaces): + port_info = {'mac_address': '123'} + # No interface at all mock_interfaces.side_effect = [[]] - rv = self.app.post('/' + api_server.VERSION + "/plug/network") + rv = self.app.post('/' + api_server.VERSION + "/plug/network", + content_type='application/json', + data=json.dumps(port_info)) self.assertEqual(404, rv.status_code) self.assertEqual(dict(details="No suitable network interface found"), json.loads(rv.data.decode('utf-8'))) @@ -484,7 +488,9 @@ class ServerTestCase(base.TestCase): # No interface down mock_interfaces.side_effect = [['blah']] mock_ifaddress.side_effect = [[netifaces.AF_INET]] - rv = self.app.post('/' + api_server.VERSION + "/plug/network") + rv = self.app.post('/' + api_server.VERSION + "/plug/network", + content_type='application/json', + data=json.dumps(port_info)) self.assertEqual(404, rv.status_code) self.assertEqual(dict(details="No suitable network interface found"), json.loads(rv.data.decode('utf-8'))) @@ -492,10 +498,13 @@ class ServerTestCase(base.TestCase): # One Interface down, Happy Path mock_interfaces.side_effect = [['blah']] - mock_ifaddress.side_effect = [['bla']] + mock_ifaddress.side_effect = [[netifaces.AF_LINK], + {netifaces.AF_LINK: [{'addr': '123'}]}] m = mock.mock_open() with mock.patch('%s.open' % BUILTINS, m, create=True): - rv = self.app.post('/' + api_server.VERSION + "/plug/network") + rv = self.app.post('/' + api_server.VERSION + "/plug/network", + content_type='application/json', + data=json.dumps(port_info)) self.assertEqual(202, rv.status_code) m.assert_called_once_with( '/etc/network/interfaces.d/blah.cfg', 'w') @@ -509,13 +518,16 @@ class ServerTestCase(base.TestCase): # same as above but ifup fails mock_interfaces.side_effect = [['blah']] - mock_ifaddress.side_effect = [['bla']] + mock_ifaddress.side_effect = [[netifaces.AF_LINK], + {netifaces.AF_LINK: [{'addr': '123'}]}] mock_check_output.side_effect = [subprocess.CalledProcessError( 7, 'test', RANDOM_ERROR), subprocess.CalledProcessError( 7, 'test', RANDOM_ERROR)] m = mock.mock_open() with mock.patch('%s.open' % BUILTINS, m, create=True): - rv = self.app.post('/' + api_server.VERSION + "/plug/network") + rv = self.app.post('/' + api_server.VERSION + "/plug/network", + content_type='application/json', + data=json.dumps(port_info)) self.assertEqual(500, rv.status_code) self.assertEqual( {'details': RANDOM_ERROR, @@ -529,7 +541,9 @@ class ServerTestCase(base.TestCase): def test_plug_VIP(self, mock_pyroute2, mock_check_output, mock_ifaddress, mock_interfaces): - subnet_info = {'subnet_cidr': '10.0.0.0/24', 'gateway': '10.0.0.1'} + subnet_info = {'subnet_cidr': '10.0.0.0/24', + 'gateway': '10.0.0.1', + 'mac_address': '123'} # malformated ip rv = self.app.post('/' + api_server.VERSION + '/plug/vip/error', @@ -562,7 +576,8 @@ class ServerTestCase(base.TestCase): # One Interface down, Happy Path mock_interfaces.side_effect = [['blah']] - mock_ifaddress.side_effect = [['bla']] + mock_ifaddress.side_effect = [[netifaces.AF_LINK], + {netifaces.AF_LINK: [{'addr': '123'}]}] m = mock.mock_open() with mock.patch('%s.open' % BUILTINS, m, create=True): rv = self.app.post('/' + api_server.VERSION + @@ -585,7 +600,8 @@ class ServerTestCase(base.TestCase): ['ifup', 'blah:0'], stderr=-2) mock_interfaces.side_effect = [['blah']] - mock_ifaddress.side_effect = [['blah']] + mock_ifaddress.side_effect = [[netifaces.AF_LINK], + {netifaces.AF_LINK: [{'addr': '123'}]}] mock_check_output.side_effect = [ 'unplug1', subprocess.CalledProcessError( diff --git a/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver.py b/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver.py index 950f67dfee..368288792f 100644 --- a/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver.py +++ b/octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver.py @@ -21,6 +21,7 @@ import six from octavia.amphorae.drivers.haproxy import exceptions as exc from octavia.amphorae.drivers.haproxy import rest_api_driver as driver from octavia.db import models +from octavia.network import data_models as network_models from octavia.tests.unit import base as base from octavia.tests.unit.common.sample_configs import sample_configs @@ -29,7 +30,8 @@ FAKE_GATEWAY = '10.0.0.1' FAKE_IP = 'fake' FAKE_PEM_FILENAME = "file_name" FAKE_SUBNET_INFO = {'subnet_cidr': FAKE_CIDR, - 'gateway': FAKE_GATEWAY} + 'gateway': FAKE_GATEWAY, + 'mac_address': '123'} FAKE_UUID_1 = uuidutils.generate_uuid() @@ -48,6 +50,7 @@ class HaproxyAmphoraLoadBalancerDriverTest(base.TestCase): self.amp = self.sl.load_balancer.amphorae[0] self.sv = sample_configs.sample_vip_tuple() self.lb = self.sl.load_balancer + self.port = network_models.Port(mac_address='123') @mock.patch('octavia.common.tls_utils.cert_parser.get_host_names') def test_update(self, mock_cert): @@ -119,13 +122,15 @@ class HaproxyAmphoraLoadBalancerDriverTest(base.TestCase): amphorae_network_config = mock.MagicMock() amphorae_network_config.get().vip_subnet.cidr = FAKE_CIDR amphorae_network_config.get().vip_subnet.gateway_ip = FAKE_GATEWAY + amphorae_network_config.get().vrrp_port = self.port self.driver.post_vip_plug(self.lb, amphorae_network_config) self.driver.client.plug_vip.assert_called_once_with( self.amp, self.lb.vip.ip_address, FAKE_SUBNET_INFO) def test_post_network_plug(self): - self.driver.post_network_plug(self.amp) - self.driver.client.plug_network.assert_called_once_with(self.amp) + self.driver.post_network_plug(self.amp, self.port) + self.driver.client.plug_network.assert_called_once_with( + self.amp, dict(mac_address='123')) class AmphoraAPIClientTest(base.TestCase): @@ -135,6 +140,7 @@ class AmphoraAPIClientTest(base.TestCase): self.driver = driver.AmphoraAPIClient() self.base_url = "https://127.0.0.1:8443/0.5" self.amp = models.Amphora(lb_network_ip='127.0.0.1', compute_id='123') + self.port_info = dict(mac_address='123') @requests_mock.mock() def test_get_info(self, m): @@ -605,5 +611,5 @@ class AmphoraAPIClientTest(base.TestCase): m.post("{base}/plug/network".format( base=self.base_url) ) - self.driver.plug_network(self.amp) + self.driver.plug_network(self.amp, self.port_info) self.assertTrue(m.called) diff --git a/octavia/tests/unit/amphorae/drivers/haproxy/test_ssh_driver.py b/octavia/tests/unit/amphorae/drivers/haproxy/test_ssh_driver.py index 250a4560ab..24be5fa04a 100644 --- a/octavia/tests/unit/amphorae/drivers/haproxy/test_ssh_driver.py +++ b/octavia/tests/unit/amphorae/drivers/haproxy/test_ssh_driver.py @@ -65,6 +65,7 @@ class TestSshDriver(base.TestCase): self.driver.client.exec_command.return_value = ( mock.Mock(), mock.Mock(), mock.Mock()) self.driver.amp_config = mock.MagicMock() + self.port = network_models.Port(mac_address='123') def test_update(self): with mock.patch.object( @@ -232,10 +233,14 @@ class TestSshDriver(base.TestCase): lb_network_ip=MOCK_IP_ADDRESS)] vip = data_models.Vip(ip_address=MOCK_IP_ADDRESS) lb = data_models.LoadBalancer(amphorae=amps, vip=vip) - vip_network = network_models.Network(id=MOCK_NETWORK_ID) + amphorae_net_config = {amps[0].id: network_models.AmphoraNetworkConfig( + amphora=amps[0], + vrrp_port=self.port + )} exec_command.return_value = ('', '') - self.driver.post_vip_plug(lb, vip_network) - exec_command.assert_called_once_with(ssh_driver.CMD_GREP_DOWN_LINKS) + self.driver.post_vip_plug(lb, amphorae_net_config) + exec_command.assert_called_once_with( + ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123')) @mock.patch.object(ssh_driver.HaproxyManager, '_execute_command') def test_post_vip_plug(self, exec_command): @@ -251,12 +256,14 @@ class TestSshDriver(base.TestCase): amphorae_net_config = {amps[0].id: network_models.AmphoraNetworkConfig( amphora=amps[0], vip_subnet=vip_subnet, - vip_port=vip_port + vip_port=vip_port, + vrrp_port=self.port )} iface = 'eth1' exec_command.return_value = ('{0}: '.format(iface), '') self.driver.post_vip_plug(lb, amphorae_net_config) - grep_call = mock.call(ssh_driver.CMD_GREP_DOWN_LINKS) + grep_call = mock.call( + ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123')) dhclient_call = mock.call(ssh_driver.CMD_DHCLIENT.format(iface), run_as_root=True) add_ip_call = mock.call(ssh_driver.CMD_ADD_IP_ADDR.format( @@ -298,8 +305,9 @@ class TestSshDriver(base.TestCase): amp = data_models.Amphora(id=MOCK_AMP_ID, compute_id=MOCK_COMPUTE_ID, lb_network_ip=MOCK_IP_ADDRESS) exec_command.return_value = ('', '') - self.driver.post_network_plug(amp) - exec_command.assert_called_once_with(ssh_driver.CMD_GREP_DOWN_LINKS) + self.driver.post_network_plug(amp, self.port) + exec_command.assert_called_once_with( + ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123')) @mock.patch.object(ssh_driver.HaproxyManager, '_execute_command') def test_post_network_plug(self, exec_command): @@ -307,8 +315,9 @@ class TestSshDriver(base.TestCase): lb_network_ip=MOCK_IP_ADDRESS) iface = 'eth1' exec_command.return_value = ('{0}: '.format(iface), '') - self.driver.post_network_plug(amp) - grep_call = mock.call(ssh_driver.CMD_GREP_DOWN_LINKS) + self.driver.post_network_plug(amp, self.port) + grep_call = mock.call( + ssh_driver.CMD_GREP_LINK_BY_MAC.format(mac_address='123')) dhclient_call = mock.call(ssh_driver.CMD_DHCLIENT.format(iface), run_as_root=True) show_ip_call = mock.call(ssh_driver.CMD_SHOW_IP_ADDR.format(iface)) diff --git a/octavia/tests/unit/controller/worker/flows/test_member_flows.py b/octavia/tests/unit/controller/worker/flows/test_member_flows.py index 40f26660de..fb073d502d 100644 --- a/octavia/tests/unit/controller/worker/flows/test_member_flows.py +++ b/octavia/tests/unit/controller/worker/flows/test_member_flows.py @@ -37,7 +37,7 @@ class TestMemberFlows(base.TestCase): self.assertIn('vip', member_flow.requires) self.assertEqual(len(member_flow.requires), 3) - self.assertEqual(len(member_flow.provides), 1) + self.assertEqual(len(member_flow.provides), 2) def test_get_delete_member_flow(self): diff --git a/octavia/tests/unit/controller/worker/tasks/test_amphora_driver_tasks.py b/octavia/tests/unit/controller/worker/tasks/test_amphora_driver_tasks.py index d261e171bf..d1a703a48f 100644 --- a/octavia/tests/unit/controller/worker/tasks/test_amphora_driver_tasks.py +++ b/octavia/tests/unit/controller/worker/tasks/test_amphora_driver_tasks.py @@ -230,11 +230,12 @@ class TestAmphoraDriverTasks(base.TestCase): _LB_mock.amphorae = [_amphora_mock] amphora_post_network_plug_obj = (amphora_driver_tasks. AmphoraePostNetworkPlug()) - _deltas_mock = {_amphora_mock.id: mock.Mock()} + port_mock = mock.Mock() + _deltas_mock = {_amphora_mock.id: [port_mock]} amphora_post_network_plug_obj.execute(_LB_mock, _deltas_mock) (mock_driver.post_network_plug. - assert_called_once_with(_amphora_mock)) + assert_called_once_with(_amphora_mock, port_mock)) # Test revert amp = amphora_post_network_plug_obj.revert(None, _LB_mock, diff --git a/specs/version0.5/amphora-driver-interface.rst b/specs/version0.5/amphora-driver-interface.rst index 0ad779738a..083fb5a86b 100755 --- a/specs/version0.5/amphora-driver-interface.rst +++ b/specs/version0.5/amphora-driver-interface.rst @@ -82,11 +82,15 @@ Establish a base class to model the desire functionality: """ pass - def post_network_plug(self, amphora): + def post_network_plug(self, amphora, port): """OPTIONAL - called after adding a compute instance to a network. This will perform any necessary actions to allow for connectivity for that network on that instance. + + port is an instance of octavia.network.data_models.Port. It + contains information about the port, subnet, and network that + was just plugged. """ def post_vip_plug(self, load_balancer, amphorae_network_config):