Plug vip and networks by port mac address

Co-Authored-By: German Eichberger <german.eichberger@hp.com>

Change-Id: Ic6206a97ad67f6364d850e033760ee60d7161f6f
This commit is contained in:
Brandon Logan 2015-09-05 05:20:22 -05:00
parent 0bc2612952
commit 3b23de32b8
17 changed files with 158 additions and 74 deletions

View File

@ -1088,6 +1088,7 @@ Plug VIP
* *subnet_cidr*: The vip subnet in cidr notation * *subnet_cidr*: The vip subnet in cidr notation
* *gateway*: The vip subnet gateway address * *gateway*: The vip subnet gateway address
* *mac_address*: The mac address of the interface to plug
* **Success Response:** * **Success Response:**
@ -1137,7 +1138,8 @@ Plug VIP
JSON POST parameters: JSON POST parameters:
{ {
'subnet_cidr': '203.0.113.0/24', '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: JSON Response:
@ -1176,7 +1178,10 @@ Plug Network
* **Method:** POST * **Method:** POST
* **URL params:** none * **URL params:** none
* **Data params:** none * **Data params:**
* *mac_address*: The mac address of the interface to plug
* **Success Response:** * **Success Response:**
* Code: 202 * Code: 202
@ -1212,6 +1217,11 @@ Plug Network
POST URL: POST URL:
https://octavia-haproxy-img-00328.local/v0.1/plug/network/ https://octavia-haproxy-img-00328.local/v0.1/plug/network/
JSON POST parameters:
{
'mac_address': '78:31:c1:ce:0b:3c'
}
JSON Response: JSON Response:
{ {
'message': 'OK', 'message': 'OK',

View File

@ -38,7 +38,7 @@ template_port = j2_env.get_template(ETH_X_VIP_CONF)
template_vip = j2_env.get_template(ETH_PORT_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 # validate vip
try: try:
socket.inet_aton(vip) socket.inet_aton(vip)
@ -46,7 +46,7 @@ def plug_vip(vip, subnet_cidr, gateway):
return flask.make_response(flask.jsonify(dict( return flask.make_response(flask.jsonify(dict(
message="Invalid VIP")), 400) message="Invalid VIP")), 400)
interface = _interface_down() interface = _interface_by_mac(mac_address)
# assume for now only a fixed subnet size # assume for now only a fixed subnet size
sections = vip.split('.')[:3] sections = vip.split('.')[:3]
@ -110,8 +110,8 @@ def plug_vip(vip, subnet_cidr, gateway):
vip=vip, interface=interface))), 202) vip=vip, interface=interface))), 202)
def plug_network(): def plug_network(mac_address):
interface = _interface_down() interface = _interface_by_mac(mac_address)
# write interface file # write interface file
with open(util.get_network_interface_file(interface), 'w') as text_file: with open(util.get_network_interface_file(interface), 'w') as text_file:
@ -127,17 +127,15 @@ def plug_network():
interface=interface))), 202) interface=interface))), 202)
def _interface_down(): def _interface_by_mac(mac):
# Find the interface which is down for interface in netifaces.interfaces():
down = [interface for interface in netifaces.interfaces() if if netifaces.AF_LINK in netifaces.ifaddresses(interface):
netifaces.AF_INET not in netifaces.ifaddresses(interface)] for link in netifaces.ifaddresses(interface)[netifaces.AF_LINK]:
if len(down) != 1: if link.get('addr') == mac:
# There should only be ONE interface being plugged; if there is return interface
# none down or more than one we have a problem...
raise exceptions.HTTPException( raise exceptions.HTTPException(
response=flask.make_response(flask.jsonify(dict( response=flask.make_response(flask.jsonify(dict(
details="No suitable network interface found")), 404)) details="No suitable network interface found")), 404))
return down[0]
def _bring_if_up(params, what): def _bring_if_up(params, what):

View File

@ -115,20 +115,28 @@ def delete_certificate(listener_id, filename):
def plug_vip(vip): def plug_vip(vip):
# Catch any issues with the subnet info json # Catch any issues with the subnet info json
try: try:
subnet_info = flask.request.get_json() net_info = flask.request.get_json()
assert type(subnet_info) is dict assert type(net_info) is dict
assert 'subnet_cidr' in subnet_info assert 'subnet_cidr' in net_info
assert 'gateway' in subnet_info assert 'gateway' in net_info
assert 'mac_address' in net_info
except Exception: except Exception:
raise exceptions.BadRequest(description='Invalid subnet information') raise exceptions.BadRequest(description='Invalid subnet information')
return plug.plug_vip(vip, return plug.plug_vip(vip,
subnet_info['subnet_cidr'], net_info['subnet_cidr'],
subnet_info['gateway']) net_info['gateway'],
net_info['mac_address'])
@app.route('/' + api_server.VERSION + '/plug/network', methods=['POST']) @app.route('/' + api_server.VERSION + '/plug/network', methods=['POST'])
def plug_network(): 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']) @app.route('/' + api_server.VERSION + '/certificate', methods=['PUT'])

View File

@ -149,11 +149,13 @@ class AmphoraLoadBalancerDriver(object):
""" """
pass pass
def post_network_plug(self, amphora): def post_network_plug(self, amphora, port):
"""Called after amphora added to network """Called after amphora added to network
:param amphora: amphora object, needs id and network ip(s) :param amphora: amphora object, needs id and network ip(s)
:type amphora: object :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 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 network, there may be steps necessary on the amphora to allow it to

View File

@ -102,14 +102,22 @@ class HaproxyAmphoraLoadBalancerDriver(driver_base.AmphoraLoadBalancerDriver):
def post_vip_plug(self, load_balancer, amphorae_network_config): def post_vip_plug(self, load_balancer, amphorae_network_config):
for amp in load_balancer.amphorae: for amp in load_balancer.amphorae:
subnet = amphorae_network_config.get(amp.id).vip_subnet subnet = amphorae_network_config.get(amp.id).vip_subnet
subnet_info = {'subnet_cidr': subnet.cidr, # NOTE(blogan): using the vrrp port here because that is what the
'gateway': subnet.gateway_ip} # 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, self.client.plug_vip(amp,
load_balancer.vip.ip_address, load_balancer.vip.ip_address,
subnet_info) net_info)
def post_network_plug(self, amphora): def post_network_plug(self, amphora, port):
self.client.plug_network(amphora) port_info = {'mac_address': port.mac_address}
self.client.plug_network(amphora, port_info)
def _process_tls_certificates(self, listener): def _process_tls_certificates(self, listener):
"""Processes TLS data from the listener. """Processes TLS data from the listener.
@ -297,12 +305,13 @@ class AmphoraAPIClient(object):
listener_id=listener_id, filename=pem_filename)) listener_id=listener_id, filename=pem_filename))
return exc.check_exception(r) return exc.check_exception(r)
def plug_network(self, amp): def plug_network(self, amp, port):
r = self.post(amp, 'plug/network') r = self.post(amp, 'plug/network',
json=port)
return exc.check_exception(r) 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, r = self.post(amp,
'plug/vip/{vip}'.format(vip=vip), 'plug/vip/{vip}'.format(vip=vip),
json=subnet_info) json=net_info)
return exc.check_exception(r) return exc.check_exception(r)

View File

@ -36,7 +36,8 @@ VIP_ROUTE_TABLE = 'vip'
CMD_DHCLIENT = "dhclient {0}" CMD_DHCLIENT = "dhclient {0}"
CMD_ADD_IP_ADDR = "ip addr add {0}/24 dev {1}" CMD_ADD_IP_ADDR = "ip addr add {0}/24 dev {1}"
CMD_SHOW_IP_ADDR = "ip addr show {0}" 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 = ( CMD_CREATE_VIP_ROUTE_TABLE = (
"su -c 'echo \"1 {0}\" >> /etc/iproute2/rt_tables'" "su -c 'echo \"1 {0}\" >> /etc/iproute2/rt_tables'"
) )
@ -184,7 +185,9 @@ class HaproxyManager(driver_base.AmphoraLoadBalancerDriver):
# Connect to amphora # Connect to amphora
self._connect(hostname=amp.lb_network_ip) 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] iface = stdout[:-2]
if not iface: if not iface:
self.client.close() self.client.close()
@ -195,9 +198,10 @@ class HaproxyManager(driver_base.AmphoraLoadBalancerDriver):
iface, amphorae_network_config.get(amp.id)) iface, amphorae_network_config.get(amp.id))
self.client.close() self.client.close()
def post_network_plug(self, amphora): def post_network_plug(self, amphora, port):
self._connect(hostname=amphora.lb_network_ip) 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] iface = stdout[:-2]
if not iface: if not iface:
self.client.close() self.client.close()

View File

@ -98,6 +98,7 @@ OBJECT = 'object'
SERVER_PEM = 'server_pem' SERVER_PEM = 'server_pem'
VIP_NETWORK = 'vip_network' VIP_NETWORK = 'vip_network'
AMPHORAE_NETWORK_CONFIG = 'amphorae_network_config' AMPHORAE_NETWORK_CONFIG = 'amphorae_network_config'
ADDED_PORTS = 'added_ports'
CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow' CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow'
CREATE_AMPHORA_FOR_LB_FLOW = 'octavia-create-amp-for-lb-flow' CREATE_AMPHORA_FOR_LB_FLOW = 'octavia-create-amp-for-lb-flow'

View File

@ -34,9 +34,9 @@ class MemberFlows(object):
requires=constants.LOADBALANCER, requires=constants.LOADBALANCER,
provides=constants.DELTAS)) provides=constants.DELTAS))
create_member_flow.add(network_tasks.HandleNetworkDeltas( 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( 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( create_member_flow.add(amphora_driver_tasks.ListenerUpdate(
requires=(constants.LISTENER, constants.VIP))) requires=(constants.LISTENER, constants.VIP)))

View File

@ -165,12 +165,16 @@ class AmphoraPostNetworkPlug(BaseAmphoraTask):
class AmphoraePostNetworkPlug(BaseAmphoraTask): class AmphoraePostNetworkPlug(BaseAmphoraTask):
"""Task to notify the amphorae post network plug.""" """Task to notify the amphorae post network plug."""
def execute(self, loadbalancer, deltas): def execute(self, loadbalancer, added_ports):
"""Execute post_network_plug routine.""" """Execute post_network_plug routine."""
for amphora in loadbalancer.amphorae: for amphora in loadbalancer.amphorae:
if amphora.id in deltas and deltas[amphora.id].add_nics: if amphora.id in added_ports:
self.amphora_driver.post_network_plug(amphora) for port in added_ports[amphora.id]:
LOG.debug("Posted network plug for the compute instance") 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): def revert(self, result, loadbalancer, deltas, *args, **kwargs):
"""Handle a failed post network plug.""" """Handle a failed post network plug."""

View File

@ -200,10 +200,18 @@ class HandleNetworkDeltas(BaseNetworkTask):
def execute(self, deltas): def execute(self, deltas):
"""Handle network plugging based off deltas.""" """Handle network plugging based off deltas."""
added_ports = {}
for amp_id, delta in six.iteritems(deltas): for amp_id, delta in six.iteritems(deltas):
added_ports[amp_id] = []
for nic in delta.add_nics: for nic in delta.add_nics:
self.network_driver.plug_network(delta.compute_id, interface = self.network_driver.plug_network(delta.compute_id,
nic.network_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: for nic in delta.delete_nics:
try: try:
self.network_driver.unplug_network(delta.compute_id, self.network_driver.unplug_network(delta.compute_id,
@ -213,6 +221,7 @@ class HandleNetworkDeltas(BaseNetworkTask):
except Exception as e: except Exception as e:
LOG.error( LOG.error(
_LE("Unable to unplug network - exception: %s"), e) _LE("Unable to unplug network - exception: %s"), e)
return added_ports
def revert(self, result, deltas): def revert(self, result, deltas):
"""Handle a network plug or unplug failures.""" """Handle a network plug or unplug failures."""

View File

@ -73,7 +73,8 @@ class Port(data_models.BaseDataModel):
def __init__(self, id=None, name=None, device_id=None, device_owner=None, def __init__(self, id=None, name=None, device_id=None, device_owner=None,
mac_address=None, network_id=None, status=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.id = id
self.name = name self.name = name
self.device_id = device_id self.device_id = device_id
@ -84,6 +85,7 @@ class Port(data_models.BaseDataModel):
self.tenant_id = tenant_id self.tenant_id = tenant_id
self.admin_state_up = admin_state_up self.admin_state_up = admin_state_up
self.fixed_ips = fixed_ips or [] self.fixed_ips = fixed_ips or []
self.network = network
def get_subnet_id(self, fixed_ip_address): def get_subnet_id(self, fixed_ip_address):
for fixed_ip in self.fixed_ips: for fixed_ip in self.fixed_ips:
@ -93,9 +95,10 @@ class Port(data_models.BaseDataModel):
class FixedIP(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.subnet_id = subnet_id
self.ip_address = ip_address self.ip_address = ip_address
self.subnet = subnet
class AmphoraNetworkConfig(data_models.BaseDataModel): class AmphoraNetworkConfig(data_models.BaseDataModel):

View File

@ -474,9 +474,13 @@ class ServerTestCase(base.TestCase):
@mock.patch('subprocess.check_output') @mock.patch('subprocess.check_output')
def test_plug_network(self, mock_check_output, mock_ifaddress, def test_plug_network(self, mock_check_output, mock_ifaddress,
mock_interfaces): mock_interfaces):
port_info = {'mac_address': '123'}
# No interface at all # No interface at all
mock_interfaces.side_effect = [[]] 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(404, rv.status_code)
self.assertEqual(dict(details="No suitable network interface found"), self.assertEqual(dict(details="No suitable network interface found"),
json.loads(rv.data.decode('utf-8'))) json.loads(rv.data.decode('utf-8')))
@ -484,7 +488,9 @@ class ServerTestCase(base.TestCase):
# No interface down # No interface down
mock_interfaces.side_effect = [['blah']] mock_interfaces.side_effect = [['blah']]
mock_ifaddress.side_effect = [[netifaces.AF_INET]] 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(404, rv.status_code)
self.assertEqual(dict(details="No suitable network interface found"), self.assertEqual(dict(details="No suitable network interface found"),
json.loads(rv.data.decode('utf-8'))) json.loads(rv.data.decode('utf-8')))
@ -492,10 +498,13 @@ class ServerTestCase(base.TestCase):
# One Interface down, Happy Path # One Interface down, Happy Path
mock_interfaces.side_effect = [['blah']] 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() m = mock.mock_open()
with mock.patch('%s.open' % BUILTINS, m, create=True): 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) self.assertEqual(202, rv.status_code)
m.assert_called_once_with( m.assert_called_once_with(
'/etc/network/interfaces.d/blah.cfg', 'w') '/etc/network/interfaces.d/blah.cfg', 'w')
@ -509,13 +518,16 @@ class ServerTestCase(base.TestCase):
# same as above but ifup fails # same as above but ifup fails
mock_interfaces.side_effect = [['blah']] 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( mock_check_output.side_effect = [subprocess.CalledProcessError(
7, 'test', RANDOM_ERROR), subprocess.CalledProcessError( 7, 'test', RANDOM_ERROR), subprocess.CalledProcessError(
7, 'test', RANDOM_ERROR)] 7, 'test', RANDOM_ERROR)]
m = mock.mock_open() m = mock.mock_open()
with mock.patch('%s.open' % BUILTINS, m, create=True): 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(500, rv.status_code)
self.assertEqual( self.assertEqual(
{'details': RANDOM_ERROR, {'details': RANDOM_ERROR,
@ -529,7 +541,9 @@ class ServerTestCase(base.TestCase):
def test_plug_VIP(self, mock_pyroute2, mock_check_output, mock_ifaddress, def test_plug_VIP(self, mock_pyroute2, mock_check_output, mock_ifaddress,
mock_interfaces): 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 # malformated ip
rv = self.app.post('/' + api_server.VERSION + '/plug/vip/error', rv = self.app.post('/' + api_server.VERSION + '/plug/vip/error',
@ -562,7 +576,8 @@ class ServerTestCase(base.TestCase):
# One Interface down, Happy Path # One Interface down, Happy Path
mock_interfaces.side_effect = [['blah']] 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() m = mock.mock_open()
with mock.patch('%s.open' % BUILTINS, m, create=True): with mock.patch('%s.open' % BUILTINS, m, create=True):
rv = self.app.post('/' + api_server.VERSION + rv = self.app.post('/' + api_server.VERSION +
@ -585,7 +600,8 @@ class ServerTestCase(base.TestCase):
['ifup', 'blah:0'], stderr=-2) ['ifup', 'blah:0'], stderr=-2)
mock_interfaces.side_effect = [['blah']] 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 = [ mock_check_output.side_effect = [
'unplug1', 'unplug1',
subprocess.CalledProcessError( subprocess.CalledProcessError(

View File

@ -21,6 +21,7 @@ import six
from octavia.amphorae.drivers.haproxy import exceptions as exc from octavia.amphorae.drivers.haproxy import exceptions as exc
from octavia.amphorae.drivers.haproxy import rest_api_driver as driver from octavia.amphorae.drivers.haproxy import rest_api_driver as driver
from octavia.db import models 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 import base as base
from octavia.tests.unit.common.sample_configs import sample_configs from octavia.tests.unit.common.sample_configs import sample_configs
@ -29,7 +30,8 @@ FAKE_GATEWAY = '10.0.0.1'
FAKE_IP = 'fake' FAKE_IP = 'fake'
FAKE_PEM_FILENAME = "file_name" FAKE_PEM_FILENAME = "file_name"
FAKE_SUBNET_INFO = {'subnet_cidr': FAKE_CIDR, FAKE_SUBNET_INFO = {'subnet_cidr': FAKE_CIDR,
'gateway': FAKE_GATEWAY} 'gateway': FAKE_GATEWAY,
'mac_address': '123'}
FAKE_UUID_1 = uuidutils.generate_uuid() FAKE_UUID_1 = uuidutils.generate_uuid()
@ -48,6 +50,7 @@ class HaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.amp = self.sl.load_balancer.amphorae[0] self.amp = self.sl.load_balancer.amphorae[0]
self.sv = sample_configs.sample_vip_tuple() self.sv = sample_configs.sample_vip_tuple()
self.lb = self.sl.load_balancer 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') @mock.patch('octavia.common.tls_utils.cert_parser.get_host_names')
def test_update(self, mock_cert): def test_update(self, mock_cert):
@ -119,13 +122,15 @@ class HaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
amphorae_network_config = mock.MagicMock() amphorae_network_config = mock.MagicMock()
amphorae_network_config.get().vip_subnet.cidr = FAKE_CIDR amphorae_network_config.get().vip_subnet.cidr = FAKE_CIDR
amphorae_network_config.get().vip_subnet.gateway_ip = FAKE_GATEWAY 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.post_vip_plug(self.lb, amphorae_network_config)
self.driver.client.plug_vip.assert_called_once_with( self.driver.client.plug_vip.assert_called_once_with(
self.amp, self.lb.vip.ip_address, FAKE_SUBNET_INFO) self.amp, self.lb.vip.ip_address, FAKE_SUBNET_INFO)
def test_post_network_plug(self): def test_post_network_plug(self):
self.driver.post_network_plug(self.amp) self.driver.post_network_plug(self.amp, self.port)
self.driver.client.plug_network.assert_called_once_with(self.amp) self.driver.client.plug_network.assert_called_once_with(
self.amp, dict(mac_address='123'))
class AmphoraAPIClientTest(base.TestCase): class AmphoraAPIClientTest(base.TestCase):
@ -135,6 +140,7 @@ class AmphoraAPIClientTest(base.TestCase):
self.driver = driver.AmphoraAPIClient() self.driver = driver.AmphoraAPIClient()
self.base_url = "https://127.0.0.1:8443/0.5" 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.amp = models.Amphora(lb_network_ip='127.0.0.1', compute_id='123')
self.port_info = dict(mac_address='123')
@requests_mock.mock() @requests_mock.mock()
def test_get_info(self, m): def test_get_info(self, m):
@ -605,5 +611,5 @@ class AmphoraAPIClientTest(base.TestCase):
m.post("{base}/plug/network".format( m.post("{base}/plug/network".format(
base=self.base_url) base=self.base_url)
) )
self.driver.plug_network(self.amp) self.driver.plug_network(self.amp, self.port_info)
self.assertTrue(m.called) self.assertTrue(m.called)

View File

@ -65,6 +65,7 @@ class TestSshDriver(base.TestCase):
self.driver.client.exec_command.return_value = ( self.driver.client.exec_command.return_value = (
mock.Mock(), mock.Mock(), mock.Mock()) mock.Mock(), mock.Mock(), mock.Mock())
self.driver.amp_config = mock.MagicMock() self.driver.amp_config = mock.MagicMock()
self.port = network_models.Port(mac_address='123')
def test_update(self): def test_update(self):
with mock.patch.object( with mock.patch.object(
@ -232,10 +233,14 @@ class TestSshDriver(base.TestCase):
lb_network_ip=MOCK_IP_ADDRESS)] lb_network_ip=MOCK_IP_ADDRESS)]
vip = data_models.Vip(ip_address=MOCK_IP_ADDRESS) vip = data_models.Vip(ip_address=MOCK_IP_ADDRESS)
lb = data_models.LoadBalancer(amphorae=amps, vip=vip) 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 = ('', '') exec_command.return_value = ('', '')
self.driver.post_vip_plug(lb, vip_network) self.driver.post_vip_plug(lb, amphorae_net_config)
exec_command.assert_called_once_with(ssh_driver.CMD_GREP_DOWN_LINKS) 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') @mock.patch.object(ssh_driver.HaproxyManager, '_execute_command')
def test_post_vip_plug(self, exec_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( amphorae_net_config = {amps[0].id: network_models.AmphoraNetworkConfig(
amphora=amps[0], amphora=amps[0],
vip_subnet=vip_subnet, vip_subnet=vip_subnet,
vip_port=vip_port vip_port=vip_port,
vrrp_port=self.port
)} )}
iface = 'eth1' iface = 'eth1'
exec_command.return_value = ('{0}: '.format(iface), '') exec_command.return_value = ('{0}: '.format(iface), '')
self.driver.post_vip_plug(lb, amphorae_net_config) 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), dhclient_call = mock.call(ssh_driver.CMD_DHCLIENT.format(iface),
run_as_root=True) run_as_root=True)
add_ip_call = mock.call(ssh_driver.CMD_ADD_IP_ADDR.format( 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, amp = data_models.Amphora(id=MOCK_AMP_ID, compute_id=MOCK_COMPUTE_ID,
lb_network_ip=MOCK_IP_ADDRESS) lb_network_ip=MOCK_IP_ADDRESS)
exec_command.return_value = ('', '') exec_command.return_value = ('', '')
self.driver.post_network_plug(amp) self.driver.post_network_plug(amp, self.port)
exec_command.assert_called_once_with(ssh_driver.CMD_GREP_DOWN_LINKS) 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') @mock.patch.object(ssh_driver.HaproxyManager, '_execute_command')
def test_post_network_plug(self, exec_command): def test_post_network_plug(self, exec_command):
@ -307,8 +315,9 @@ class TestSshDriver(base.TestCase):
lb_network_ip=MOCK_IP_ADDRESS) lb_network_ip=MOCK_IP_ADDRESS)
iface = 'eth1' iface = 'eth1'
exec_command.return_value = ('{0}: '.format(iface), '') exec_command.return_value = ('{0}: '.format(iface), '')
self.driver.post_network_plug(amp) self.driver.post_network_plug(amp, self.port)
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), dhclient_call = mock.call(ssh_driver.CMD_DHCLIENT.format(iface),
run_as_root=True) run_as_root=True)
show_ip_call = mock.call(ssh_driver.CMD_SHOW_IP_ADDR.format(iface)) show_ip_call = mock.call(ssh_driver.CMD_SHOW_IP_ADDR.format(iface))

View File

@ -37,7 +37,7 @@ class TestMemberFlows(base.TestCase):
self.assertIn('vip', member_flow.requires) self.assertIn('vip', member_flow.requires)
self.assertEqual(len(member_flow.requires), 3) 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): def test_get_delete_member_flow(self):

View File

@ -230,11 +230,12 @@ class TestAmphoraDriverTasks(base.TestCase):
_LB_mock.amphorae = [_amphora_mock] _LB_mock.amphorae = [_amphora_mock]
amphora_post_network_plug_obj = (amphora_driver_tasks. amphora_post_network_plug_obj = (amphora_driver_tasks.
AmphoraePostNetworkPlug()) 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) amphora_post_network_plug_obj.execute(_LB_mock, _deltas_mock)
(mock_driver.post_network_plug. (mock_driver.post_network_plug.
assert_called_once_with(_amphora_mock)) assert_called_once_with(_amphora_mock, port_mock))
# Test revert # Test revert
amp = amphora_post_network_plug_obj.revert(None, _LB_mock, amp = amphora_post_network_plug_obj.revert(None, _LB_mock,

View File

@ -82,11 +82,15 @@ Establish a base class to model the desire functionality:
""" """
pass pass
def post_network_plug(self, amphora): def post_network_plug(self, amphora, port):
"""OPTIONAL - called after adding a compute instance to a network. """OPTIONAL - called after adding a compute instance to a network.
This will perform any necessary actions to allow for connectivity This will perform any necessary actions to allow for connectivity
for that network on that instance. 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): def post_vip_plug(self, load_balancer, amphorae_network_config):