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:
parent
0bc2612952
commit
3b23de32b8
@ -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',
|
||||||
|
@ -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):
|
||||||
|
@ -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'])
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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'
|
||||||
|
@ -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)))
|
||||||
|
@ -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."""
|
||||||
|
@ -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."""
|
||||||
|
@ -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):
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user