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
* *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',

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)
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):

View File

@ -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'])

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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'

View File

@ -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)))

View File

@ -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."""

View File

@ -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."""

View File

@ -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):

View File

@ -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(

View File

@ -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)

View File

@ -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))

View File

@ -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):

View File

@ -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,

View File

@ -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):