Subnet host routes support for plug_network
Subnets will sometimes be defined to have static routes that all fixed ips on that subnet should use, neutron calls them host routes in the API. This makes Octavia aware of them. Co-Authored-By: Michael Johnson <johnsomor@gmail.com> Change-Id: I37b79da5e4cf532a31780537702d6effa656de5b
This commit is contained in:
parent
8a2872f9a3
commit
d826fc2865
|
@ -16,6 +16,7 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
|
@ -47,9 +48,11 @@ template_port = j2_env.get_template(ETH_X_PORT_CONF)
|
|||
template_vip = j2_env.get_template(ETH_X_VIP_CONF)
|
||||
|
||||
|
||||
def plug_vip(vip, subnet_cidr, gateway, mac_address, vrrp_ip=None):
|
||||
def plug_vip(vip, subnet_cidr, gateway,
|
||||
mac_address, vrrp_ip=None, host_routes=None):
|
||||
# Validate vip and subnet_cidr, calculate broadcast address and netmask
|
||||
try:
|
||||
render_host_routes = []
|
||||
ip = ipaddress.ip_address(
|
||||
vip if six.text_type == type(vip) else six.u(vip))
|
||||
network = ipaddress.ip_network(
|
||||
|
@ -65,6 +68,14 @@ def plug_vip(vip, subnet_cidr, gateway, mac_address, vrrp_ip=None):
|
|||
vrrp_ip if six.text_type == type(vrrp_ip) else six.u(vrrp_ip)
|
||||
)
|
||||
vrrp_version = vrrp_ip_obj.version
|
||||
if host_routes:
|
||||
for hr in host_routes:
|
||||
network = ipaddress.ip_network(
|
||||
hr['destination'] if isinstance(
|
||||
hr['destination'], six.text_type) else
|
||||
six.u(hr['destination']))
|
||||
render_host_routes.append({'network': network,
|
||||
'gw': hr['nexthop']})
|
||||
except ValueError:
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="Invalid VIP")), 400)
|
||||
|
@ -127,6 +138,7 @@ def plug_vip(vip, subnet_cidr, gateway, mac_address, vrrp_ip=None):
|
|||
gateway=gateway,
|
||||
vrrp_ip=vrrp_ip,
|
||||
vrrp_ipv6=vrrp_version is 6,
|
||||
host_routes=render_host_routes,
|
||||
)
|
||||
text_file.write(text)
|
||||
|
||||
|
@ -156,6 +168,59 @@ def plug_vip(vip, subnet_cidr, gateway, mac_address, vrrp_ip=None):
|
|||
vip=vip, interface=primary_interface))), 202)
|
||||
|
||||
|
||||
def _generate_network_file_text(netns_interface, fixed_ips):
|
||||
text = ''
|
||||
if fixed_ips is None:
|
||||
text = template_port.render(interface=netns_interface)
|
||||
else:
|
||||
for index, fixed_ip in enumerate(fixed_ips, -1):
|
||||
if index == -1:
|
||||
netns_ip_interface = netns_interface
|
||||
else:
|
||||
netns_ip_interface = "{int}:{ip}".format(
|
||||
int=netns_interface, ip=index)
|
||||
try:
|
||||
ip_addr = fixed_ip['ip_address']
|
||||
cidr = fixed_ip['subnet_cidr']
|
||||
ip = ipaddress.ip_address(
|
||||
ip_addr if six.text_type == type(
|
||||
ip_addr) else six.u(ip_addr))
|
||||
network = ipaddress.ip_network(
|
||||
cidr if six.text_type == type(
|
||||
cidr) else six.u(cidr))
|
||||
broadcast = network.broadcast_address.exploded
|
||||
netmask = (network.prefixlen if ip.version is 6
|
||||
else network.netmask.exploded)
|
||||
host_routes = []
|
||||
for hr in fixed_ip.get('host_routes', []):
|
||||
network = ipaddress.ip_network(
|
||||
hr['destination'] if isinstance(
|
||||
hr['destination'], six.text_type) else
|
||||
six.u(hr['destination']))
|
||||
host_routes.append({'network': network,
|
||||
'gw': hr['nexthop']})
|
||||
except ValueError:
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="Invalid network IP")), 400)
|
||||
new_text = template_port.render(interface=netns_ip_interface,
|
||||
ipv6=ip.version is 6,
|
||||
ip_address=ip.exploded,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask,
|
||||
host_routes=host_routes)
|
||||
text = '\n'.join([text, new_text])
|
||||
return text
|
||||
|
||||
|
||||
def _check_ip_addresses(fixed_ips):
|
||||
if fixed_ips:
|
||||
for ip in fixed_ips:
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET, ip.get('ip_address'))
|
||||
except socket.error:
|
||||
socket.inet_pton(socket.AF_INET6, ip.get('ip_address'))
|
||||
|
||||
|
||||
def plug_network(mac_address, fixed_ips):
|
||||
|
||||
# Check if the interface is already in the network namespace
|
||||
|
@ -167,6 +232,13 @@ def plug_network(mac_address, fixed_ips):
|
|||
|
||||
# This is the interface as it was initially plugged into the
|
||||
# default network namespace, this will likely always be eth1
|
||||
|
||||
try:
|
||||
_check_ip_addresses(fixed_ips=fixed_ips)
|
||||
except socket.error:
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="Invalid network port")), 400)
|
||||
|
||||
default_netns_interface = _interface_by_mac(mac_address)
|
||||
|
||||
# We need to determine the interface name when inside the namespace
|
||||
|
@ -199,40 +271,8 @@ def plug_network(mac_address, fixed_ips):
|
|||
|
||||
with os.fdopen(os.open(interface_file_path, flags, mode),
|
||||
'w') as text_file:
|
||||
if fixed_ips is None:
|
||||
text = template_port.render(interface=netns_interface)
|
||||
text_file.write(text)
|
||||
else:
|
||||
ip_count = -1
|
||||
for fixed_ip in fixed_ips:
|
||||
if ip_count == -1:
|
||||
netns_ip_interface = netns_interface
|
||||
ip_count += 1
|
||||
else:
|
||||
netns_ip_interface = "{int}:{ip}".format(
|
||||
int=netns_interface, ip=ip_count)
|
||||
ip_count += 1
|
||||
try:
|
||||
ip_addr = fixed_ip['ip_address']
|
||||
cidr = fixed_ip['subnet_cidr']
|
||||
ip = ipaddress.ip_address(
|
||||
ip_addr if six.text_type == type(
|
||||
ip_addr) else six.u(ip_addr))
|
||||
network = ipaddress.ip_network(
|
||||
cidr if six.text_type == type(
|
||||
cidr) else six.u(cidr))
|
||||
broadcast = network.broadcast_address.exploded
|
||||
netmask = (network.prefixlen if ip.version is 6
|
||||
else network.netmask.exploded)
|
||||
except ValueError:
|
||||
return flask.make_response(flask.jsonify(dict(
|
||||
message="Invalid network IP")), 400)
|
||||
text = template_port.render(interface=netns_ip_interface,
|
||||
ipv6=ip.version is 6,
|
||||
ip_address=ip.exploded,
|
||||
broadcast=broadcast,
|
||||
netmask=netmask)
|
||||
text_file.write(text)
|
||||
text = _generate_network_file_text(netns_interface, fixed_ips)
|
||||
text_file.write(text)
|
||||
|
||||
# Update the list of interfaces to add to the namespace
|
||||
_update_plugged_interfaces_file(netns_interface, mac_address)
|
||||
|
|
|
@ -123,7 +123,8 @@ def plug_vip(vip):
|
|||
net_info['subnet_cidr'],
|
||||
net_info['gateway'],
|
||||
net_info['mac_address'],
|
||||
net_info.get('vrrp_ip'))
|
||||
net_info.get('vrrp_ip'),
|
||||
net_info.get('host_routes'))
|
||||
|
||||
|
||||
@app.route('/' + api_server.VERSION + '/plug/network', methods=['POST'])
|
||||
|
|
|
@ -20,6 +20,10 @@ iface {{ interface }} inet{{ '6' if ipv6 }} static
|
|||
address {{ ip_address }}
|
||||
broadcast {{ broadcast }}
|
||||
netmask {{ netmask }}
|
||||
{%- for hr in host_routes %}
|
||||
up route add -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
iface {{ interface }} inet dhcp
|
||||
auto {{ interface }}:0
|
||||
|
|
|
@ -23,6 +23,10 @@ address {{ vrrp_ip }}
|
|||
broadcast {{ broadcast }}
|
||||
netmask {{ netmask }}
|
||||
gateway {{ gateway }}
|
||||
{%- for hr in host_routes %}
|
||||
up route add -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
down route del -net {{ hr.network }} gw {{ hr.gw }} dev {{ interface }}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
iface {{ interface }} inet{{ '6' if vip_ipv6 }} {{ 'auto' if vip_ipv6 else 'dhcp' }}
|
||||
{%- endif %}
|
||||
|
|
|
@ -127,10 +127,14 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||
# compatibility with old amphora agent versions.
|
||||
|
||||
port = amphorae_network_config.get(amphora.id).vrrp_port
|
||||
host_routes = [{'nexthop': hr.nexthop,
|
||||
'destination': hr.destination}
|
||||
for hr in subnet.host_routes]
|
||||
net_info = {'subnet_cidr': subnet.cidr,
|
||||
'gateway': subnet.gateway_ip,
|
||||
'mac_address': port.mac_address,
|
||||
'vrrp_ip': amphora.vrrp_ip}
|
||||
'vrrp_ip': amphora.vrrp_ip,
|
||||
'host_routes': host_routes}
|
||||
try:
|
||||
self.client.plug_vip(amphora,
|
||||
load_balancer.vip.ip_address,
|
||||
|
@ -143,8 +147,12 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||
def post_network_plug(self, amphora, port):
|
||||
fixed_ips = []
|
||||
for fixed_ip in port.fixed_ips:
|
||||
host_routes = [{'nexthop': hr.nexthop,
|
||||
'destination': hr.destination}
|
||||
for hr in fixed_ip.subnet.host_routes]
|
||||
ip = {'ip_address': fixed_ip.ip_address,
|
||||
'subnet_cidr': fixed_ip.subnet.cidr}
|
||||
'subnet_cidr': fixed_ip.subnet.cidr,
|
||||
'host_routes': host_routes}
|
||||
fixed_ips.append(ip)
|
||||
port_info = {'mac_address': port.mac_address,
|
||||
'fixed_ips': fixed_ips}
|
||||
|
|
|
@ -59,7 +59,8 @@ class Network(data_models.BaseDataModel):
|
|||
class Subnet(data_models.BaseDataModel):
|
||||
|
||||
def __init__(self, id=None, name=None, network_id=None, project_id=None,
|
||||
gateway_ip=None, cidr=None, ip_version=None):
|
||||
gateway_ip=None, cidr=None, ip_version=None,
|
||||
host_routes=None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.network_id = network_id
|
||||
|
@ -67,6 +68,7 @@ class Subnet(data_models.BaseDataModel):
|
|||
self.gateway_ip = gateway_ip
|
||||
self.cidr = cidr
|
||||
self.ip_version = ip_version
|
||||
self.host_routes = host_routes
|
||||
|
||||
|
||||
class Port(data_models.BaseDataModel):
|
||||
|
@ -113,3 +115,10 @@ class AmphoraNetworkConfig(data_models.BaseDataModel):
|
|||
self.vrrp_port = vrrp_port
|
||||
self.ha_subnet = ha_subnet
|
||||
self.ha_port = ha_port
|
||||
|
||||
|
||||
class HostRoute(data_models.BaseDataModel):
|
||||
|
||||
def __init__(self, nexthop=None, destination=None):
|
||||
self.nexthop = nexthop
|
||||
self.destination = destination
|
||||
|
|
|
@ -18,12 +18,17 @@ from octavia.network import data_models as network_models
|
|||
|
||||
def convert_subnet_dict_to_model(subnet_dict):
|
||||
subnet = subnet_dict.get('subnet', subnet_dict)
|
||||
subnet_hrs = subnet.get('host_routes', [])
|
||||
host_routes = [network_models.HostRoute(nexthop=hr.get('nexthop'),
|
||||
destination=hr.get('destination'))
|
||||
for hr in subnet_hrs]
|
||||
return network_models.Subnet(id=subnet.get('id'), name=subnet.get('name'),
|
||||
network_id=subnet.get('network_id'),
|
||||
project_id=subnet.get('tenant_id'),
|
||||
gateway_ip=subnet.get('gateway_ip'),
|
||||
cidr=subnet.get('cidr'),
|
||||
ip_version=subnet.get('ip_version')
|
||||
ip_version=subnet.get('ip_version'),
|
||||
host_routes=host_routes
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -38,12 +38,12 @@ RANDOM_ERROR = 'random error'
|
|||
OK = dict(message='OK')
|
||||
|
||||
|
||||
class ServerTestCase(base.TestCase):
|
||||
class TestServerTestCase(base.TestCase):
|
||||
app = None
|
||||
|
||||
def setUp(self):
|
||||
self.app = server.app.test_client()
|
||||
super(ServerTestCase, self).setUp()
|
||||
super(TestServerTestCase, self).setUp()
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('os.makedirs')
|
||||
|
@ -487,16 +487,15 @@ class ServerTestCase(base.TestCase):
|
|||
handle.write.assert_any_call(six.b('TestT'))
|
||||
handle.write.assert_any_call(six.b('est'))
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'plug._netns_interface_exists')
|
||||
@mock.patch('netifaces.interfaces')
|
||||
@mock.patch('netifaces.ifaddresses')
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
@mock.patch('pyroute2.NetNS')
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_plug_network(self, mock_check_output, mock_netns,
|
||||
mock_pyroute2, mock_ifaddress, mock_interfaces,
|
||||
mock_int_exists):
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'plug._netns_interface_exists')
|
||||
def test_plug_network(self, mock_int_exists, mock_check_output, mock_netns,
|
||||
mock_pyroute2, mock_ifaddress, mock_interfaces):
|
||||
port_info = {'mac_address': '123'}
|
||||
test_int_num = random.randint(0, 9999)
|
||||
|
||||
|
@ -605,7 +604,7 @@ class ServerTestCase(base.TestCase):
|
|||
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n# Generated by Octavia agent\n'
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
'auto eth' + test_int_num +
|
||||
'\niface eth' + test_int_num + ' inet static\n' +
|
||||
'address 10.0.0.5\nbroadcast 10.0.0.255\n' +
|
||||
|
@ -645,7 +644,7 @@ class ServerTestCase(base.TestCase):
|
|||
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n# Generated by Octavia agent\n'
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
'auto eth' + test_int_num +
|
||||
'\niface eth' + test_int_num + ' inet6 static\n' +
|
||||
'address 2001:0db8:0000:0000:0000:0000:0000:0002\n'
|
||||
|
@ -696,6 +695,78 @@ class ServerTestCase(base.TestCase):
|
|||
'message': 'Error plugging network'},
|
||||
json.loads(rv.data.decode('utf-8')))
|
||||
|
||||
@mock.patch('netifaces.interfaces')
|
||||
@mock.patch('netifaces.ifaddresses')
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
@mock.patch('pyroute2.NetNS')
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_plug_network_host_routes(self, mock_check_output, mock_netns,
|
||||
mock_pyroute2, mock_ifaddress,
|
||||
mock_interfaces):
|
||||
SUBNET_CIDR = '192.0.2.0/24'
|
||||
BROADCAST = '192.0.2.255'
|
||||
NETMASK = '255.255.255.0'
|
||||
IP = '192.0.1.5'
|
||||
MAC = '123'
|
||||
DEST1 = '198.51.100.0/24'
|
||||
DEST2 = '203.0.113.0/24'
|
||||
NEXTHOP = '192.0.2.1'
|
||||
|
||||
netns_handle = mock_netns.return_value.__enter__.return_value
|
||||
netns_handle.get_links.return_value = [{
|
||||
'attrs': [['IFLA_IFNAME', consts.NETNS_PRIMARY_INTERFACE]]}]
|
||||
|
||||
port_info = {'mac_address': MAC, 'fixed_ips': [
|
||||
{'ip_address': IP, 'subnet_cidr': SUBNET_CIDR,
|
||||
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
|
||||
{'destination': DEST2, 'nexthop': NEXTHOP}]}]}
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
file_name = '/etc/netns/{0}/network/interfaces.d/{1}.cfg'.format(
|
||||
consts.AMPHORA_NAMESPACE, consts.NETNS_PRIMARY_INTERFACE)
|
||||
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
||||
with mock.patch('os.open') as mock_open, mock.patch.object(
|
||||
os, 'fdopen', m) as mock_fdopen:
|
||||
mock_open.return_value = 123
|
||||
rv = self.app.post('/' + api_server.VERSION + "/plug/network",
|
||||
content_type='application/json',
|
||||
data=json.dumps(port_info))
|
||||
self.assertEqual(202, rv.status_code)
|
||||
|
||||
mock_open.assert_any_call(file_name, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'w')
|
||||
|
||||
plug_inf_file = '/var/lib/octavia/plugged_interfaces'
|
||||
flags = os.O_RDWR | os.O_CREAT
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
mock_open.assert_any_call(plug_inf_file, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'r+')
|
||||
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
'auto ' + consts.NETNS_PRIMARY_INTERFACE +
|
||||
'\niface ' + consts.NETNS_PRIMARY_INTERFACE +
|
||||
' inet static\n' +
|
||||
'address ' + IP + '\nbroadcast ' + BROADCAST + '\n' +
|
||||
'netmask ' + NETMASK + '\n' +
|
||||
'up route add -net ' + DEST1 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
'down route del -net ' + DEST1 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
'up route add -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
'down route del -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
)
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', consts.NETNS_PRIMARY_INTERFACE], stderr=-2)
|
||||
|
||||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'plug._netns_interface_exists')
|
||||
@mock.patch('netifaces.interfaces')
|
||||
|
@ -755,6 +826,75 @@ class ServerTestCase(base.TestCase):
|
|||
self.assertEqual(dict(details="No suitable network interface found"),
|
||||
json.loads(rv.data.decode('utf-8')))
|
||||
|
||||
# Happy Path IPv4, with VRRP_IP and host route
|
||||
full_subnet_info = {
|
||||
'subnet_cidr': '203.0.113.0/24',
|
||||
'gateway': '203.0.113.1',
|
||||
'mac_address': '123',
|
||||
'vrrp_ip': '203.0.113.4',
|
||||
'host_routes': [{'destination': '203.0.114.0/24',
|
||||
'nexthop': '203.0.113.5'},
|
||||
{'destination': '203.0.115.0/24',
|
||||
'nexthop': '203.0.113.5'}]
|
||||
}
|
||||
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
file_name = ('/etc/netns/{netns}/network/interfaces.d/'
|
||||
'{netns_int}.cfg'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE,
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
||||
|
||||
with mock.patch('os.open') as mock_open, mock.patch.object(
|
||||
os, 'fdopen', m) as mock_fdopen:
|
||||
mock_open.return_value = 123
|
||||
rv = self.app.post('/' + api_server.VERSION +
|
||||
"/plug/vip/203.0.113.2",
|
||||
content_type='application/json',
|
||||
data=json.dumps(full_subnet_info))
|
||||
self.assertEqual(202, rv.status_code)
|
||||
mock_open.assert_any_call(file_name, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'w')
|
||||
|
||||
plug_inf_file = '/var/lib/octavia/plugged_interfaces'
|
||||
flags = os.O_RDWR | os.O_CREAT
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
mock_open.assert_any_call(plug_inf_file, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'r+')
|
||||
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n# Generated by Octavia agent\n'
|
||||
'auto {netns_int} {netns_int}:0\n'
|
||||
'iface {netns_int} inet static\n'
|
||||
'address 203.0.113.4\n'
|
||||
'broadcast 203.0.113.255\n'
|
||||
'netmask 255.255.255.0\n'
|
||||
'gateway 203.0.113.1\n'
|
||||
'up route add -net 203.0.114.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 203.0.114.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'up route add -net 203.0.115.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 203.0.115.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'\n'
|
||||
'iface {netns_int}:0 inet static\n'
|
||||
'address 203.0.113.2\n'
|
||||
'broadcast 203.0.113.255\n'
|
||||
'netmask 255.255.255.0'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', '{netns_int}:0'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE)], stderr=-2)
|
||||
|
||||
# One Interface down, Happy Path IPv4
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
|
@ -868,6 +1008,73 @@ class ServerTestCase(base.TestCase):
|
|||
self.assertEqual(dict(details="No suitable network interface found"),
|
||||
json.loads(rv.data.decode('utf-8')))
|
||||
|
||||
# Happy Path IPv6, with VRRP_IP and host route
|
||||
full_subnet_info = {
|
||||
'subnet_cidr': '2001:db8::/32',
|
||||
'gateway': '2001:db8::1',
|
||||
'mac_address': '123',
|
||||
'vrrp_ip': '2001:db8::4',
|
||||
'host_routes': [{'destination': '2001:db9::/32',
|
||||
'nexthop': '2001:db8::5'},
|
||||
{'destination': '2001:db9::/32',
|
||||
'nexthop': '2001:db8::5'}]
|
||||
}
|
||||
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
file_name = ('/etc/netns/{netns}/network/interfaces.d/'
|
||||
'{netns_int}.cfg'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE,
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
||||
|
||||
with mock.patch('os.open') as mock_open, mock.patch.object(
|
||||
os, 'fdopen', m) as mock_fdopen:
|
||||
mock_open.return_value = 123
|
||||
rv = self.app.post('/' + api_server.VERSION +
|
||||
"/plug/vip/2001:db8::2",
|
||||
content_type='application/json',
|
||||
data=json.dumps(full_subnet_info))
|
||||
self.assertEqual(202, rv.status_code)
|
||||
mock_open.assert_any_call(file_name, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'w')
|
||||
|
||||
plug_inf_file = '/var/lib/octavia/plugged_interfaces'
|
||||
flags = os.O_RDWR | os.O_CREAT
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
mock_open.assert_any_call(plug_inf_file, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'r+')
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n# Generated by Octavia agent\n'
|
||||
'auto {netns_int} {netns_int}:0\n'
|
||||
'iface {netns_int} inet6 static\n'
|
||||
'address 2001:db8::4\n'
|
||||
'broadcast 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff\n'
|
||||
'netmask 32\n'
|
||||
'gateway 2001:db8::1\n'
|
||||
'up route add -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'up route add -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'\n'
|
||||
'iface {netns_int}:0 inet6 static\n'
|
||||
'address 2001:0db8:0000:0000:0000:0000:0000:0002\n'
|
||||
'broadcast 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff\n'
|
||||
'netmask 32'.format(netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', '{netns_int}:0'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE)], stderr=-2)
|
||||
|
||||
# One Interface down, Happy Path IPv6
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
|
|
|
@ -499,8 +499,7 @@ class ServerTestCase(base.TestCase):
|
|||
@mock.patch('octavia.amphorae.backends.agent.api_server.'
|
||||
'plug._netns_interface_exists')
|
||||
def test_plug_network(self, mock_int_exists, mock_check_output, mock_netns,
|
||||
mock_pyroute2, mock_ifaddress,
|
||||
mock_interfaces):
|
||||
mock_pyroute2, mock_ifaddress, mock_interfaces):
|
||||
port_info = {'mac_address': '123'}
|
||||
test_int_num = random.randint(0, 9999)
|
||||
|
||||
|
@ -588,6 +587,78 @@ class ServerTestCase(base.TestCase):
|
|||
'message': 'Error plugging network'},
|
||||
json.loads(rv.data.decode('utf-8')))
|
||||
|
||||
@mock.patch('netifaces.interfaces')
|
||||
@mock.patch('netifaces.ifaddresses')
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
@mock.patch('pyroute2.NetNS')
|
||||
@mock.patch('subprocess.check_output')
|
||||
def test_plug_network_host_routes(self, mock_check_output, mock_netns,
|
||||
mock_pyroute2, mock_ifaddress,
|
||||
mock_interfaces):
|
||||
SUBNET_CIDR = '192.0.2.0/24'
|
||||
BROADCAST = '192.0.2.255'
|
||||
NETMASK = '255.255.255.0'
|
||||
IP = '192.0.1.5'
|
||||
MAC = '123'
|
||||
DEST1 = '198.51.100.0/24'
|
||||
DEST2 = '203.0.113.0/24'
|
||||
NEXTHOP = '192.0.2.1'
|
||||
|
||||
netns_handle = mock_netns.return_value.__enter__.return_value
|
||||
netns_handle.get_links.return_value = [{
|
||||
'attrs': [['IFLA_IFNAME', consts.NETNS_PRIMARY_INTERFACE]]}]
|
||||
|
||||
port_info = {'mac_address': MAC, 'fixed_ips': [
|
||||
{'ip_address': IP, 'subnet_cidr': SUBNET_CIDR,
|
||||
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
|
||||
{'destination': DEST2, 'nexthop': NEXTHOP}]}]}
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
file_name = '/etc/netns/{0}/network/interfaces.d/{1}.cfg'.format(
|
||||
consts.AMPHORA_NAMESPACE, consts.NETNS_PRIMARY_INTERFACE)
|
||||
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
||||
with mock.patch('os.open') as mock_open, mock.patch.object(
|
||||
os, 'fdopen', m) as mock_fdopen:
|
||||
mock_open.return_value = 123
|
||||
rv = self.app.post('/' + api_server.VERSION + "/plug/network",
|
||||
content_type='application/json',
|
||||
data=json.dumps(port_info))
|
||||
self.assertEqual(202, rv.status_code)
|
||||
|
||||
mock_open.assert_any_call(file_name, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'w')
|
||||
|
||||
plug_inf_file = '/var/lib/octavia/plugged_interfaces'
|
||||
flags = os.O_RDWR | os.O_CREAT
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
mock_open.assert_any_call(plug_inf_file, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'r+')
|
||||
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
'auto ' + consts.NETNS_PRIMARY_INTERFACE +
|
||||
'\niface ' + consts.NETNS_PRIMARY_INTERFACE +
|
||||
' inet static\n' +
|
||||
'address ' + IP + '\nbroadcast ' + BROADCAST + '\n' +
|
||||
'netmask ' + NETMASK + '\n' +
|
||||
'up route add -net ' + DEST1 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
'down route del -net ' + DEST1 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
'up route add -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
'down route del -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + consts.NETNS_PRIMARY_INTERFACE + '\n'
|
||||
)
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', consts.NETNS_PRIMARY_INTERFACE], stderr=-2)
|
||||
|
||||
@mock.patch('netifaces.interfaces')
|
||||
@mock.patch('netifaces.ifaddresses')
|
||||
@mock.patch('pyroute2.IPRoute')
|
||||
|
@ -635,6 +706,75 @@ class ServerTestCase(base.TestCase):
|
|||
self.assertEqual(dict(details="No suitable network interface found"),
|
||||
json.loads(rv.data.decode('utf-8')))
|
||||
|
||||
# Happy Path IPv4, with VRRP_IP and host route
|
||||
full_subnet_info = {
|
||||
'subnet_cidr': '203.0.113.0/24',
|
||||
'gateway': '203.0.113.1',
|
||||
'mac_address': '123',
|
||||
'vrrp_ip': '203.0.113.4',
|
||||
'host_routes': [{'destination': '203.0.114.0/24',
|
||||
'nexthop': '203.0.113.5'},
|
||||
{'destination': '203.0.115.0/24',
|
||||
'nexthop': '203.0.113.5'}]
|
||||
}
|
||||
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
file_name = ('/etc/netns/{netns}/network/interfaces.d/'
|
||||
'{netns_int}.cfg'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE,
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
||||
|
||||
with mock.patch('os.open') as mock_open, mock.patch.object(
|
||||
os, 'fdopen', m) as mock_fdopen:
|
||||
mock_open.return_value = 123
|
||||
rv = self.app.post('/' + api_server.VERSION +
|
||||
"/plug/vip/203.0.113.2",
|
||||
content_type='application/json',
|
||||
data=json.dumps(full_subnet_info))
|
||||
self.assertEqual(202, rv.status_code)
|
||||
mock_open.assert_any_call(file_name, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'w')
|
||||
|
||||
plug_inf_file = '/var/lib/octavia/plugged_interfaces'
|
||||
flags = os.O_RDWR | os.O_CREAT
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
mock_open.assert_any_call(plug_inf_file, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'r+')
|
||||
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n# Generated by Octavia agent\n'
|
||||
'auto {netns_int} {netns_int}:0\n'
|
||||
'iface {netns_int} inet static\n'
|
||||
'address 203.0.113.4\n'
|
||||
'broadcast 203.0.113.255\n'
|
||||
'netmask 255.255.255.0\n'
|
||||
'gateway 203.0.113.1\n'
|
||||
'up route add -net 203.0.114.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 203.0.114.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'up route add -net 203.0.115.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 203.0.115.0/24 gw 203.0.113.5 '
|
||||
'dev {netns_int}\n'
|
||||
'\n'
|
||||
'iface {netns_int}:0 inet static\n'
|
||||
'address 203.0.113.2\n'
|
||||
'broadcast 203.0.113.255\n'
|
||||
'netmask 255.255.255.0'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', '{netns_int}:0'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE)], stderr=-2)
|
||||
|
||||
# One Interface down, Happy Path IPv4
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
|
@ -748,6 +888,73 @@ class ServerTestCase(base.TestCase):
|
|||
self.assertEqual(dict(details="No suitable network interface found"),
|
||||
json.loads(rv.data.decode('utf-8')))
|
||||
|
||||
# Happy Path IPv6, with VRRP_IP and host route
|
||||
full_subnet_info = {
|
||||
'subnet_cidr': '2001:db8::/32',
|
||||
'gateway': '2001:db8::1',
|
||||
'mac_address': '123',
|
||||
'vrrp_ip': '2001:db8::4',
|
||||
'host_routes': [{'destination': '2001:db9::/32',
|
||||
'nexthop': '2001:db8::5'},
|
||||
{'destination': '2001:db9::/32',
|
||||
'nexthop': '2001:db8::5'}]
|
||||
}
|
||||
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
file_name = ('/etc/netns/{netns}/network/interfaces.d/'
|
||||
'{netns_int}.cfg'.format(
|
||||
netns=consts.AMPHORA_NAMESPACE,
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
m = self.useFixture(test_utils.OpenFixture(file_name)).mock_open
|
||||
|
||||
with mock.patch('os.open') as mock_open, mock.patch.object(
|
||||
os, 'fdopen', m) as mock_fdopen:
|
||||
mock_open.return_value = 123
|
||||
rv = self.app.post('/' + api_server.VERSION +
|
||||
"/plug/vip/2001:db8::2",
|
||||
content_type='application/json',
|
||||
data=json.dumps(full_subnet_info))
|
||||
self.assertEqual(202, rv.status_code)
|
||||
mock_open.assert_any_call(file_name, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'w')
|
||||
|
||||
plug_inf_file = '/var/lib/octavia/plugged_interfaces'
|
||||
flags = os.O_RDWR | os.O_CREAT
|
||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||
mock_open.assert_any_call(plug_inf_file, flags, mode)
|
||||
mock_fdopen.assert_any_call(123, 'r+')
|
||||
handle = m()
|
||||
handle.write.assert_any_call(
|
||||
'\n# Generated by Octavia agent\n'
|
||||
'auto {netns_int} {netns_int}:0\n'
|
||||
'iface {netns_int} inet6 static\n'
|
||||
'address 2001:db8::4\n'
|
||||
'broadcast 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff\n'
|
||||
'netmask 32\n'
|
||||
'gateway 2001:db8::1\n'
|
||||
'up route add -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'up route add -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'down route del -net 2001:db9::/32 gw 2001:db8::5 '
|
||||
'dev {netns_int}\n'
|
||||
'\n'
|
||||
'iface {netns_int}:0 inet6 static\n'
|
||||
'address 2001:0db8:0000:0000:0000:0000:0000:0002\n'
|
||||
'broadcast 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff\n'
|
||||
'netmask 32'.format(netns_int=consts.NETNS_PRIMARY_INTERFACE))
|
||||
mock_check_output.assert_called_with(
|
||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||
'ifup', '{netns_int}:0'.format(
|
||||
netns_int=consts.NETNS_PRIMARY_INTERFACE)], stderr=-2)
|
||||
|
||||
# One Interface down, Happy Path IPv6
|
||||
mock_interfaces.side_effect = [['blah']]
|
||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||
|
|
|
@ -132,3 +132,39 @@ class TestPlug(base.TestCase):
|
|||
|
||||
# Interface is not found in netns
|
||||
self.assertFalse(plug._netns_interface_exists('321'))
|
||||
|
||||
|
||||
class TestPlugNetwork(base.TestCase):
|
||||
|
||||
def test__generate_network_file_text_static_ip(self):
|
||||
netns_interface = 'eth1234'
|
||||
FIXED_IP = '192.0.2.2'
|
||||
BROADCAST = '192.0.2.255'
|
||||
SUBNET_CIDR = '192.0.2.0/24'
|
||||
NETMASK = '255.255.255.0'
|
||||
DEST1 = '198.51.100.0/24'
|
||||
DEST2 = '203.0.113.0/24'
|
||||
NEXTHOP = '192.0.2.1'
|
||||
fixed_ips = [{'ip_address': FIXED_IP,
|
||||
'subnet_cidr': SUBNET_CIDR,
|
||||
'host_routes': [
|
||||
{'destination': DEST1, 'nexthop': NEXTHOP},
|
||||
{'destination': DEST2, 'nexthop': NEXTHOP}
|
||||
]}]
|
||||
text = plug._generate_network_file_text(netns_interface, fixed_ips)
|
||||
expected_text = (
|
||||
'\n\n# Generated by Octavia agent\n'
|
||||
'auto ' + netns_interface + '\n'
|
||||
'iface ' + netns_interface + ' inet static\n'
|
||||
'address ' + FIXED_IP + '\n'
|
||||
'broadcast ' + BROADCAST + '\n'
|
||||
'netmask ' + NETMASK + '\n'
|
||||
'up route add -net ' + DEST1 + ' gw ' + NEXTHOP +
|
||||
' dev ' + netns_interface + '\n'
|
||||
'down route del -net ' + DEST1 + ' gw ' + NEXTHOP +
|
||||
' dev ' + netns_interface + '\n'
|
||||
'up route add -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + netns_interface + '\n'
|
||||
'down route del -net ' + DEST2 + ' gw ' + NEXTHOP +
|
||||
' dev ' + netns_interface + '\n')
|
||||
self.assertEqual(expected_text, text)
|
||||
|
|
|
@ -28,8 +28,8 @@ 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
|
||||
|
||||
FAKE_CIDR = '10.0.0.0/24'
|
||||
FAKE_GATEWAY = '10.0.0.1'
|
||||
FAKE_CIDR = '198.51.100.0/24'
|
||||
FAKE_GATEWAY = '192.51.100.1'
|
||||
FAKE_IP = 'fake'
|
||||
FAKE_PEM_FILENAME = "file_name"
|
||||
FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||
|
@ -41,6 +41,11 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
super(TestHaproxyAmphoraLoadBalancerDriverTest, self).setUp()
|
||||
|
||||
DEST1 = '198.51.100.0/24'
|
||||
DEST2 = '203.0.113.0/24'
|
||||
NEXTHOP = '192.0.2.1'
|
||||
|
||||
self.driver = driver.HaproxyAmphoraLoadBalancerDriver()
|
||||
|
||||
self.driver.cert_manager = mock.MagicMock()
|
||||
|
@ -54,15 +59,22 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
|||
self.sv = sample_configs.sample_vip_tuple()
|
||||
self.lb = self.sl.load_balancer
|
||||
self.fixed_ip = mock.MagicMock()
|
||||
self.fixed_ip.ip_address = '10.0.0.5'
|
||||
self.fixed_ip.subnet.cidr = '10.0.0.0/24'
|
||||
self.fixed_ip.ip_address = '198.51.100.5'
|
||||
self.fixed_ip.subnet.cidr = '198.51.100.0/24'
|
||||
self.port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
|
||||
fixed_ips=[self.fixed_ip])
|
||||
|
||||
self.host_routes = [network_models.HostRoute(destination=DEST1,
|
||||
nexthop=NEXTHOP),
|
||||
network_models.HostRoute(destination=DEST2,
|
||||
nexthop=NEXTHOP)]
|
||||
host_routes_data = [{'destination': DEST1, 'nexthop': NEXTHOP},
|
||||
{'destination': DEST2, 'nexthop': NEXTHOP}]
|
||||
self.subnet_info = {'subnet_cidr': FAKE_CIDR,
|
||||
'gateway': FAKE_GATEWAY,
|
||||
'mac_address': FAKE_MAC_ADDRESS,
|
||||
'vrrp_ip': self.amp.vrrp_ip}
|
||||
'vrrp_ip': self.amp.vrrp_ip,
|
||||
'host_routes': host_routes_data}
|
||||
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||
@mock.patch('octavia.common.tls_utils.cert_parser.get_host_names')
|
||||
|
@ -158,6 +170,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(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().vip_subnet.host_routes = self.host_routes
|
||||
amphorae_network_config.get().vrrp_port = self.port
|
||||
self.driver.post_vip_plug(self.amp, self.lb, amphorae_network_config)
|
||||
self.driver.client.plug_vip.assert_called_once_with(
|
||||
|
@ -176,8 +189,44 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
|||
self.driver.post_network_plug(self.amp, self.port)
|
||||
self.driver.client.plug_network.assert_called_once_with(
|
||||
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
|
||||
fixed_ips=[dict(ip_address='10.0.0.5',
|
||||
subnet_cidr='10.0.0.0/24')]))
|
||||
fixed_ips=[dict(ip_address='198.51.100.5',
|
||||
subnet_cidr='198.51.100.0/24',
|
||||
host_routes=[])]))
|
||||
|
||||
def test_post_network_plug_with_host_routes(self):
|
||||
SUBNET_ID = 'SUBNET_ID'
|
||||
FIXED_IP1 = '192.0.2.2'
|
||||
FIXED_IP2 = '192.0.2.3'
|
||||
SUBNET_CIDR = '192.0.2.0/24'
|
||||
DEST1 = '198.51.100.0/24'
|
||||
DEST2 = '203.0.113.0/24'
|
||||
NEXTHOP = '192.0.2.1'
|
||||
host_routes = [network_models.HostRoute(destination=DEST1,
|
||||
nexthop=NEXTHOP),
|
||||
network_models.HostRoute(destination=DEST2,
|
||||
nexthop=NEXTHOP)]
|
||||
subnet = network_models.Subnet(id=SUBNET_ID, cidr=SUBNET_CIDR,
|
||||
ip_version=4, host_routes=host_routes)
|
||||
fixed_ips = [
|
||||
network_models.FixedIP(subnet_id=subnet.id, ip_address=FIXED_IP1,
|
||||
subnet=subnet),
|
||||
network_models.FixedIP(subnet_id=subnet.id, ip_address=FIXED_IP2,
|
||||
subnet=subnet)
|
||||
]
|
||||
port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
|
||||
fixed_ips=fixed_ips)
|
||||
self.driver.post_network_plug(self.amp, port)
|
||||
expected_fixed_ips = [
|
||||
{'ip_address': FIXED_IP1, 'subnet_cidr': SUBNET_CIDR,
|
||||
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
|
||||
{'destination': DEST2, 'nexthop': NEXTHOP}]},
|
||||
{'ip_address': FIXED_IP2, 'subnet_cidr': SUBNET_CIDR,
|
||||
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
|
||||
{'destination': DEST2, 'nexthop': NEXTHOP}]}
|
||||
]
|
||||
self.driver.client.plug_network.assert_called_once_with(
|
||||
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
|
||||
fixed_ips=expected_fixed_ips))
|
||||
|
||||
def test_get_vrrp_interface(self):
|
||||
self.driver.get_vrrp_interface(self.amp)
|
||||
|
@ -745,7 +794,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
|||
@requests_mock.mock()
|
||||
def test_get_interface(self, m):
|
||||
interface = [{"interface": "eth1"}]
|
||||
ip_addr = '10.0.0.1'
|
||||
ip_addr = '192.51.100.1'
|
||||
m.get("{base}/interface/{ip_addr}".format(base=self.base_url,
|
||||
ip_addr=ip_addr),
|
||||
json=interface)
|
||||
|
|
Loading…
Reference in New Issue