Fixes Octavia handling of subnets without DHCP
Currently Octavia assumes that DHCP service is available on the VIP and member subnets. This is not the case at all operators. This patch makes Octavia use the IP information provided when the ports are created, if available. If the IP information is not available on the ports it will fall back to relying on DHCP. Change-Id: I08a93d4318bbce48128019376320782d1a334369 Closes-Bug: #1607900
This commit is contained in:
parent
bb9612478b
commit
53ac6823fa
@ -1,4 +1,5 @@
|
|||||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
# Copyright 2016 Rackspace
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -20,9 +21,12 @@ import stat
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
import ipaddress
|
||||||
import jinja2
|
import jinja2
|
||||||
import netifaces
|
import netifaces
|
||||||
|
from oslo_config import cfg
|
||||||
import pyroute2
|
import pyroute2
|
||||||
|
import six
|
||||||
from werkzeug import exceptions
|
from werkzeug import exceptions
|
||||||
|
|
||||||
from octavia.amphorae.backends.agent.api_server import util
|
from octavia.amphorae.backends.agent.api_server import util
|
||||||
@ -30,6 +34,9 @@ from octavia.common import constants as consts
|
|||||||
from octavia.i18n import _LE, _LI
|
from octavia.i18n import _LE, _LI
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.import_group('amphora_agent', 'octavia.common.config')
|
||||||
|
|
||||||
ETH_PORT_CONF = 'plug_vip_ethX.conf.j2'
|
ETH_PORT_CONF = 'plug_vip_ethX.conf.j2'
|
||||||
|
|
||||||
ETH_X_VIP_CONF = 'plug_port_ethX.conf.j2'
|
ETH_X_VIP_CONF = 'plug_port_ethX.conf.j2'
|
||||||
@ -42,7 +49,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, mac_address):
|
def plug_vip(vip, subnet_cidr, gateway, mac_address, vrrp_ip):
|
||||||
# validate vip
|
# validate vip
|
||||||
try:
|
try:
|
||||||
socket.inet_aton(vip)
|
socket.inet_aton(vip)
|
||||||
@ -72,14 +79,24 @@ def plug_vip(vip, subnet_cidr, gateway, mac_address):
|
|||||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||||
# mode 00644
|
# mode 00644
|
||||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||||
with os.fdopen(os.open(name, flags, mode), 'w') as file:
|
with os.fdopen(os.open(name, flags, mode), 'w') as int_file:
|
||||||
file.write('auto lo\n')
|
int_file.write('auto lo\n')
|
||||||
file.write('iface lo inet loopback\n')
|
int_file.write('iface lo inet loopback\n')
|
||||||
file.write('source /etc/netns/{}/network/interfaces.d/*.cfg\n'.format(
|
if not CONF.amphora_agent.agent_server_network_file:
|
||||||
|
int_file.write('source /etc/netns/{}/network/'
|
||||||
|
'interfaces.d/*.cfg\n'.format(
|
||||||
consts.AMPHORA_NAMESPACE))
|
consts.AMPHORA_NAMESPACE))
|
||||||
|
|
||||||
# write interface file
|
# write interface file
|
||||||
|
|
||||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||||
|
|
||||||
|
# If we are using a consolidated interfaces file, just append
|
||||||
|
# otherwise clear the per interface file as we are rewriting it
|
||||||
|
# TODO(johnsom): We need a way to clean out old interfaces records
|
||||||
|
if CONF.amphora_agent.agent_server_network_file:
|
||||||
|
flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND
|
||||||
|
else:
|
||||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||||
|
|
||||||
with os.fdopen(os.open(interface_file_path, flags, mode),
|
with os.fdopen(os.open(interface_file_path, flags, mode),
|
||||||
@ -89,7 +106,9 @@ def plug_vip(vip, subnet_cidr, gateway, mac_address):
|
|||||||
vip=vip,
|
vip=vip,
|
||||||
broadcast=broadcast,
|
broadcast=broadcast,
|
||||||
# assume for now only a fixed subnet size
|
# assume for now only a fixed subnet size
|
||||||
netmask='255.255.255.0')
|
netmask='255.255.255.0',
|
||||||
|
gateway=gateway,
|
||||||
|
vrrp_ip=vrrp_ip)
|
||||||
text_file.write(text)
|
text_file.write(text)
|
||||||
|
|
||||||
# Update the list of interfaces to add to the namespace
|
# Update the list of interfaces to add to the namespace
|
||||||
@ -117,7 +136,7 @@ def plug_vip(vip, subnet_cidr, gateway, mac_address):
|
|||||||
vip=vip, interface=interface))), 202)
|
vip=vip, interface=interface))), 202)
|
||||||
|
|
||||||
|
|
||||||
def plug_network(mac_address):
|
def plug_network(mac_address, fixed_ips):
|
||||||
# This is the interface as it was initially plugged into the
|
# This is the interface as it was initially plugged into the
|
||||||
# default network namespace, this will likely always be eth1
|
# default network namespace, this will likely always be eth1
|
||||||
default_netns_interface = _interface_by_mac(mac_address)
|
default_netns_interface = _interface_by_mac(mac_address)
|
||||||
@ -138,14 +157,54 @@ def plug_network(mac_address):
|
|||||||
interface_file_path = util.get_network_interface_file(netns_interface)
|
interface_file_path = util.get_network_interface_file(netns_interface)
|
||||||
|
|
||||||
# write interface file
|
# write interface file
|
||||||
|
|
||||||
|
# If we are using a consolidated interfaces file, just append
|
||||||
|
# otherwise clear the per interface file as we are rewriting it
|
||||||
|
# TODO(johnsom): We need a way to clean out old interfaces records
|
||||||
|
if CONF.amphora_agent.agent_server_network_file:
|
||||||
|
flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND
|
||||||
|
else:
|
||||||
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||||
|
|
||||||
# mode 00644
|
# mode 00644
|
||||||
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
||||||
|
|
||||||
with os.fdopen(os.open(interface_file_path, flags, mode),
|
with os.fdopen(os.open(interface_file_path, flags, mode),
|
||||||
'w') as text_file:
|
'w') as text_file:
|
||||||
|
if fixed_ips is None:
|
||||||
text = template_port.render(interface=netns_interface)
|
text = template_port.render(interface=netns_interface)
|
||||||
text_file.write(text)
|
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)
|
||||||
|
|
||||||
# Update the list of interfaces to add to the namespace
|
# Update the list of interfaces to add to the namespace
|
||||||
_update_plugged_interfaces_file(netns_interface, mac_address)
|
_update_plugged_interfaces_file(netns_interface, mac_address)
|
||||||
|
@ -122,7 +122,8 @@ def plug_vip(vip):
|
|||||||
return plug.plug_vip(vip,
|
return plug.plug_vip(vip,
|
||||||
net_info['subnet_cidr'],
|
net_info['subnet_cidr'],
|
||||||
net_info['gateway'],
|
net_info['gateway'],
|
||||||
net_info['mac_address'])
|
net_info['mac_address'],
|
||||||
|
net_info.get('vrrp_ip'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/' + api_server.VERSION + '/plug/network', methods=['POST'])
|
@app.route('/' + api_server.VERSION + '/plug/network', methods=['POST'])
|
||||||
@ -133,7 +134,8 @@ def plug_network():
|
|||||||
assert 'mac_address' in port_info
|
assert 'mac_address' in port_info
|
||||||
except Exception:
|
except Exception:
|
||||||
raise exceptions.BadRequest(description='Invalid port information')
|
raise exceptions.BadRequest(description='Invalid port information')
|
||||||
return plug.plug_network(port_info['mac_address'])
|
return plug.plug_network(port_info['mac_address'],
|
||||||
|
port_info.get('fixed_ips'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/' + api_server.VERSION + '/certificate', methods=['PUT'])
|
@app.route('/' + api_server.VERSION + '/certificate', methods=['PUT'])
|
||||||
|
@ -14,5 +14,15 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
#}
|
#}
|
||||||
# Generated by Octavia agent
|
# Generated by Octavia agent
|
||||||
auto {{ interface }} {{ interface }}:0
|
auto {{ interface }}
|
||||||
|
{%- if ip_address %}
|
||||||
|
iface {{ interface }} inet{{ '6' if ipv6 }} static
|
||||||
|
address {{ ip_address }}
|
||||||
|
broadcast {{ broadcast }}
|
||||||
|
netmask {{ netmask }}
|
||||||
|
{%- else %}
|
||||||
iface {{ interface }} inet dhcp
|
iface {{ interface }} inet dhcp
|
||||||
|
auto {{ interface }}:0
|
||||||
|
iface {{ interface }}:0 inet6 auto
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
# Copyright 2016 Rackspace
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -15,7 +16,17 @@
|
|||||||
#}
|
#}
|
||||||
# Generated by Octavia agent
|
# Generated by Octavia agent
|
||||||
auto {{ interface }} {{ interface }}:0
|
auto {{ interface }} {{ interface }}:0
|
||||||
|
|
||||||
|
{%- if vrrp_ip %}
|
||||||
|
iface {{ interface }} inet static
|
||||||
|
address {{ vrrp_ip }}
|
||||||
|
broadcast {{ broadcast }}
|
||||||
|
netmask {{ netmask }}
|
||||||
|
gateway {{ gateway }}
|
||||||
|
{%- else %}
|
||||||
iface {{ interface }} inet dhcp
|
iface {{ interface }} inet dhcp
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
iface {{ interface }}:0 inet static
|
iface {{ interface }}:0 inet static
|
||||||
address {{ vip }}
|
address {{ vip }}
|
||||||
broadcast {{ broadcast }}
|
broadcast {{ broadcast }}
|
||||||
|
@ -123,16 +123,26 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||||||
# tight coupling between the network driver and amphora
|
# tight coupling between the network driver and amphora
|
||||||
# driver. We will need to revisit this to try and remove
|
# driver. We will need to revisit this to try and remove
|
||||||
# this tight coupling.
|
# this tight coupling.
|
||||||
|
# NOTE (johnsom): I am loading the vrrp_ip into the
|
||||||
|
# net_info structure here so that I don't break
|
||||||
|
# compatibility with old amphora agent versions.
|
||||||
port = amphorae_network_config.get(amp.id).vrrp_port
|
port = amphorae_network_config.get(amp.id).vrrp_port
|
||||||
net_info = {'subnet_cidr': subnet.cidr,
|
net_info = {'subnet_cidr': subnet.cidr,
|
||||||
'gateway': subnet.gateway_ip,
|
'gateway': subnet.gateway_ip,
|
||||||
'mac_address': port.mac_address}
|
'mac_address': port.mac_address,
|
||||||
|
'vrrp_ip': amp.vrrp_ip}
|
||||||
self.client.plug_vip(amp,
|
self.client.plug_vip(amp,
|
||||||
load_balancer.vip.ip_address,
|
load_balancer.vip.ip_address,
|
||||||
net_info)
|
net_info)
|
||||||
|
|
||||||
def post_network_plug(self, amphora, port):
|
def post_network_plug(self, amphora, port):
|
||||||
port_info = {'mac_address': port.mac_address}
|
fixed_ips = []
|
||||||
|
for fixed_ip in port.fixed_ips:
|
||||||
|
ip = {'ip_address': fixed_ip.ip_address,
|
||||||
|
'subnet_cidr': fixed_ip.subnet.cidr}
|
||||||
|
fixed_ips.append(ip)
|
||||||
|
port_info = {'mac_address': port.mac_address,
|
||||||
|
'fixed_ips': fixed_ips}
|
||||||
self.client.plug_network(amphora, port_info)
|
self.client.plug_network(amphora, port_info)
|
||||||
|
|
||||||
def get_vrrp_interface(self, amphora):
|
def get_vrrp_interface(self, amphora):
|
||||||
|
@ -552,13 +552,118 @@ class ServerTestCase(base.TestCase):
|
|||||||
handle = m()
|
handle = m()
|
||||||
handle.write.assert_any_call(
|
handle.write.assert_any_call(
|
||||||
'\n# Generated by Octavia agent\n'
|
'\n# Generated by Octavia agent\n'
|
||||||
'auto eth' + test_int_num + ' eth' + test_int_num +
|
'auto eth' + test_int_num +
|
||||||
':0\niface eth' + test_int_num + ' inet dhcp')
|
'\niface eth' + test_int_num + ' inet dhcp\n'
|
||||||
|
'auto eth' + test_int_num + ':0\n'
|
||||||
|
'iface eth' + test_int_num + ':0 inet6 auto\n')
|
||||||
mock_check_output.assert_called_with(
|
mock_check_output.assert_called_with(
|
||||||
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||||
'ifup', 'eth' + test_int_num], stderr=-2)
|
'ifup', 'eth' + test_int_num], stderr=-2)
|
||||||
|
|
||||||
|
# fixed IPs happy path
|
||||||
|
port_info = {'mac_address': '123', 'fixed_ips': [
|
||||||
|
{'ip_address': '10.0.0.5', 'subnet_cidr': '10.0.0.0/24'}]}
|
||||||
|
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/eth{1}.cfg'.format(
|
||||||
|
consts.AMPHORA_NAMESPACE, test_int_num)
|
||||||
|
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# 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' +
|
||||||
|
'netmask 255.255.255.0\n')
|
||||||
|
mock_check_output.assert_called_with(
|
||||||
|
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||||
|
'ifup', 'eth' + test_int_num], stderr=-2)
|
||||||
|
|
||||||
|
# fixed IPs happy path IPv6
|
||||||
|
port_info = {'mac_address': '123', 'fixed_ips': [
|
||||||
|
{'ip_address': '2001:db8::2', 'subnet_cidr': '2001:db8::/32'}]}
|
||||||
|
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/eth{1}.cfg'.format(
|
||||||
|
consts.AMPHORA_NAMESPACE, test_int_num)
|
||||||
|
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# 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'
|
||||||
|
'broadcast 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff\n' +
|
||||||
|
'netmask 32\n')
|
||||||
|
mock_check_output.assert_called_with(
|
||||||
|
['ip', 'netns', 'exec', consts.AMPHORA_NAMESPACE,
|
||||||
|
'ifup', 'eth' + test_int_num], stderr=-2)
|
||||||
|
|
||||||
|
# fixed IPs, bogus IP
|
||||||
|
port_info = {'mac_address': '123', 'fixed_ips': [
|
||||||
|
{'ip_address': '10005', 'subnet_cidr': '10.0.0.0/24'}]}
|
||||||
|
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/eth{1}.cfg'.format(
|
||||||
|
consts.AMPHORA_NAMESPACE, test_int_num)
|
||||||
|
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(400, rv.status_code)
|
||||||
|
|
||||||
# same as above but ifup fails
|
# same as above but ifup fails
|
||||||
|
port_info = {'mac_address': '123', 'fixed_ips': [
|
||||||
|
{'ip_address': '10.0.0.5', 'subnet_cidr': '10.0.0.0/24'}]}
|
||||||
mock_interfaces.side_effect = [['blah']]
|
mock_interfaces.side_effect = [['blah']]
|
||||||
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
mock_ifaddress.side_effect = [[netifaces.AF_LINK],
|
||||||
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
{netifaces.AF_LINK: [{'addr': '123'}]}]
|
||||||
@ -654,7 +759,7 @@ class ServerTestCase(base.TestCase):
|
|||||||
handle.write.assert_any_call(
|
handle.write.assert_any_call(
|
||||||
'\n# Generated by Octavia agent\n'
|
'\n# Generated by Octavia agent\n'
|
||||||
'auto blah blah:0\n'
|
'auto blah blah:0\n'
|
||||||
'iface blah inet dhcp\n'
|
'iface blah inet dhcp\n\n'
|
||||||
'iface blah:0 inet static\n'
|
'iface blah:0 inet static\n'
|
||||||
'address 203.0.113.2\n'
|
'address 203.0.113.2\n'
|
||||||
'broadcast 203.0.113.255\n'
|
'broadcast 203.0.113.255\n'
|
||||||
|
@ -558,8 +558,10 @@ class ServerTestCase(base.TestCase):
|
|||||||
handle = m()
|
handle = m()
|
||||||
handle.write.assert_any_call(
|
handle.write.assert_any_call(
|
||||||
'\n# Generated by Octavia agent\n'
|
'\n# Generated by Octavia agent\n'
|
||||||
'auto eth' + test_int_num + ' eth' + test_int_num + ':0\n'
|
'auto eth' + test_int_num + '\n'
|
||||||
'iface eth' + test_int_num + ' inet dhcp')
|
'iface eth' + test_int_num + ' inet dhcp\n'
|
||||||
|
'auto eth' + test_int_num + ':0\n'
|
||||||
|
'iface eth' + test_int_num + ':0 inet6 auto\n')
|
||||||
mock_check_output.assert_called_with(
|
mock_check_output.assert_called_with(
|
||||||
['ip', 'netns', 'exec', 'amphora-haproxy', 'ifup',
|
['ip', 'netns', 'exec', 'amphora-haproxy', 'ifup',
|
||||||
'eth' + test_int_num], stderr=-2)
|
'eth' + test_int_num], stderr=-2)
|
||||||
@ -660,7 +662,7 @@ class ServerTestCase(base.TestCase):
|
|||||||
handle.write.assert_any_call(
|
handle.write.assert_any_call(
|
||||||
'\n# Generated by Octavia agent\n'
|
'\n# Generated by Octavia agent\n'
|
||||||
'auto blah blah:0\n'
|
'auto blah blah:0\n'
|
||||||
'iface blah inet dhcp\n'
|
'iface blah inet dhcp\n\n'
|
||||||
'iface blah:0 inet static\n'
|
'iface blah:0 inet static\n'
|
||||||
'address 203.0.113.2\n'
|
'address 203.0.113.2\n'
|
||||||
'broadcast 203.0.113.255\n'
|
'broadcast 203.0.113.255\n'
|
||||||
|
@ -32,11 +32,9 @@ FAKE_CIDR = '10.0.0.0/24'
|
|||||||
FAKE_GATEWAY = '10.0.0.1'
|
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,
|
|
||||||
'gateway': FAKE_GATEWAY,
|
|
||||||
'mac_address': '123'}
|
|
||||||
FAKE_UUID_1 = uuidutils.generate_uuid()
|
FAKE_UUID_1 = uuidutils.generate_uuid()
|
||||||
FAKE_VRRP_IP = '10.1.0.1'
|
FAKE_VRRP_IP = '10.1.0.1'
|
||||||
|
FAKE_MAC_ADDRESS = '123'
|
||||||
|
|
||||||
|
|
||||||
class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
||||||
@ -55,7 +53,16 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(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')
|
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.port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
|
||||||
|
fixed_ips=[self.fixed_ip])
|
||||||
|
|
||||||
|
self.subnet_info = {'subnet_cidr': FAKE_CIDR,
|
||||||
|
'gateway': FAKE_GATEWAY,
|
||||||
|
'mac_address': FAKE_MAC_ADDRESS,
|
||||||
|
'vrrp_ip': self.amp.vrrp_ip}
|
||||||
|
|
||||||
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
@mock.patch('octavia.common.tls_utils.cert_parser.load_certificates_data')
|
||||||
@mock.patch('octavia.common.tls_utils.cert_parser.get_host_names')
|
@mock.patch('octavia.common.tls_utils.cert_parser.get_host_names')
|
||||||
@ -154,12 +161,23 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
|||||||
amphorae_network_config.get().vrrp_port = self.port
|
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, self.subnet_info)
|
||||||
|
|
||||||
def test_post_network_plug(self):
|
def test_post_network_plug(self):
|
||||||
|
# Test dhcp path
|
||||||
|
port = network_models.Port(mac_address=FAKE_MAC_ADDRESS, fixed_ips=[])
|
||||||
|
self.driver.post_network_plug(self.amp, port)
|
||||||
|
self.driver.client.plug_network.assert_called_once_with(
|
||||||
|
self.amp, dict(mac_address=FAKE_MAC_ADDRESS, fixed_ips=[]))
|
||||||
|
|
||||||
|
self.driver.client.plug_network.reset_mock()
|
||||||
|
|
||||||
|
# Test fixed IP path
|
||||||
self.driver.post_network_plug(self.amp, self.port)
|
self.driver.post_network_plug(self.amp, self.port)
|
||||||
self.driver.client.plug_network.assert_called_once_with(
|
self.driver.client.plug_network.assert_called_once_with(
|
||||||
self.amp, dict(mac_address='123'))
|
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
|
||||||
|
fixed_ips=[dict(ip_address='10.0.0.5',
|
||||||
|
subnet_cidr='10.0.0.0/24')]))
|
||||||
|
|
||||||
def test_get_vrrp_interface(self):
|
def test_get_vrrp_interface(self):
|
||||||
self.driver.get_vrrp_interface(self.amp)
|
self.driver.get_vrrp_interface(self.amp)
|
||||||
@ -174,11 +192,16 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
|||||||
self.driver = driver.AmphoraAPIClient()
|
self.driver = driver.AmphoraAPIClient()
|
||||||
self.base_url = "https://127.0.0.1:9443/0.5"
|
self.base_url = "https://127.0.0.1:9443/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')
|
self.port_info = dict(mac_address=FAKE_MAC_ADDRESS)
|
||||||
# Override with much lower values for testing purposes..
|
# Override with much lower values for testing purposes..
|
||||||
conf = oslo_fixture.Config(cfg.CONF)
|
conf = oslo_fixture.Config(cfg.CONF)
|
||||||
conf.config(group="haproxy_amphora", connection_max_retries=2)
|
conf.config(group="haproxy_amphora", connection_max_retries=2)
|
||||||
|
|
||||||
|
self.subnet_info = {'subnet_cidr': FAKE_CIDR,
|
||||||
|
'gateway': FAKE_GATEWAY,
|
||||||
|
'mac_address': FAKE_MAC_ADDRESS,
|
||||||
|
'vrrp_ip': self.amp.vrrp_ip}
|
||||||
|
|
||||||
def test_request(self):
|
def test_request(self):
|
||||||
self.assertRaises(driver_except.TimeOutException,
|
self.assertRaises(driver_except.TimeOutException,
|
||||||
self.driver.request,
|
self.driver.request,
|
||||||
@ -691,7 +714,7 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
|||||||
m.post("{base}/plug/vip/{vip}".format(
|
m.post("{base}/plug/vip/{vip}".format(
|
||||||
base=self.base_url, vip=FAKE_IP)
|
base=self.base_url, vip=FAKE_IP)
|
||||||
)
|
)
|
||||||
self.driver.plug_vip(self.amp, FAKE_IP, FAKE_SUBNET_INFO)
|
self.driver.plug_vip(self.amp, FAKE_IP, self.subnet_info)
|
||||||
self.assertTrue(m.called)
|
self.assertTrue(m.called)
|
||||||
|
|
||||||
@requests_mock.mock()
|
@requests_mock.mock()
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Adds support for networks that do not have DHCP services enabled.
|
||||||
|
upgrade:
|
||||||
|
- To support networks without DHCP you must upgrade your amphora image.
|
@ -38,5 +38,5 @@ taskflow>=1.26.0 # Apache-2.0
|
|||||||
#for the amphora api
|
#for the amphora api
|
||||||
Flask!=0.11,<1.0,>=0.10 # BSD
|
Flask!=0.11,<1.0,>=0.10 # BSD
|
||||||
netifaces>=0.10.4 # MIT
|
netifaces>=0.10.4 # MIT
|
||||||
|
ipaddress>=1.0.7;python_version<'3.3' # PSF
|
||||||
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
|
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user