Fix plugging member subnets on existing networks

CalculateAmphoraDelta uses now subnets instead of networks.
HandleNetworkDelta plugs subnets on existing ports, or removes subnet
from ports
The Amphora server supports multiple 'plug' calls to the same port,
allowing to update the settings of an interface.

Stable branches: Also includes a follow up patch for amphorav1 [0]

[0] Ibbfca939ff580fd13c094c86721f703a711ffc46

Co-Authored-By: Gregory Thiemonge <gthiemon@redhat.com>

Story: 2004112
Task: 27533

Change-Id: I1384c6f52eec99e6573a8e83fe5a80a632804083
(cherry picked from commit 4b0d4c1b59)
(cherry picked from commit cade09e72a)
This commit is contained in:
Adam Harwell 2019-06-14 07:13:02 -07:00 committed by Gregory Thiemonge
parent c513c9e77a
commit 16fa7f8eb9
50 changed files with 2739 additions and 637 deletions

View File

@ -164,10 +164,10 @@ class AmphoraInfo(object):
for interface in netns.get_links():
interface_name = None
for item in interface['attrs']:
if (item[0] == 'IFLA_IFNAME' and
if (item[0] == consts.IFLA_IFNAME and
not item[1].startswith('eth')):
break
if item[0] == 'IFLA_IFNAME':
if item[0] == consts.IFLA_IFNAME:
interface_name = item[1]
if item[0] == 'IFLA_STATS64':
networks[interface_name] = dict(

View File

@ -66,7 +66,7 @@ class BaseOS(object):
def write_vip_interface_file(self, interface, vip, ip_version,
prefixlen, gateway,
mtu, vrrp_ip,
host_routes):
host_routes, fixed_ips=None):
vip_interface = interface_file.VIPInterfaceFile(
name=interface,
mtu=mtu,
@ -76,6 +76,7 @@ class BaseOS(object):
gateway=gateway,
vrrp_ip=vrrp_ip,
host_routes=host_routes,
fixed_ips=fixed_ips,
topology=CONF.controller_worker.loadbalancer_topology)
vip_interface.write()
@ -94,7 +95,8 @@ class BaseOS(object):
try:
out = subprocess.check_output(cmd.split(),
stderr=subprocess.STDOUT)
LOG.debug(out)
for line in out.decode('utf-8').split('\n'):
LOG.debug(line)
except subprocess.CalledProcessError as e:
LOG.error('Failed to set up %s due to error: %s %s', interface,
e, e.output)

View File

@ -132,23 +132,61 @@ class Plug(object):
except socket.error:
socket.inet_pton(socket.AF_INET6, ip.get('ip_address'))
def plug_network(self, mac_address, fixed_ips, mtu=None):
# Check if the interface is already in the network namespace
# Do not attempt to re-plug the network if it is already in the
# network namespace
if self._netns_interface_exists(mac_address):
return webob.Response(json=dict(
message="Interface already exists"), status=409)
# This is the interface as it was initially plugged into the
# default network namespace, this will likely always be eth1
def plug_network(self, mac_address, fixed_ips, mtu=None,
vip_net_info=None):
try:
self._check_ip_addresses(fixed_ips=fixed_ips)
except socket.error:
return webob.Response(json=dict(
message="Invalid network port"), status=400)
# Check if the interface is already in the network namespace
# Do not attempt to re-plug the network if it is already in the
# network namespace, just ensure all fixed_ips are up
if self._netns_interface_exists(mac_address):
# Get the existing interface name and path
existing_interface = self._netns_interface_by_mac(mac_address)
# If we have net_info, this is the special case of plugging a new
# subnet on the vrrp port, which is essentially a re-vip-plug
if vip_net_info:
ip = ipaddress.ip_address(vip_net_info['vip'])
network = ipaddress.ip_network(vip_net_info['subnet_cidr'])
vip = ip.exploded
prefixlen = network.prefixlen
vrrp_ip = vip_net_info.get('vrrp_ip')
gateway = vip_net_info['gateway']
host_routes = vip_net_info.get('host_routes', ())
self._osutils.write_vip_interface_file(
interface=existing_interface,
vip=vip,
ip_version=ip.version,
prefixlen=prefixlen,
gateway=gateway,
vrrp_ip=vrrp_ip,
host_routes=host_routes,
mtu=mtu,
fixed_ips=fixed_ips)
self._osutils.bring_interface_up(existing_interface, 'vip')
# Otherwise, we are just plugging a run-of-the-mill network
else:
# Write an updated config
self._osutils.write_port_interface_file(
interface=existing_interface,
fixed_ips=fixed_ips,
mtu=mtu)
self._osutils.bring_interface_up(existing_interface, 'network')
return webob.Response(json=dict(
message="OK",
details="Updated existing interface {interface}".format(
# TODO(rm_work): Everything in this should probably use
# HTTP code 200, but continuing to use 202 for consistency.
interface=existing_interface)), status=202)
# This is the interface as it was initially plugged into the
# default network namespace, this will likely always be eth1
default_netns_interface = self._interface_by_mac(mac_address)
# We need to determine the interface name when inside the namespace
@ -192,7 +230,7 @@ class Plug(object):
idx = ipr.link_lookup(address=mac)[0]
addr = ipr.get_links(idx)[0]
for attr in addr['attrs']:
if attr[0] == 'IFLA_IFNAME':
if attr[0] == consts.IFLA_IFNAME:
return attr[1]
except Exception as e:
LOG.info('Unable to find interface with MAC: %s, rescanning '
@ -222,11 +260,14 @@ class Plug(object):
text_file.write("{mac_address} {interface}\n".format(
mac_address=mac_address, interface=interface))
def _netns_interface_exists(self, mac_address):
def _netns_interface_by_mac(self, mac_address):
with pyroute2.NetNS(consts.AMPHORA_NAMESPACE,
flags=os.O_CREAT) as netns:
for link in netns.get_links():
for attr in link['attrs']:
if attr[0] == 'IFLA_ADDRESS' and attr[1] == mac_address:
return True
return False
attr_dict = dict(link['attrs'])
if attr_dict.get(consts.IFLA_ADDRESS) == mac_address:
return attr_dict.get(consts.IFLA_IFNAME)
return None
def _netns_interface_exists(self, mac_address):
return self._netns_interface_by_mac(mac_address) is not None

View File

@ -214,7 +214,8 @@ class Server(object):
description='Invalid port information') from e
return self._plug.plug_network(port_info['mac_address'],
port_info.get('fixed_ips'),
port_info.get('mtu'))
port_info.get('mtu'),
port_info.get('vip_net_info'))
def upload_cert(self):
return certificate_update.upload_server_cert()

View File

@ -129,13 +129,14 @@ class VIPInterfaceFile(InterfaceFile):
consts.GATEWAY: gateway,
consts.FLAGS: [consts.ONLINK]
})
self.routes.append({
consts.DST: (
"::/0" if ip_version == 6 else "0.0.0.0/0"),
consts.GATEWAY: gateway,
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1,
})
if topology != consts.TOPOLOGY_ACTIVE_STANDBY:
self.routes.append({
consts.DST: (
"::/0" if ip_version == 6 else "0.0.0.0/0"),
consts.GATEWAY: gateway,
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1,
})
# In ACTIVE_STANDBY topology, keepalived configures the VIP address.
# Keep track of it in the interface file but mark it with a special
@ -148,9 +149,13 @@ class VIPInterfaceFile(InterfaceFile):
# tool (keepalived)
consts.OCTAVIA_OWNED: topology != consts.TOPOLOGY_ACTIVE_STANDBY
})
vip_cidr = ipaddress.ip_network(
"{}/{}".format(vip, prefixlen), strict=False)
self.routes.append({
consts.DST: vip_cidr.exploded,
consts.SCOPE: 'link',
})
if topology != consts.TOPOLOGY_ACTIVE_STANDBY:
vip_cidr = ipaddress.ip_network(
"{}/{}".format(vip, prefixlen), strict=False)
self.routes.append({
consts.DST: vip_cidr.exploded,
consts.PREFSRC: vip,
@ -169,23 +174,40 @@ class VIPInterfaceFile(InterfaceFile):
ip_versions = {ip_version}
if fixed_ips:
for fixed_ip in fixed_ips:
ip_addr = fixed_ip['ip_address']
cidr = fixed_ip['subnet_cidr']
ip = ipaddress.ip_address(ip_addr)
network = ipaddress.ip_network(cidr)
prefixlen = network.prefixlen
self.addresses.append({
consts.ADDRESS: fixed_ip['ip_address'],
consts.PREFIXLEN: prefixlen,
for fixed_ip in fixed_ips or ():
ip_addr = fixed_ip['ip_address']
cidr = fixed_ip['subnet_cidr']
ip = ipaddress.ip_address(ip_addr)
network = ipaddress.ip_network(cidr)
prefixlen = network.prefixlen
self.addresses.append({
consts.ADDRESS: fixed_ip['ip_address'],
consts.PREFIXLEN: prefixlen,
})
ip_versions.add(ip.version)
gateway = fixed_ip.get('gateway')
if gateway:
# Add default routes if there's a gateway
self.routes.append({
consts.DST: (
"::/0" if ip.version == 6 else "0.0.0.0/0"),
consts.GATEWAY: gateway,
consts.FLAGS: [consts.ONLINK]
})
if topology != consts.TOPOLOGY_ACTIVE_STANDBY:
self.routes.append({
consts.DST: (
"::/0" if ip.version == 6 else "0.0.0.0/0"),
consts.GATEWAY: gateway,
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1,
})
ip_versions.add(ip.version)
host_routes = self.get_host_routes(
fixed_ip.get('host_routes', []))
self.routes.extend(host_routes)
host_routes = self.get_host_routes(
fixed_ip.get('host_routes', []))
self.routes.extend(host_routes)
for ip_v in ip_versions:
self.scripts[consts.IFACE_UP].append({

View File

@ -15,6 +15,7 @@ import ipaddress
import pyroute2
from octavia.common import constants as consts
from octavia.common import exceptions
@ -47,7 +48,7 @@ def _find_interface(ip_address, rtnl_api, normalized_addr):
for int_attr in lookup_int[0]['attrs']:
# Look for the attribute name/value pair that includes
# the interface name
if int_attr[0] == 'IFLA_IFNAME':
if int_attr[0] == consts.IFLA_IFNAME:
# Return the matching interface name that is in
# int_attr[1] for the matching interface attribute
# name

View File

@ -162,6 +162,8 @@ class AmphoraLoadBalancerDriver(object, metaclass=abc.ABCMeta):
:param amphorae_network_config: A data model containing information
about the subnets and ports that an
amphorae owns.
:type amphorae_network_config: octavia.network.data_models.
AmphoraNetworkConfig
:param vrrp_port: VRRP port associated with the load balancer
:type vrrp_port: octavia.network.data_models.Port
@ -175,13 +177,18 @@ class AmphoraLoadBalancerDriver(object, metaclass=abc.ABCMeta):
the vip, such as bring up interfaces.
"""
def post_network_plug(self, amphora, port):
def post_network_plug(self, amphora, port, amphora_network_config):
"""Called after amphora added to network
:param amphora: amphora object, needs id and network ip(s)
:type amphora: octavia.db.models.Amphora
:param port: contains information of the plugged port
:type port: octavia.network.data_models.Port
:param amphora_network_config: A data model containing information
about the subnets and ports that an
amphorae owns.
:type amphora_network_config: octavia.network.data_models.
AmphoraNetworkConfig
This method is optional to implement. After adding an amphora to a
network, there may be steps necessary on the amphora to allow it to

View File

@ -39,6 +39,7 @@ from octavia.common.tls_utils import cert_parser
from octavia.common import utils
from octavia.db import api as db_apis
from octavia.db import repositories as repo
from octavia.network import data_models as network_models
LOG = logging.getLogger(__name__)
@ -390,23 +391,33 @@ class HaproxyAmphoraLoadBalancerDriver(
def finalize_amphora(self, amphora):
pass
def _build_net_info(self, port, amphora, subnet, mtu=None):
# NOTE(blogan): using the vrrp port here because that
# is what the allowed address pairs network driver sets
# this particular port to. This does expose a bit of
# tight coupling between the network driver and amphora
# driver. We will need to revisit this to try and remove
# this tight coupling.
# 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.
host_routes = [{'nexthop': hr[consts.NEXTHOP],
'destination': hr[consts.DESTINATION]}
for hr in subnet[consts.HOST_ROUTES]]
net_info = {'subnet_cidr': subnet[consts.CIDR],
'gateway': subnet[consts.GATEWAY_IP],
'mac_address': port[consts.MAC_ADDRESS],
'vrrp_ip': amphora[consts.VRRP_IP],
'mtu': mtu or port[consts.NETWORK][consts.MTU],
'host_routes': host_routes}
return net_info
def post_vip_plug(self, amphora, load_balancer, amphorae_network_config,
vrrp_port=None, vip_subnet=None):
if amphora.status != consts.DELETED:
self._populate_amphora_api_version(amphora)
if vip_subnet is None:
subnet = amphorae_network_config.get(amphora.id).vip_subnet
else:
subnet = vip_subnet
# NOTE(blogan): using the vrrp port here because that
# is what the allowed address pairs network driver sets
# this particular port to. This does expose a bit of
# tight coupling between the network driver and amphora
# driver. We will need to revisit this to try and remove
# this tight coupling.
# 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.
vip_subnet = amphorae_network_config.get(amphora.id).vip_subnet
if vrrp_port is None:
port = amphorae_network_config.get(amphora.id).vrrp_port
mtu = port.network.mtu
@ -415,15 +426,9 @@ class HaproxyAmphoraLoadBalancerDriver(
mtu = port.network['mtu']
LOG.debug("Post-VIP-Plugging with vrrp_ip %s vrrp_port %s",
amphora.vrrp_ip, port.id)
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,
'mtu': mtu,
'host_routes': host_routes}
net_info = self._build_net_info(
port.to_dict(recurse=True), amphora.to_dict(),
vip_subnet.to_dict(recurse=True), mtu)
try:
self.clients[amphora.api_version].plug_vip(
amphora, load_balancer.vip.ip_address, net_info)
@ -432,7 +437,7 @@ class HaproxyAmphoraLoadBalancerDriver(
'skipping post_vip_plug',
{'mac': port.mac_address})
def post_network_plug(self, amphora, port):
def post_network_plug(self, amphora, port, amphora_network_config):
fixed_ips = []
for fixed_ip in port.fixed_ips:
host_routes = [{'nexthop': hr.nexthop,
@ -440,11 +445,25 @@ class HaproxyAmphoraLoadBalancerDriver(
for hr in fixed_ip.subnet.host_routes]
ip = {'ip_address': fixed_ip.ip_address,
'subnet_cidr': fixed_ip.subnet.cidr,
'host_routes': host_routes}
'host_routes': host_routes,
'gateway': fixed_ip.subnet.gateway_ip}
fixed_ips.append(ip)
port_info = {'mac_address': port.mac_address,
'fixed_ips': fixed_ips,
'mtu': port.network.mtu}
if port.id == amphora.vrrp_port_id:
if isinstance(amphora_network_config,
network_models.AmphoraNetworkConfig):
amphora_network_config = amphora_network_config.to_dict(
recurse=True)
# We have to special-case sharing the vrrp port and pass through
# enough extra information to populate the whole VIP port
net_info = self._build_net_info(
port.to_dict(recurse=True), amphora.to_dict(),
amphora_network_config[consts.VIP_SUBNET],
port.network.mtu)
net_info['vip'] = amphora.ha_ip
port_info['vip_net_info'] = net_info
try:
self._populate_amphora_api_version(amphora)
self.clients[amphora.api_version].plug_network(amphora, port_info)

View File

@ -85,9 +85,10 @@ class NoopManager(object):
self.__class__.__name__, amphora.id)
self.amphoraconfig[amphora.id] = (amphora.id, 'finalize amphora')
def post_network_plug(self, amphora, port):
LOG.debug("Amphora %s no-op, post network plug amphora %s, port %s",
self.__class__.__name__, amphora.id, port.id)
def post_network_plug(self, amphora, port, amphora_network_config):
LOG.debug("Amphora %s no-op, post network plug amphora %s, port %s, "
"amphora_network_config %s", self.__class__.__name__,
amphora.id, port.id, amphora_network_config)
self.amphoraconfig[amphora.id, port.id] = (amphora.id, port.id,
'post_network_plug')
@ -160,9 +161,9 @@ class NoopAmphoraLoadBalancerDriver(
self.driver.finalize_amphora(amphora)
def post_network_plug(self, amphora, port):
def post_network_plug(self, amphora, port, amphora_network_config):
self.driver.post_network_plug(amphora, port)
self.driver.post_network_plug(amphora, port, amphora_network_config)
def post_vip_plug(self, amphora, load_balancer, amphorae_network_config,
vrrp_port=None, vip_subnet=None):

View File

@ -298,7 +298,7 @@ SUPPORTED_TASKFLOW_ENGINE_TYPES = ['serial', 'parallel']
# Task/Flow constants
ACTIVE_CONNECTIONS = 'active_connections'
ADD_NICS = 'add_nics'
ADDED_PORTS = 'added_ports'
ADD_SUBNETS = 'add_subnets'
ADMIN_STATE_UP = 'admin_state_up'
ALLOWED_ADDRESS_PAIRS = 'allowed_address_pairs'
AMP_DATA = 'amp_data'
@ -330,9 +330,11 @@ CREATED_AT = 'created_at'
CRL_CONTAINER_ID = 'crl_container_id'
DEFAULT_TLS_CONTAINER_DATA = 'default_tls_container_data'
DELETE_NICS = 'delete_nics'
DELETE_SUBNETS = 'delete_subnets'
DELTA = 'delta'
DELTAS = 'deltas'
DESCRIPTION = 'description'
DESTINATION = 'destination'
DEVICE_OWNER = 'device_owner'
ENABLED = 'enabled'
FAILED_AMP_VRRP_PORT_ID = 'failed_amp_vrrp_port_id'
@ -342,6 +344,7 @@ FAILOVER_AMPHORA_ID = 'failover_amphora_id'
FIELDS = 'fields'
FIXED_IPS = 'fixed_ips'
FLAVOR_ID = 'flavor_id'
GATEWAY_IP = 'gateway_ip'
HA_IP = 'ha_ip'
HA_PORT_ID = 'ha_port_id'
HEALTH_MON = 'health_mon'
@ -349,6 +352,7 @@ HEALTH_MONITOR = 'health_monitor'
HEALTH_MONITOR_ID = 'health_monitor_id'
HEALTHMONITOR_ID = 'healthmonitor_id'
HEALTH_MONITOR_UPDATES = 'health_monitor_updates'
HOST_ROUTES = 'host_routes'
ID = 'id'
IMAGE_ID = 'image_id'
IP_ADDRESS = 'ip_address'
@ -367,6 +371,7 @@ LOADBALANCER = 'loadbalancer'
LOADBALANCER_ID = 'loadbalancer_id'
LOAD_BALANCER_ID = 'load_balancer_id'
LOAD_BALANCER_UPDATES = 'load_balancer_updates'
MAC_ADDRESS = 'mac_address'
MANAGEMENT_NETWORK = 'management_network'
MEMBER = 'member'
MEMBER_ID = 'member_id'
@ -376,6 +381,7 @@ MESSAGE = 'message'
NAME = 'name'
NETWORK = 'network'
NETWORK_ID = 'network_id'
NEXTHOP = 'nexthop'
NICS = 'nics'
OBJECT = 'object'
ORIGINAL_HEALTH_MONITOR = 'original_health_monitor'
@ -423,10 +429,12 @@ TOPOLOGY = 'topology'
TOTAL_CONNECTIONS = 'total_connections'
UPDATED_AT = 'updated_at'
UPDATE_DICT = 'update_dict'
UPDATED_PORTS = 'updated_ports'
VALID_VIP_NETWORKS = 'valid_vip_networks'
VIP = 'vip'
VIP_ADDRESS = 'vip_address'
VIP_NETWORK = 'vip_network'
VIP_NETWORK_ID = 'vip_network_id'
VIP_PORT_ID = 'vip_port_id'
VIP_QOS_POLICY_ID = 'vip_qos_policy_id'
VIP_SG_ID = 'vip_sg_id'
@ -925,5 +933,8 @@ IFACE_DOWN = 'down'
COMMAND = 'command'
IFLA_ADDRESS = 'IFLA_ADDRESS'
IFLA_IFNAME = 'IFLA_IFNAME'
# Amphora network directory
AMP_NET_DIR_TEMPLATE = '/etc/octavia/interfaces/'

View File

@ -429,6 +429,7 @@ class ControllerWorker(base_taskflow.BaseTaskFlowEngine):
constants.MEMBER: member,
constants.LISTENERS: listeners,
constants.LOADBALANCER: load_balancer,
constants.LOADBALANCER_ID: load_balancer.id,
constants.POOL: pool}
if load_balancer.availability_zone:
store[constants.AVAILABILITY_ZONE] = (
@ -461,6 +462,7 @@ class ControllerWorker(base_taskflow.BaseTaskFlowEngine):
constants.MEMBER: member,
constants.LISTENERS: listeners,
constants.LOADBALANCER: load_balancer,
constants.LOADBALANCER_ID: load_balancer.id,
constants.POOL: pool}
if load_balancer.availability_zone:
store[constants.AVAILABILITY_ZONE] = (
@ -512,6 +514,7 @@ class ControllerWorker(base_taskflow.BaseTaskFlowEngine):
store = {
constants.LISTENERS: listeners,
constants.LOADBALANCER: load_balancer,
constants.LOADBALANCER_ID: load_balancer.id,
constants.POOL: pool}
if load_balancer.availability_zone:
store[constants.AVAILABILITY_ZONE] = (

View File

@ -436,18 +436,17 @@ class AmphoraFlows(object):
amp_for_failover_flow.add(network_tasks.CalculateAmphoraDelta(
name=prefix + '-' + constants.CALCULATE_AMPHORA_DELTA,
requires=(constants.LOADBALANCER, constants.AMPHORA,
constants.AVAILABILITY_ZONE, constants.VRRP_PORT),
rebind={constants.VRRP_PORT: constants.BASE_PORT},
constants.AVAILABILITY_ZONE),
provides=constants.DELTA))
amp_for_failover_flow.add(network_tasks.HandleNetworkDelta(
name=prefix + '-' + constants.HANDLE_NETWORK_DELTA,
requires=(constants.AMPHORA, constants.DELTA),
provides=constants.ADDED_PORTS))
provides=constants.UPDATED_PORTS))
amp_for_failover_flow.add(amphora_driver_tasks.AmphoraePostNetworkPlug(
name=prefix + '-' + constants.AMPHORAE_POST_NETWORK_PLUG,
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)))
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS)))
return amp_for_failover_flow

View File

@ -202,12 +202,12 @@ class LoadBalancerFlows(object):
)
flows.append(
network_tasks.HandleNetworkDeltas(
requires=constants.DELTAS, provides=constants.ADDED_PORTS
requires=constants.DELTAS, provides=constants.UPDATED_PORTS
)
)
flows.append(
amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS)
)
)
flows.append(

View File

@ -43,10 +43,14 @@ class MemberFlows(object):
requires=(constants.LOADBALANCER, constants.AVAILABILITY_ZONE),
provides=constants.DELTAS))
create_member_flow.add(network_tasks.HandleNetworkDeltas(
requires=constants.DELTAS, provides=constants.ADDED_PORTS))
requires=(constants.DELTAS, constants.LOADBALANCER),
provides=constants.UPDATED_PORTS))
create_member_flow.add(network_tasks.GetAmphoraeNetworkConfigs(
requires=constants.LOADBALANCER_ID,
provides=constants.AMPHORAE_NETWORK_CONFIG))
create_member_flow.add(amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)
))
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS,
constants.AMPHORAE_NETWORK_CONFIG)))
create_member_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=constants.LOADBALANCER))
create_member_flow.add(database_tasks.MarkMemberActiveInDB(
@ -73,6 +77,18 @@ class MemberFlows(object):
constants.POOL]))
delete_member_flow.add(database_tasks.MarkMemberPendingDeleteInDB(
requires=constants.MEMBER))
delete_member_flow.add(network_tasks.CalculateDelta(
requires=(constants.LOADBALANCER, constants.AVAILABILITY_ZONE),
provides=constants.DELTAS))
delete_member_flow.add(network_tasks.HandleNetworkDeltas(
requires=(constants.DELTAS, constants.LOADBALANCER),
provides=constants.UPDATED_PORTS))
delete_member_flow.add(network_tasks.GetAmphoraeNetworkConfigs(
requires=constants.LOADBALANCER_ID,
provides=constants.AMPHORAE_NETWORK_CONFIG))
delete_member_flow.add(amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS,
constants.AMPHORAE_NETWORK_CONFIG)))
delete_member_flow.add(model_tasks.
DeleteModelObject(rebind={constants.OBJECT:
constants.MEMBER}))
@ -188,10 +204,15 @@ class MemberFlows(object):
requires=(constants.LOADBALANCER, constants.AVAILABILITY_ZONE),
provides=constants.DELTAS))
batch_update_members_flow.add(network_tasks.HandleNetworkDeltas(
requires=constants.DELTAS, provides=constants.ADDED_PORTS))
requires=(constants.DELTAS, constants.LOADBALANCER),
provides=constants.UPDATED_PORTS))
batch_update_members_flow.add(network_tasks.GetAmphoraeNetworkConfigs(
requires=constants.LOADBALANCER_ID,
provides=constants.AMPHORAE_NETWORK_CONFIG))
batch_update_members_flow.add(
amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)))
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS,
constants.AMPHORAE_NETWORK_CONFIG)))
# Update the Listener (this makes the changes active on the Amp)
batch_update_members_flow.add(amphora_driver_tasks.ListenersUpdate(

View File

@ -196,10 +196,11 @@ class AmphoraFinalize(BaseAmphoraTask):
class AmphoraPostNetworkPlug(BaseAmphoraTask):
"""Task to notify the amphora post network plug."""
def execute(self, amphora, ports):
def execute(self, amphora, ports, amphora_network_config):
"""Execute post_network_plug routine."""
for port in ports:
self.amphora_driver.post_network_plug(amphora, port)
self.amphora_driver.post_network_plug(
amphora, port, amphora_network_config)
LOG.debug("post_network_plug called on compute instance "
"%(compute_id)s for port %(port_id)s",
{"compute_id": amphora.compute_id, "port_id": port.id})
@ -215,7 +216,7 @@ class AmphoraPostNetworkPlug(BaseAmphoraTask):
class AmphoraePostNetworkPlug(BaseAmphoraTask):
"""Task to notify the amphorae post network plug."""
def execute(self, loadbalancer, added_ports):
def execute(self, loadbalancer, updated_ports, amphorae_network_config):
"""Execute post_network_plug routine."""
amp_post_plug = AmphoraPostNetworkPlug()
# We need to make sure we have the fresh list of amphora
@ -223,10 +224,11 @@ class AmphoraePostNetworkPlug(BaseAmphoraTask):
db_apis.get_session(), load_balancer_id=loadbalancer.id,
status=constants.AMPHORA_ALLOCATED)[0]
for amphora in amphorae:
if amphora.id in added_ports:
amp_post_plug.execute(amphora, added_ports[amphora.id])
if amphora.id in updated_ports:
amp_post_plug.execute(amphora, updated_ports[amphora.id],
amphorae_network_config[amphora.id])
def revert(self, result, loadbalancer, added_ports, *args, **kwargs):
def revert(self, result, loadbalancer, updated_ports, *args, **kwargs):
"""Handle a failed post network plug."""
if isinstance(result, failure.Failure):
return

View File

@ -53,42 +53,101 @@ class CalculateAmphoraDelta(BaseNetworkTask):
default_provides = constants.DELTA
def execute(self, loadbalancer, amphora, availability_zone,
vrrp_port=None):
def execute(self, loadbalancer, amphora, availability_zone):
LOG.debug("Calculating network delta for amphora id: %s", amphora.id)
if vrrp_port is None:
vrrp_port = self.network_driver.get_port(amphora.vrrp_port_id)
vip_subnet_to_net_map = {
loadbalancer.vip.subnet_id:
loadbalancer.vip.network_id,
}
# Figure out what networks we want
# seed with lb network(s)
if (availability_zone and
availability_zone.get(constants.MANAGEMENT_NETWORK)):
management_nets = [availability_zone.get(
constants.MANAGEMENT_NETWORK)]
management_nets = [
availability_zone.get(constants.MANAGEMENT_NETWORK)]
else:
management_nets = CONF.controller_worker.amp_boot_network_list
desired_network_ids = {vrrp_port.network_id}.union(management_nets)
desired_subnet_to_net_map = {}
for mgmt_net_id in management_nets:
for subnet_id in self.network_driver.get_network(
mgmt_net_id).subnets:
desired_subnet_to_net_map[subnet_id] = mgmt_net_id
desired_subnet_to_net_map.update(vip_subnet_to_net_map)
for pool in loadbalancer.pools:
member_networks = [
self.network_driver.get_subnet(member.subnet_id).network_id
for member in pool.members
if member.subnet_id
]
desired_network_ids.update(member_networks)
for member in pool.members:
if (member.subnet_id and
member.provisioning_status !=
constants.PENDING_DELETE):
member_network = self.network_driver.get_subnet(
member.subnet_id).network_id
desired_subnet_to_net_map[member.subnet_id] = (
member_network)
nics = self.network_driver.get_plugged_networks(amphora.compute_id)
# assume we don't have two nics in the same network
actual_network_nics = dict((nic.network_id, nic) for nic in nics)
desired_network_ids = set(desired_subnet_to_net_map.values())
desired_subnet_ids = set(desired_subnet_to_net_map)
del_ids = set(actual_network_nics) - desired_network_ids
delete_nics = list(
actual_network_nics[net_id] for net_id in del_ids)
# Calculate Network deltas
nics = self.network_driver.get_plugged_networks(
amphora.compute_id)
# we don't have two nics in the same network
network_to_nic_map = {nic.network_id: nic for nic in nics}
plugged_network_ids = set(network_to_nic_map)
del_ids = plugged_network_ids - desired_network_ids
delete_nics = [n_data_models.Interface(
network_id=net_id,
port_id=network_to_nic_map[net_id].port_id)
for net_id in del_ids]
add_ids = desired_network_ids - plugged_network_ids
add_nics = [n_data_models.Interface(
network_id=add_net_id,
fixed_ips=[
n_data_models.FixedIP(
subnet_id=subnet_id)
for subnet_id, net_id in desired_subnet_to_net_map.items()
if net_id == add_net_id])
for add_net_id in add_ids]
# Calculate member Subnet deltas
plugged_subnets = {}
for nic in network_to_nic_map.values():
for fixed_ip in nic.fixed_ips or []:
plugged_subnets[fixed_ip.subnet_id] = nic.network_id
plugged_subnet_ids = set(plugged_subnets)
del_subnet_ids = plugged_subnet_ids - desired_subnet_ids
add_subnet_ids = desired_subnet_ids - plugged_subnet_ids
def _subnet_updates(subnet_ids, subnets):
updates = []
for s in subnet_ids:
network_id = subnets[s]
nic = network_to_nic_map.get(network_id)
port_id = nic.port_id if nic else None
updates.append({
constants.SUBNET_ID: s,
constants.NETWORK_ID: network_id,
constants.PORT_ID: port_id
})
return updates
add_subnets = _subnet_updates(add_subnet_ids,
desired_subnet_to_net_map)
del_subnets = _subnet_updates(del_subnet_ids,
plugged_subnets)
add_ids = desired_network_ids - set(actual_network_nics)
add_nics = list(n_data_models.Interface(
network_id=net_id) for net_id in add_ids)
delta = n_data_models.Delta(
amphora_id=amphora.id, compute_id=amphora.compute_id,
add_nics=add_nics, delete_nics=delete_nics)
amphora_id=amphora.id,
compute_id=amphora.compute_id,
add_nics=add_nics, delete_nics=delete_nics,
add_subnets=add_subnets,
delete_subnets=del_subnets)
return delta
@ -234,28 +293,87 @@ class HandleNetworkDelta(BaseNetworkTask):
Plug or unplug networks based on delta
"""
def _fill_port_info(self, port):
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)
def execute(self, amphora, delta):
"""Handle network plugging based off deltas."""
added_ports = {}
added_ports[amphora.id] = []
updated_ports = {}
for nic in delta.add_nics:
interface = self.network_driver.plug_network(delta.compute_id,
nic.network_id)
subnet_id = nic.fixed_ips[0].subnet_id
interface = self.network_driver.plug_network(
amphora.compute_id, nic.network_id)
port = self.network_driver.get_port(interface.port_id)
port.network = self.network_driver.get_network(port.network_id)
for fixed_ip in port.fixed_ips:
fixed_ip.subnet = self.network_driver.get_subnet(
fixed_ip.subnet_id)
added_ports[amphora.id].append(port)
# nova may plugged undesired subnets (it plugs one of the subnets
# of the network), we can safely unplug the subnets we don't need,
# the desired subnet will be added in the 'ADD_SUBNETS' loop.
extra_subnets = [
fixed_ip.subnet_id
for fixed_ip in port.fixed_ips
if fixed_ip.subnet_id != subnet_id]
for subnet_id in extra_subnets:
port = self.network_driver.unplug_fixed_ip(
port_id=interface.port_id, subnet_id=subnet_id)
self._fill_port_info(port)
updated_ports[port.network_id] = port
for update in delta.add_subnets:
network_id = update[constants.NETWORK_ID]
# Get already existing port from Deltas or
# newly created port from updated_ports dict
port_id = (update[constants.PORT_ID] or
updated_ports[network_id].id)
subnet_id = update[constants.SUBNET_ID]
# Avoid duplicated subnets
has_subnet = False
if network_id in updated_ports:
has_subnet = any(
fixed_ip.subnet_id == subnet_id
for fixed_ip in updated_ports[network_id].fixed_ips)
if not has_subnet:
port = self.network_driver.plug_fixed_ip(
port_id=port_id, subnet_id=subnet_id)
self._fill_port_info(port)
updated_ports[network_id] = port
for update in delta.delete_subnets:
network_id = update[constants.NETWORK_ID]
port_id = update[constants.PORT_ID]
subnet_id = update[constants.SUBNET_ID]
port = self.network_driver.unplug_fixed_ip(
port_id=port_id, subnet_id=subnet_id)
self._fill_port_info(port)
# In neutron, when removing an ipv6 subnet (with slaac) from a
# port, it just ignores it.
# https://bugs.launchpad.net/neutron/+bug/1945156
# When it happens, don't add the port to the updated_ports dict
has_subnet = any(
fixed_ip.subnet_id == subnet_id
for fixed_ip in port.fixed_ips)
if not has_subnet:
updated_ports[network_id] = port
for nic in delta.delete_nics:
network_id = nic.network_id
try:
self.network_driver.unplug_network(delta.compute_id,
nic.network_id)
self.network_driver.unplug_network(
amphora.compute_id, network_id)
except base.NetworkNotFound:
LOG.debug("Network %d not found ", nic.network_id)
LOG.debug("Network %s not found", network_id)
except Exception:
LOG.exception("Unable to unplug network")
return added_ports
port_id = nic.port_id
try:
self.network_driver.delete_port(port_id)
except Exception:
LOG.exception("Unable to delete the port")
updated_ports.pop(network_id, None)
return {amphora.id: list(updated_ports.values())}
def revert(self, result, amphora, delta, *args, **kwargs):
"""Handle a network plug or unplug failures."""
@ -274,7 +392,14 @@ class HandleNetworkDelta(BaseNetworkTask):
self.network_driver.unplug_network(delta.compute_id,
nic.network_id)
except Exception:
pass
LOG.exception("Unable to unplug network %s",
nic.network_id)
port_id = nic.port_id
try:
self.network_driver.delete_port(port_id)
except Exception:
LOG.exception("Unable to delete port %s", port_id)
class HandleNetworkDeltas(BaseNetworkTask):
@ -284,35 +409,28 @@ class HandleNetworkDeltas(BaseNetworkTask):
networks based on delta
"""
def execute(self, deltas):
def execute(self, deltas, loadbalancer):
"""Handle network plugging based off deltas."""
added_ports = {}
amphorae = {amp.id: amp for amp in loadbalancer.amphorae}
updated_ports = {}
handle_delta = HandleNetworkDelta()
for amp_id, delta in deltas.items():
added_ports[amp_id] = []
for nic in delta.add_nics:
interface = self.network_driver.plug_network(delta.compute_id,
nic.network_id)
port = self.network_driver.get_port(interface.port_id)
port.network = self.network_driver.get_network(port.network_id)
for fixed_ip in port.fixed_ips:
fixed_ip.subnet = self.network_driver.get_subnet(
fixed_ip.subnet_id)
added_ports[amp_id].append(port)
for nic in delta.delete_nics:
try:
self.network_driver.unplug_network(delta.compute_id,
nic.network_id)
except base.NetworkNotFound:
LOG.debug("Network %d not found ", nic.network_id)
except Exception:
LOG.exception("Unable to unplug network")
return added_ports
ret = handle_delta.execute(amphorae[amp_id], delta)
updated_ports.update(ret)
return updated_ports
def revert(self, result, deltas, *args, **kwargs):
"""Handle a network plug or unplug failures."""
if isinstance(result, failure.Failure):
return
if not deltas:
return
for amp_id, delta in deltas.items():
LOG.warning("Unable to plug networks for amp id %s",
delta.amphora_id)
@ -323,8 +441,15 @@ class HandleNetworkDeltas(BaseNetworkTask):
try:
self.network_driver.unplug_network(delta.compute_id,
nic.network_id)
except base.NetworkNotFound:
pass
except Exception:
LOG.exception("Unable to unplug network %s",
nic.network_id)
port_id = nic.port_id
try:
self.network_driver.delete_port(port_id)
except Exception:
LOG.exception("Unable to delete port %s", port_id)
class PlugVIP(BaseNetworkTask):

View File

@ -403,18 +403,17 @@ class AmphoraFlows(object):
amp_for_failover_flow.add(network_tasks.CalculateAmphoraDelta(
name=prefix + '-' + constants.CALCULATE_AMPHORA_DELTA,
requires=(constants.LOADBALANCER, constants.AMPHORA,
constants.AVAILABILITY_ZONE, constants.VRRP_PORT),
rebind={constants.VRRP_PORT: constants.BASE_PORT},
constants.AVAILABILITY_ZONE),
provides=constants.DELTA))
amp_for_failover_flow.add(network_tasks.HandleNetworkDelta(
name=prefix + '-' + constants.HANDLE_NETWORK_DELTA,
requires=(constants.AMPHORA, constants.DELTA),
provides=constants.ADDED_PORTS))
provides=constants.UPDATED_PORTS))
amp_for_failover_flow.add(amphora_driver_tasks.AmphoraePostNetworkPlug(
name=prefix + '-' + constants.AMPHORAE_POST_NETWORK_PLUG,
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)))
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS)))
return amp_for_failover_flow

View File

@ -196,12 +196,12 @@ class LoadBalancerFlows(object):
)
flows.append(
network_tasks.HandleNetworkDeltas(
requires=constants.DELTAS, provides=constants.ADDED_PORTS
requires=constants.DELTAS, provides=constants.UPDATED_PORTS
)
)
flows.append(
amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS)
)
)
flows.append(

View File

@ -42,10 +42,14 @@ class MemberFlows(object):
requires=(constants.LOADBALANCER, constants.AVAILABILITY_ZONE),
provides=constants.DELTAS))
create_member_flow.add(network_tasks.HandleNetworkDeltas(
requires=constants.DELTAS, provides=constants.ADDED_PORTS))
requires=(constants.DELTAS, constants.LOADBALANCER),
provides=constants.UPDATED_PORTS))
create_member_flow.add(network_tasks.GetAmphoraeNetworkConfigs(
requires=constants.LOADBALANCER_ID,
provides=constants.AMPHORAE_NETWORK_CONFIG))
create_member_flow.add(amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)
))
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS,
constants.AMPHORAE_NETWORK_CONFIG)))
create_member_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=constants.LOADBALANCER_ID))
create_member_flow.add(database_tasks.MarkMemberActiveInDB(
@ -72,6 +76,18 @@ class MemberFlows(object):
constants.POOL_ID]))
delete_member_flow.add(database_tasks.MarkMemberPendingDeleteInDB(
requires=constants.MEMBER))
delete_member_flow.add(network_tasks.CalculateDelta(
requires=(constants.LOADBALANCER, constants.AVAILABILITY_ZONE),
provides=constants.DELTAS))
delete_member_flow.add(network_tasks.HandleNetworkDeltas(
requires=(constants.DELTAS, constants.LOADBALANCER),
provides=constants.UPDATED_PORTS))
delete_member_flow.add(network_tasks.GetAmphoraeNetworkConfigs(
requires=constants.LOADBALANCER_ID,
provides=constants.AMPHORAE_NETWORK_CONFIG))
delete_member_flow.add(amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS,
constants.AMPHORAE_NETWORK_CONFIG)))
delete_member_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=constants.LOADBALANCER_ID))
delete_member_flow.add(database_tasks.DeleteMemberInDB(
@ -183,10 +199,15 @@ class MemberFlows(object):
requires=(constants.LOADBALANCER, constants.AVAILABILITY_ZONE),
provides=constants.DELTAS))
batch_update_members_flow.add(network_tasks.HandleNetworkDeltas(
requires=constants.DELTAS, provides=constants.ADDED_PORTS))
requires=(constants.DELTAS, constants.LOADBALANCER),
provides=constants.UPDATED_PORTS))
batch_update_members_flow.add(network_tasks.GetAmphoraeNetworkConfigs(
requires=constants.LOADBALANCER_ID,
provides=constants.AMPHORAE_NETWORK_CONFIG))
batch_update_members_flow.add(
amphora_driver_tasks.AmphoraePostNetworkPlug(
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)))
requires=(constants.LOADBALANCER, constants.UPDATED_PORTS,
constants.AMPHORAE_NETWORK_CONFIG)))
# Update the Listener (this makes the changes active on the Amp)
batch_update_members_flow.add(amphora_driver_tasks.ListenersUpdate(

View File

@ -259,7 +259,7 @@ class AmphoraFinalize(BaseAmphoraTask):
class AmphoraPostNetworkPlug(BaseAmphoraTask):
"""Task to notify the amphora post network plug."""
def execute(self, amphora, ports):
def execute(self, amphora, ports, amphora_network_config):
"""Execute post_network_plug routine."""
db_amp = self.amphora_repo.get(db_apis.get_session(),
id=amphora[constants.ID])
@ -279,8 +279,9 @@ class AmphoraPostNetworkPlug(BaseAmphoraTask):
fixed_ips.append(data_models.FixedIP(
subnet=data_models.Subnet(**subnet_arg), **ip))
self.amphora_driver.post_network_plug(
db_amp, data_models.Port(network=net, fixed_ips=fixed_ips,
**port))
db_amp,
data_models.Port(network=net, fixed_ips=fixed_ips, **port),
amphora_network_config)
LOG.debug("post_network_plug called on compute instance "
"%(compute_id)s for port %(port_id)s",
@ -298,17 +299,18 @@ class AmphoraPostNetworkPlug(BaseAmphoraTask):
class AmphoraePostNetworkPlug(BaseAmphoraTask):
"""Task to notify the amphorae post network plug."""
def execute(self, loadbalancer, added_ports):
def execute(self, loadbalancer, updated_ports, amphorae_network_config):
"""Execute post_network_plug routine."""
amp_post_plug = AmphoraPostNetworkPlug()
db_lb = self.loadbalancer_repo.get(
db_apis.get_session(), id=loadbalancer[constants.LOADBALANCER_ID])
for amphora in db_lb.amphorae:
if amphora.id in added_ports:
if amphora.id in updated_ports:
amp_post_plug.execute(amphora.to_dict(),
added_ports[amphora.id])
updated_ports[amphora.id],
amphorae_network_config[amphora.id])
def revert(self, result, loadbalancer, added_ports, *args, **kwargs):
def revert(self, result, loadbalancer, updated_ports, *args, **kwargs):
"""Handle a failed post network plug."""
if isinstance(result, failure.Failure):
return

View File

@ -55,17 +55,15 @@ class CalculateAmphoraDelta(BaseNetworkTask):
default_provides = constants.DELTA
def execute(self, loadbalancer, amphora, availability_zone,
vrrp_port=None):
# TODO(gthiemonge) ensure we no longer need vrrp_port
def execute(self, loadbalancer, amphora, availability_zone):
LOG.debug("Calculating network delta for amphora id: %s",
amphora.get(constants.ID))
if vrrp_port is None:
vrrp_port = self.network_driver.get_port(
amphora[constants.VRRP_PORT_ID])
vrrp_port_network_id = vrrp_port.network_id
else:
vrrp_port_network_id = vrrp_port[constants.NETWORK_ID]
vip_subnet_to_net_map = {
loadbalancer[constants.VIP_SUBNET_ID]:
loadbalancer[constants.VIP_NETWORK_ID]
}
# Figure out what networks we want
# seed with lb network(s)
@ -75,33 +73,88 @@ class CalculateAmphoraDelta(BaseNetworkTask):
availability_zone.get(constants.MANAGEMENT_NETWORK)]
else:
management_nets = CONF.controller_worker.amp_boot_network_list
desired_network_ids = {vrrp_port_network_id}.union(management_nets)
db_lb = self.loadbalancer_repo.get(
db_apis.get_session(), id=loadbalancer[constants.LOADBALANCER_ID])
for pool in db_lb.pools:
member_networks = [
self.network_driver.get_subnet(member.subnet_id).network_id
for member in pool.members
if member.subnet_id
]
desired_network_ids.update(member_networks)
desired_subnet_to_net_map = {}
for mgmt_net_id in management_nets:
for subnet_id in self.network_driver.get_network(
mgmt_net_id).subnets:
desired_subnet_to_net_map[subnet_id] = mgmt_net_id
desired_subnet_to_net_map.update(vip_subnet_to_net_map)
for pool in db_lb.pools:
for member in pool.members:
if (member.subnet_id and
member.provisioning_status !=
constants.PENDING_DELETE):
member_network = self.network_driver.get_subnet(
member.subnet_id).network_id
desired_subnet_to_net_map[member.subnet_id] = (
member_network)
desired_network_ids = set(desired_subnet_to_net_map.values())
desired_subnet_ids = set(desired_subnet_to_net_map)
# Calculate Network deltas
nics = self.network_driver.get_plugged_networks(
amphora[constants.COMPUTE_ID])
# assume we don't have two nics in the same network
actual_network_nics = dict((nic.network_id, nic) for nic in nics)
# we don't have two nics in the same network
network_to_nic_map = {nic.network_id: nic for nic in nics}
del_ids = set(actual_network_nics) - desired_network_ids
delete_nics = list(
n_data_models.Interface(network_id=net_id) for net_id in del_ids)
plugged_network_ids = set(network_to_nic_map)
del_ids = plugged_network_ids - desired_network_ids
delete_nics = [n_data_models.Interface(
network_id=net_id,
port_id=network_to_nic_map[net_id].port_id)
for net_id in del_ids]
add_ids = desired_network_ids - plugged_network_ids
add_nics = [n_data_models.Interface(
network_id=add_net_id,
fixed_ips=[
n_data_models.FixedIP(
subnet_id=subnet_id)
for subnet_id, net_id in desired_subnet_to_net_map.items()
if net_id == add_net_id])
for add_net_id in add_ids]
# Calculate member Subnet deltas
plugged_subnets = {}
for nic in network_to_nic_map.values():
for fixed_ip in nic.fixed_ips or []:
plugged_subnets[fixed_ip.subnet_id] = nic.network_id
plugged_subnet_ids = set(plugged_subnets)
del_subnet_ids = plugged_subnet_ids - desired_subnet_ids
add_subnet_ids = desired_subnet_ids - plugged_subnet_ids
def _subnet_updates(subnet_ids, subnets):
updates = []
for s in subnet_ids:
network_id = subnets[s]
nic = network_to_nic_map.get(network_id)
port_id = nic.port_id if nic else None
updates.append({
constants.SUBNET_ID: s,
constants.NETWORK_ID: network_id,
constants.PORT_ID: port_id
})
return updates
add_subnets = _subnet_updates(add_subnet_ids,
desired_subnet_to_net_map)
del_subnets = _subnet_updates(del_subnet_ids,
plugged_subnets)
add_ids = desired_network_ids - set(actual_network_nics)
add_nics = list(n_data_models.Interface(
network_id=net_id) for net_id in add_ids)
delta = n_data_models.Delta(
amphora_id=amphora[constants.ID],
compute_id=amphora[constants.COMPUTE_ID],
add_nics=add_nics, delete_nics=delete_nics)
add_nics=add_nics, delete_nics=delete_nics,
add_subnets=add_subnets,
delete_subnets=del_subnets)
return delta.to_dict(recurse=True)
@ -256,29 +309,92 @@ class HandleNetworkDelta(BaseNetworkTask):
Plug or unplug networks based on delta
"""
def _fill_port_info(self, port):
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)
def execute(self, amphora, delta):
"""Handle network plugging based off deltas."""
added_ports = {}
added_ports[amphora[constants.ID]] = []
db_amp = self.amphora_repo.get(db_apis.get_session(),
id=amphora.get(constants.ID))
updated_ports = {}
for nic in delta[constants.ADD_NICS]:
subnet_id = nic[constants.FIXED_IPS][0][constants.SUBNET_ID]
interface = self.network_driver.plug_network(
delta[constants.COMPUTE_ID], nic[constants.NETWORK_ID])
db_amp.compute_id, nic[constants.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[amphora[constants.ID]].append(port.to_dict(
recurse=True))
# nova may plugged undesired subnets (it plugs one of the subnets
# of the network), we can safely unplug the subnets we don't need,
# the desired subnet will be added in the 'ADD_SUBNETS' loop.
extra_subnets = [
fixed_ip.subnet_id
for fixed_ip in port.fixed_ips
if fixed_ip.subnet_id != subnet_id]
for subnet_id in extra_subnets:
port = self.network_driver.unplug_fixed_ip(
port_id=interface.port_id, subnet_id=subnet_id)
self._fill_port_info(port)
updated_ports[port.network_id] = port.to_dict(recurse=True)
for update in delta.get(constants.ADD_SUBNETS, []):
network_id = update[constants.NETWORK_ID]
# Get already existing port from Deltas or
# newly created port from updated_ports dict
port_id = (update[constants.PORT_ID] or
updated_ports[network_id][constants.ID])
subnet_id = update[constants.SUBNET_ID]
# Avoid duplicated subnets
has_subnet = False
if network_id in updated_ports:
has_subnet = any(
fixed_ip[constants.SUBNET_ID] == subnet_id
for fixed_ip in updated_ports[network_id][
constants.FIXED_IPS])
if not has_subnet:
port = self.network_driver.plug_fixed_ip(
port_id=port_id, subnet_id=subnet_id)
self._fill_port_info(port)
updated_ports[network_id] = (
port.to_dict(recurse=True))
for update in delta.get(constants.DELETE_SUBNETS, []):
network_id = update[constants.NETWORK_ID]
port_id = update[constants.PORT_ID]
subnet_id = update[constants.SUBNET_ID]
port = self.network_driver.unplug_fixed_ip(
port_id=port_id, subnet_id=subnet_id)
self._fill_port_info(port)
# In neutron, when removing an ipv6 subnet (with slaac) from a
# port, it just ignores it.
# https://bugs.launchpad.net/neutron/+bug/1945156
# When it happens, don't add the port to the updated_ports dict
has_subnet = any(
fixed_ip.subnet_id == subnet_id
for fixed_ip in port.fixed_ips)
if not has_subnet:
updated_ports[network_id] = (
port.to_dict(recurse=True))
for nic in delta[constants.DELETE_NICS]:
network_id = nic[constants.NETWORK_ID]
try:
self.network_driver.unplug_network(
delta[constants.COMPUTE_ID], nic[constants.NETWORK_ID])
db_amp.compute_id, network_id)
except base.NetworkNotFound:
LOG.debug("Network %d not found ", nic[constants.NETWORK_ID])
LOG.debug("Network %s not found", network_id)
except Exception:
LOG.exception("Unable to unplug network")
return added_ports
port_id = nic[constants.PORT_ID]
try:
self.network_driver.delete_port(port_id)
except Exception:
LOG.exception("Unable to delete the port")
updated_ports.pop(network_id, None)
return {amphora[constants.ID]: list(updated_ports.values())}
def revert(self, result, amphora, delta, *args, **kwargs):
"""Handle a network plug or unplug failures."""
@ -297,7 +413,14 @@ class HandleNetworkDelta(BaseNetworkTask):
self.network_driver.unplug_network(delta[constants.COMPUTE_ID],
nic[constants.NETWORK_ID])
except Exception:
pass
LOG.exception("Unable to unplug network %s",
nic[constants.NETWORK_ID])
port_id = nic[constants.PORT_ID]
try:
self.network_driver.delete_port(port_id)
except Exception:
LOG.exception("Unable to delete port %s", port_id)
class HandleNetworkDeltas(BaseNetworkTask):
@ -307,50 +430,47 @@ class HandleNetworkDeltas(BaseNetworkTask):
networks based on delta
"""
def execute(self, deltas):
def execute(self, deltas, loadbalancer):
"""Handle network plugging based off deltas."""
added_ports = {}
db_lb = self.loadbalancer_repo.get(
db_apis.get_session(), id=loadbalancer[constants.LOADBALANCER_ID])
amphorae = {amp.id: amp for amp in db_lb.amphorae}
updated_ports = {}
handle_delta = HandleNetworkDelta()
for amp_id, delta in deltas.items():
added_ports[amp_id] = []
for nic in delta[constants.ADD_NICS]:
interface = self.network_driver.plug_network(
delta[constants.COMPUTE_ID], nic[constants.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.to_dict(recurse=True))
for nic in delta[constants.DELETE_NICS]:
try:
self.network_driver.unplug_network(
delta[constants.COMPUTE_ID],
nic[constants.NETWORK_ID])
except base.NetworkNotFound:
LOG.debug("Network %d not found ",
nic[constants.NETWORK_ID])
except Exception:
LOG.exception("Unable to unplug network")
return added_ports
ret = handle_delta.execute(amphorae[amp_id].to_dict(), delta)
updated_ports.update(ret)
return updated_ports
def revert(self, result, deltas, *args, **kwargs):
"""Handle a network plug or unplug failures."""
if isinstance(result, failure.Failure):
return
if not deltas:
return
for amp_id, delta in deltas.items():
LOG.warning("Unable to plug networks for amp id %s",
delta[constants.AMPHORA_ID])
if not delta:
return
for nic in delta[constants.ADD_NICS]:
try:
self.network_driver.unplug_network(
delta[constants.COMPUTE_ID],
nic[constants.NETWORK_ID])
except base.NetworkNotFound:
pass
except Exception:
LOG.exception("Unable to unplug network %s",
nic[constants.NETWORK_ID])
port_id = nic[constants.PORT_ID]
try:
self.network_driver.delete_port(port_id)
except Exception:
LOG.exception("Unable to delete port %s", port_id)
class PlugVIP(BaseNetworkTask):

View File

@ -169,31 +169,52 @@ class AbstractNetworkDriver(object, metaclass=abc.ABCMeta):
"""
@abc.abstractmethod
def plug_network(self, compute_id, network_id, ip_address=None):
def plug_network(self, compute_id, network_id):
"""Connects an existing amphora to an existing network.
:param compute_id: id of an amphora in the compute service
:param network_id: id of a network
:param ip_address: ip address to attempt to be assigned to interface
:return: octavia.network.data_models.Interface instance
:raises: PlugNetworkException, AmphoraNotFound, NetworkNotFound
"""
@abc.abstractmethod
def unplug_network(self, compute_id, network_id, ip_address=None):
def unplug_network(self, compute_id, network_id):
"""Disconnects an existing amphora from an existing network.
If ip_address is not specificed, all the interfaces plugged on
If ip_address is not specified, all the interfaces plugged on
network_id should be unplugged.
:param compute_id: id of an amphora in the compute service
:param network_id: id of a network
:param ip_address: specific ip_address to unplug
:return: None
:raises: UnplugNetworkException, AmphoraNotFound, NetworkNotFound,
NetworkException
"""
@abc.abstractmethod
def plug_fixed_ip(self, port_id, subnet_id, ip_address=None):
"""Plug a fixed ip to an existing port.
If ip_address is not specified, one will be auto-assigned.
:param port_id: id of a port to add a fixed ip
:param subnet_id: id of a subnet
:param ip_address: specific ip_address to add
:return: octavia.network.data_models.Port
:raises: NetworkException, PortNotFound
"""
@abc.abstractmethod
def unplug_fixed_ip(self, port_id, subnet_id):
"""Unplug a fixed ip from an existing port.
:param port_id: id of a port to remove the fixed ip from
:param subnet_id: id of a subnet
:return: octavia.network.data_models.Port
:raises: NetworkException, PortNotFound
"""
@abc.abstractmethod
def get_plugged_networks(self, compute_id):
"""Retrieves the current plugged networking configuration.

View File

@ -29,11 +29,14 @@ class Interface(data_models.BaseDataModel):
class Delta(data_models.BaseDataModel):
def __init__(self, amphora_id=None, compute_id=None,
add_nics=None, delete_nics=None):
add_nics=None, delete_nics=None,
add_subnets=None, delete_subnets=None):
self.compute_id = compute_id
self.amphora_id = amphora_id
self.add_nics = add_nics
self.delete_nics = delete_nics
self.add_subnets = add_subnets
self.delete_subnets = delete_subnets
class Network(data_models.BaseDataModel):

View File

@ -589,11 +589,10 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
load_balancer.amphorae):
self.unplug_aap_port(vip, amphora, subnet)
def plug_network(self, compute_id, network_id, ip_address=None):
def plug_network(self, compute_id, network_id):
try:
interface = self.compute.attach_network_or_port(
compute_id=compute_id, network_id=network_id,
ip_address=ip_address)
compute_id=compute_id, network_id=network_id)
except exceptions.NotFound as e:
if 'Instance' in str(e):
raise base.AmphoraNotFound(str(e))
@ -610,15 +609,14 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
return self._nova_interface_to_octavia_interface(compute_id, interface)
def unplug_network(self, compute_id, network_id, ip_address=None):
def unplug_network(self, compute_id, network_id):
interfaces = self.get_plugged_networks(compute_id)
if not interfaces:
msg = ('Amphora with compute id {compute_id} does not have any '
'plugged networks').format(compute_id=compute_id)
raise base.NetworkNotFound(msg)
unpluggers = self._get_interfaces_to_unplug(interfaces, network_id,
ip_address=ip_address)
unpluggers = self._get_interfaces_to_unplug(interfaces, network_id)
removed_port_ids = set()
for index, unplugger in enumerate(unpluggers):
self.compute.detach_port(

View File

@ -275,3 +275,35 @@ class BaseNeutronDriver(base.AbstractNetworkDriver):
def get_network_ip_availability(self, network):
return self._get_resource('network_ip_availability', network.id)
def plug_fixed_ip(self, port_id, subnet_id, ip_address=None):
port = self.get_port(port_id).to_dict(recurse=True)
fixed_ips = port['fixed_ips']
new_fixed_ip_dict = {'subnet_id': subnet_id}
if ip_address:
new_fixed_ip_dict['ip_address'] = ip_address
fixed_ips.append(new_fixed_ip_dict)
body = {'port': {'fixed_ips': fixed_ips}}
try:
updated_port = self.neutron_client.update_port(port_id, body)
return utils.convert_port_dict_to_model(updated_port)
except Exception as e:
raise base.NetworkException(str(e))
def unplug_fixed_ip(self, port_id, subnet_id):
port = self.get_port(port_id)
fixed_ips = [
fixed_ip.to_dict()
for fixed_ip in port.fixed_ips
if fixed_ip.subnet_id != subnet_id
]
body = {'port': {'fixed_ips': fixed_ips}}
try:
updated_port = self.neutron_client.update_port(port_id, body)
return utils.convert_port_dict_to_model(updated_port)
except Exception as e:
raise base.NetworkException(str(e))

View File

@ -21,8 +21,12 @@ from octavia.network import data_models as network_models
LOG = logging.getLogger(__name__)
_PLUGGED_NETWORKS = {}
_PORTS = {}
class NoopManager(object):
def __init__(self):
super().__init__()
self.networkconfigconfig = {}
@ -110,33 +114,40 @@ class NoopManager(object):
vip.ip_address)] = (vip, amphora, subnet,
'unplug_aap_port')
def plug_network(self, compute_id, network_id, ip_address=None):
def plug_network(self, compute_id, network_id):
LOG.debug("Network %s no-op, plug_network compute_id %s, network_id "
"%s, ip_address %s", self.__class__.__name__, compute_id,
network_id, ip_address)
self.networkconfigconfig[(compute_id, network_id, ip_address)] = (
compute_id, network_id, ip_address, 'plug_network')
return network_models.Interface(
"%s", self.__class__.__name__, compute_id,
network_id)
self.networkconfigconfig[(compute_id, network_id)] = (
compute_id, network_id, 'plug_network')
interface = network_models.Interface(
id=uuidutils.generate_uuid(),
compute_id=compute_id,
network_id=network_id,
fixed_ips=[],
port_id=uuidutils.generate_uuid()
)
_PORTS[interface.port_id] = network_models.Port(
id=interface.port_id,
network_id=network_id)
_PLUGGED_NETWORKS[(network_id, compute_id)] = interface
return interface
def unplug_network(self, compute_id, network_id, ip_address=None):
def unplug_network(self, compute_id, network_id):
LOG.debug("Network %s no-op, unplug_network compute_id %s, "
"network_id %s",
self.__class__.__name__, compute_id, network_id)
self.networkconfigconfig[(compute_id, network_id, ip_address)] = (
compute_id, network_id, ip_address, 'unplug_network')
self.networkconfigconfig[(compute_id, network_id)] = (
compute_id, network_id, 'unplug_network')
_PLUGGED_NETWORKS.pop((network_id, compute_id), None)
def get_plugged_networks(self, compute_id):
LOG.debug("Network %s no-op, get_plugged_networks amphora_id %s",
self.__class__.__name__, compute_id)
self.networkconfigconfig[compute_id] = (
compute_id, 'get_plugged_networks')
return []
return [pn for pn in _PLUGGED_NETWORKS.values()
if pn.compute_id == compute_id]
def update_vip(self, loadbalancer, for_delete=False):
LOG.debug("Network %s no-op, update_vip loadbalancer %s "
@ -175,7 +186,10 @@ class NoopManager(object):
LOG.debug("Port %s no-op, get_port port_id %s",
self.__class__.__name__, port_id)
self.networkconfigconfig[port_id] = (port_id, 'get_port')
return network_models.Port(id=uuidutils.generate_uuid())
if port_id in _PORTS:
return _PORTS[port_id]
return network_models.Port(id=uuidutils.generate_uuid(),
network_id=uuidutils.generate_uuid())
def get_network_by_name(self, network_name):
LOG.debug("Network %s no-op, get_network_by_name network_name %s",
@ -337,6 +351,27 @@ class NoopManager(object):
admin_state_up=admin_state_up, fixed_ips=fixed_ip_obj_list,
qos_policy_id=qos_policy_id, security_group_ids=security_group_ids)
def plug_fixed_ip(self, port_id, subnet_id, ip_address=None):
LOG.debug("Network %s no-op, plug_fixed_ip port_id %s, subnet_id "
"%s, ip_address %s", self.__class__.__name__, port_id,
subnet_id, ip_address)
self.networkconfigconfig[(port_id, subnet_id)] = (
port_id, subnet_id, ip_address, 'plug_fixed_ip')
port = network_models.Port(id=port_id,
network_id=uuidutils.generate_uuid())
_PORTS[port.id] = port
return port
def unplug_fixed_ip(self, port_id, subnet_id):
LOG.debug("Network %s no-op, unplug_fixed_ip port_id %s, subnet_id "
"%s", self.__class__.__name__, port_id,
subnet_id)
self.networkconfigconfig[(port_id, subnet_id)] = (
port_id, subnet_id, 'unplug_fixed_ip')
return _PORTS.pop(port_id, None)
class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
def __init__(self):
@ -355,15 +390,14 @@ class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
def unplug_vip(self, loadbalancer, vip):
self.driver.unplug_vip(loadbalancer, vip)
def plug_network(self, amphora_id, network_id, ip_address=None):
return self.driver.plug_network(amphora_id, network_id, ip_address)
def plug_network(self, compute_id, network_id):
return self.driver.plug_network(compute_id, network_id)
def unplug_network(self, amphora_id, network_id, ip_address=None):
self.driver.unplug_network(amphora_id, network_id,
ip_address=ip_address)
def unplug_network(self, compute_id, network_id):
self.driver.unplug_network(compute_id, network_id)
def get_plugged_networks(self, amphora_id):
return self.driver.get_plugged_networks(amphora_id)
def get_plugged_networks(self, compute_id):
return self.driver.get_plugged_networks(compute_id)
def update_vip(self, loadbalancer, for_delete=False):
self.driver.update_vip(loadbalancer, for_delete)
@ -437,3 +471,9 @@ class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
return self.driver.create_port(
network_id, name, fixed_ips, secondary_ips, security_group_ids,
admin_state_up, qos_policy_id)
def plug_fixed_ip(self, port_id, subnet_id, ip_address=None):
return self.driver.plug_fixed_ip(port_id, subnet_id, ip_address)
def unplug_fixed_ip(self, port_id, subnet_id):
return self.driver.unplug_fixed_ip(port_id, subnet_id)

View File

@ -969,10 +969,12 @@ class TestServerTestCase(base.TestCase):
@mock.patch('subprocess.check_output')
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'plug.Plug._netns_interface_exists')
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'plug.Plug._netns_interface_by_mac')
@mock.patch('os.path.isfile')
def _test_plug_network(self, distro, mock_isfile, mock_int_exists,
mock_check_output, mock_netns, mock_pyroute2,
mock_os_chmod):
def _test_plug_network(self, distro, mock_isfile, mock_int_by_mac,
mock_int_exists, mock_check_output, mock_netns,
mock_pyroute2, mock_os_chmod):
mock_ipr = mock.MagicMock()
mock_ipr_instance = mock.MagicMock()
mock_ipr_instance.link_lookup.side_effect = [
@ -991,24 +993,9 @@ class TestServerTestCase(base.TestCase):
netns_handle.get_links.return_value = [0] * test_int_num
mock_isfile.return_value = True
test_int_num = str(test_int_num)
mock_check_output.return_value = b"1\n2\n3\n"
# Interface already plugged
mock_int_exists.return_value = True
if distro == consts.UBUNTU:
rv = self.ubuntu_app.post('/' + api_server.VERSION +
"/plug/network",
content_type='application/json',
data=jsonutils.dumps(port_info))
elif distro == consts.CENTOS:
rv = self.centos_app.post('/' + api_server.VERSION +
"/plug/network",
content_type='application/json',
data=jsonutils.dumps(port_info))
self.assertEqual(409, rv.status_code)
self.assertEqual(dict(message="Interface already exists"),
jsonutils.loads(rv.data.decode('utf-8')))
mock_int_exists.return_value = False
test_int_num = str(test_int_num)
# No interface at all
file_name = '/sys/bus/pci/rescan'
@ -1674,6 +1661,9 @@ class TestServerTestCase(base.TestCase):
consts.GATEWAY: '203.0.113.1',
consts.TABLE: 1,
consts.FLAGS: [consts.ONLINK]
}, {
consts.DST: '203.0.113.0/24',
consts.SCOPE: 'link'
}, {
consts.DST: '203.0.113.0/24',
consts.PREFSRC: '203.0.113.2',
@ -1798,6 +1788,9 @@ class TestServerTestCase(base.TestCase):
consts.GATEWAY: '203.0.113.1',
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1
}, {
consts.DST: '203.0.113.0/24',
consts.SCOPE: 'link'
}, {
consts.DST: '203.0.113.0/24',
consts.PREFSRC: '203.0.113.2',
@ -2038,6 +2031,9 @@ class TestServerTestCase(base.TestCase):
consts.GATEWAY: '2001:db8::1',
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1
}, {
consts.DST: '2001:0db8::/32',
consts.SCOPE: 'link'
}, {
consts.DST: '2001:0db8::/32',
consts.PREFSRC: '2001:0db8::2',
@ -2159,6 +2155,9 @@ class TestServerTestCase(base.TestCase):
consts.GATEWAY: '2001:db8::1',
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1
}, {
consts.DST: '2001:db8::/32',
consts.SCOPE: 'link'
}, {
consts.DST: '2001:db8::/32',
consts.PREFSRC: '2001:db8::2',

View File

@ -96,6 +96,20 @@ class TestOSUtils(base.TestCase):
package_name))
self.assertEqual(centos_cmd, returned_centos_cmd)
@mock.patch('octavia.amphorae.backends.utils.interface_file.'
'InterfaceFile')
def test_write_interface_file(self, mock_interface_file):
mock_interface = mock.MagicMock()
mock_interface_file.return_value = mock_interface
self.ubuntu_os_util.write_interface_file('eth1',
'192.0.2.2', 16)
mock_interface_file.assert_called_once_with(
name='eth1',
addresses=[{"address": "192.0.2.2", "prefixlen": 16}])
mock_interface.write.assert_called_once()
@mock.patch('octavia.amphorae.backends.utils.interface_file.'
'VIPInterfaceFile')
def test_write_vip_interface_file(self, mock_vip_interface_file):
@ -142,6 +156,7 @@ class TestOSUtils(base.TestCase):
mtu=MTU,
vrrp_ip=None,
host_routes=host_routes,
fixed_ips=None,
topology="SINGLE")
mock_vip_interface_file.return_value.write.assert_called_once()
@ -167,6 +182,7 @@ class TestOSUtils(base.TestCase):
mtu=MTU,
vrrp_ip=None,
host_routes=host_routes,
fixed_ips=None,
topology="SINGLE")
@mock.patch('octavia.amphorae.backends.utils.interface_file.'

View File

@ -193,16 +193,166 @@ class TestPlug(base.TestCase):
mock_webob.Response.assert_any_call(json={'message': 'Invalid VIP'},
status=400)
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
"BaseOS.write_interface_file")
def test_plug_lo(self, mock_write_interface):
m = mock.mock_open()
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
self.test_plug.plug_lo()
mock_write_interface.assert_called_once_with(interface='lo',
ip_address='127.0.0.1',
prefixlen=8)
@mock.patch('pyroute2.NetNS', create=True)
def test__netns_interface_exists(self, mock_netns):
netns_handle = mock_netns.return_value.__enter__.return_value
netns_handle.get_links.return_value = [{
'attrs': [['IFLA_ADDRESS', '123']]}]
'attrs': [['IFLA_ADDRESS', '123'],
['IFLA_IFNAME', 'eth0']]}]
# Interface is found in netns
self.assertTrue(self.test_plug._netns_interface_exists('123'))
# Interface is not found in netns
self.assertFalse(self.test_plug._netns_interface_exists('321'))
@mock.patch.object(plug, "webob")
@mock.patch('octavia.amphorae.backends.agent.api_server.plug.Plug.'
'_netns_interface_exists', return_value=False)
@mock.patch('octavia.amphorae.backends.agent.api_server.plug.Plug.'
'_interface_by_mac', return_value=FAKE_INTERFACE)
@mock.patch('pyroute2.IPRoute', create=True)
@mock.patch('pyroute2.NetNS', create=True)
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
"BaseOS.write_port_interface_file")
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
"BaseOS.bring_interface_up")
def test_plug_network(self, mock_if_up, mock_write_port_interface,
mock_netns, mock_iproute,
mock_by_mac, mock_interface_exists, mock_webob):
fixed_ips = [
{'ip_address': FAKE_IP_IPV4,
'subnet_cidr': FAKE_CIDR_IPV4,
'gateway': FAKE_GATEWAY_IPV4,
'host_routes': [
{'destination': '192.0.2.0/24',
'nexthop': '192.0.2.254'}]
}]
mtu = 1400
m = mock.mock_open()
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
self.test_plug.plug_network(FAKE_MAC_ADDRESS, fixed_ips, 1400)
mock_write_port_interface.assert_called_once_with(
interface='eth0', fixed_ips=fixed_ips, mtu=mtu)
mock_if_up.assert_called_once_with('eth0', 'network')
mock_webob.Response.assert_any_call(
json={'message': 'OK',
'details': 'Plugged on interface eth0'},
status=202)
@mock.patch.object(plug, "webob")
@mock.patch('octavia.amphorae.backends.agent.api_server.plug.Plug.'
'_netns_interface_exists', return_value=True)
@mock.patch('octavia.amphorae.backends.agent.api_server.plug.Plug.'
'_netns_interface_by_mac', return_value=FAKE_INTERFACE)
@mock.patch('pyroute2.NetNS', create=True)
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
"BaseOS.write_port_interface_file")
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
"BaseOS.bring_interface_up")
def test_plug_network_existing_interface(self, mock_if_up,
mock_write_port_interface,
mock_netns, mock_by_mac,
mock_interface_exists,
mock_webob):
fixed_ips = [
{'ip_address': FAKE_IP_IPV4,
'subnet_cidr': FAKE_CIDR_IPV4,
'gateway': FAKE_GATEWAY_IPV4,
'host_routes': [
{'destination': '192.0.2.0/24',
'nexthop': '192.0.2.254'}]
}, {'ip_address': FAKE_IP_IPV6,
'subnet_cidr': FAKE_CIDR_IPV6,
'gateway': FAKE_GATEWAY_IPV6,
'host_routes': [
{'destination': '2001:db8::/64',
'nexthop': '2001:db8::ffff'}]
}]
mtu = 1400
m = mock.mock_open()
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
self.test_plug.plug_network(FAKE_MAC_ADDRESS, fixed_ips, 1400)
mock_write_port_interface.assert_called_once_with(
interface=FAKE_INTERFACE, fixed_ips=fixed_ips, mtu=mtu)
mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'network')
mock_webob.Response.assert_any_call(
json={'message': 'OK',
'details': 'Updated existing interface {}'.format(
FAKE_INTERFACE)},
status=202)
@mock.patch.object(plug, "webob")
@mock.patch('octavia.amphorae.backends.agent.api_server.plug.Plug.'
'_netns_interface_exists', return_value=True)
@mock.patch('octavia.amphorae.backends.agent.api_server.plug.Plug.'
'_netns_interface_by_mac', return_value=FAKE_INTERFACE)
@mock.patch('pyroute2.NetNS', create=True)
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
"BaseOS.write_vip_interface_file")
@mock.patch("octavia.amphorae.backends.agent.api_server.osutils."
"BaseOS.bring_interface_up")
def test_plug_network_on_vip(
self, mock_if_up, mock_write_vip_interface,
mock_netns, mock_by_mac, mock_interface_exists, mock_webob):
fixed_ips = [
{'ip_address': FAKE_IP_IPV4,
'subnet_cidr': FAKE_CIDR_IPV4,
'gateway': FAKE_GATEWAY_IPV4,
'host_routes': [
{'destination': '192.0.2.128/25',
'nexthop': '192.0.2.100'}]
}, {'ip_address': FAKE_IP_IPV6,
'subnet_cidr': FAKE_CIDR_IPV6,
'gateway': FAKE_GATEWAY_IPV6,
'host_routes': [
{'destination': '2001:db8::/64',
'nexthop': '2001:db8::ffff'}]
}]
mtu = 1400
vip_net_info = {
'vip': '192.0.2.10',
'subnet_cidr': '192.0.2.0/25',
'vrrp_ip': '192.0.2.11',
'gateway': '192.0.2.1',
'host_routes': []
}
m = mock.mock_open()
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
self.test_plug.plug_network(FAKE_MAC_ADDRESS, fixed_ips, mtu=1400,
vip_net_info=vip_net_info)
mock_write_vip_interface.assert_called_once_with(
interface=FAKE_INTERFACE,
vip=vip_net_info['vip'],
ip_version=4,
prefixlen=25,
gateway=vip_net_info['gateway'],
vrrp_ip=vip_net_info['vrrp_ip'],
host_routes=[],
fixed_ips=fixed_ips, mtu=mtu)
mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'vip')
mock_webob.Response.assert_any_call(
json={'message': 'OK',
'details': 'Updated existing interface {}'.format(
FAKE_INTERFACE)},
status=202)

View File

@ -74,6 +74,10 @@ class TestInterfaceFile(base.TestCase):
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1
},
{
consts.DST: cidr.exploded,
consts.SCOPE: 'link'
},
{
consts.DST: cidr.exploded,
consts.PREFSRC: VIP_ADDRESS,
@ -192,6 +196,10 @@ class TestInterfaceFile(base.TestCase):
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1
},
{
consts.DST: cidr.exploded,
consts.SCOPE: 'link'
},
{
consts.DST: cidr.exploded,
consts.PREFSRC: VIP_ADDRESS,
@ -281,6 +289,9 @@ class TestInterfaceFile(base.TestCase):
],
consts.ROUTES: [
{
consts.DST: cidr.exploded,
consts.SCOPE: 'link',
}, {
consts.DST: cidr.exploded,
consts.PREFSRC: VIP_ADDRESS,
consts.SCOPE: 'link',
@ -360,13 +371,10 @@ class TestInterfaceFile(base.TestCase):
{
consts.DST: "0.0.0.0/0",
consts.GATEWAY: GATEWAY,
consts.FLAGS: [consts.ONLINK],
},
{
consts.DST: "0.0.0.0/0",
consts.GATEWAY: GATEWAY,
consts.FLAGS: [consts.ONLINK],
consts.TABLE: 1
consts.FLAGS: [consts.ONLINK]
}, {
consts.DST: SUBNET_CIDR,
consts.SCOPE: 'link'
}
],
consts.RULES: [],
@ -447,6 +455,10 @@ class TestInterfaceFile(base.TestCase):
consts.TABLE: 1,
consts.FLAGS: [consts.ONLINK]
},
{
consts.DST: cidr.exploded,
consts.SCOPE: 'link',
},
{
consts.DST: cidr.exploded,
consts.PREFSRC: VIP_ADDRESS,

View File

@ -94,6 +94,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.fixed_ip = mock.MagicMock()
self.fixed_ip.ip_address = '198.51.100.5'
self.fixed_ip.subnet.cidr = '198.51.100.0/24'
self.fixed_ip.subnet.gateway_ip = FAKE_GATEWAY
self.network = network_models.Network(mtu=FAKE_MTU)
self.port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[self.fixed_ip],
@ -116,6 +117,9 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
constants.REQ_READ_TIMEOUT: 2,
constants.CONN_MAX_RETRIES: 3,
constants.CONN_RETRY_INTERVAL: 4}
self.amp_net_config = network_models.AmphoraNetworkConfig(
vip_subnet=self.lb.vip.subnet_id
)
@mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.'
'HaproxyAmphoraLoadBalancerDriver._process_secret')
@ -599,6 +603,13 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
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().vip_subnet.to_dict.return_value = {
'cidr': FAKE_CIDR,
'gateway_ip': FAKE_GATEWAY,
'host_routes': [
hr.to_dict(recurse=True)
for hr in 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.clients[API_VERSION].plug_vip.assert_called_once_with(
@ -609,7 +620,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[],
network=self.network)
self.driver.post_network_plug(self.amp, port)
self.driver.post_network_plug(self.amp, port, self.amp_net_config)
self.driver.clients[API_VERSION].plug_network.assert_called_once_with(
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[],
@ -618,12 +629,13 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.driver.clients[API_VERSION].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.amp_net_config)
self.driver.clients[API_VERSION].plug_network.assert_called_once_with(
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[dict(ip_address='198.51.100.5',
subnet_cidr='198.51.100.0/24',
host_routes=[])],
host_routes=[],
gateway=FAKE_GATEWAY)],
mtu=FAKE_MTU))
def test_post_network_plug_with_host_routes(self):
@ -639,6 +651,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
network_models.HostRoute(destination=DEST2,
nexthop=NEXTHOP)]
subnet = network_models.Subnet(id=SUBNET_ID, cidr=SUBNET_CIDR,
gateway_ip=FAKE_GATEWAY,
ip_version=4, host_routes=host_routes)
fixed_ips = [
network_models.FixedIP(subnet_id=subnet.id, ip_address=FIXED_IP1,
@ -649,12 +662,14 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=fixed_ips,
network=self.network)
self.driver.post_network_plug(self.amp, port)
self.driver.post_network_plug(self.amp, port, self.amp_net_config)
expected_fixed_ips = [
{'ip_address': FIXED_IP1, 'subnet_cidr': SUBNET_CIDR,
'gateway': FAKE_GATEWAY,
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
{'destination': DEST2, 'nexthop': NEXTHOP}]},
{'ip_address': FIXED_IP2, 'subnet_cidr': SUBNET_CIDR,
'gateway': FAKE_GATEWAY,
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
{'destination': DEST2, 'nexthop': NEXTHOP}]}
]

View File

@ -42,7 +42,8 @@ FAKE_IPV6 = '2001:db8::cafe'
FAKE_IPV6_LLA = 'fe80::00ff:fe00:cafe'
FAKE_PEM_FILENAME = "file_name"
FAKE_UUID_1 = uuidutils.generate_uuid()
FAKE_VRRP_IP = '10.1.0.1'
FAKE_VRRP_IP = '192.0.2.5'
FAKE_VIP_SUBNET = '192.0.2.0/24'
FAKE_MAC_ADDRESS = '123'
FAKE_MTU = 1450
FAKE_MEMBER_IP_PORT_NAME_1 = "10.0.0.10:1003"
@ -94,6 +95,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.fixed_ip = mock.MagicMock()
self.fixed_ip.ip_address = '198.51.100.5'
self.fixed_ip.subnet.cidr = '198.51.100.0/24'
self.fixed_ip.subnet.gateway_ip = FAKE_GATEWAY
self.network = network_models.Network(mtu=FAKE_MTU)
self.port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[self.fixed_ip],
@ -116,6 +118,11 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
constants.REQ_READ_TIMEOUT: 2,
constants.CONN_MAX_RETRIES: 3,
constants.CONN_RETRY_INTERVAL: 4}
self.amp_net_config = network_models.AmphoraNetworkConfig(
vip_subnet=network_models.Subnet(
id=self.lb.vip.subnet_id,
cidr=FAKE_VIP_SUBNET,
host_routes=[]))
@mock.patch('octavia.amphorae.drivers.haproxy.rest_api_driver.'
'HaproxyAmphoraLoadBalancerDriver._process_secret')
@ -694,6 +701,13 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
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().vip_subnet.to_dict.return_value = {
'cidr': FAKE_CIDR,
'gateway_ip': FAKE_GATEWAY,
'host_routes': [
hr.to_dict(recurse=True)
for hr in 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.clients[API_VERSION].plug_vip.assert_called_once_with(
@ -704,7 +718,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[],
network=self.network)
self.driver.post_network_plug(self.amp, port)
self.driver.post_network_plug(self.amp, port, self.amp_net_config)
self.driver.clients[API_VERSION].plug_network.assert_called_once_with(
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[],
@ -713,14 +727,40 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.driver.clients[API_VERSION].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.amp_net_config)
self.driver.clients[API_VERSION].plug_network.assert_called_once_with(
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[dict(ip_address='198.51.100.5',
subnet_cidr='198.51.100.0/24',
host_routes=[])],
host_routes=[],
gateway=FAKE_GATEWAY)],
mtu=FAKE_MTU))
self.driver.clients[API_VERSION].plug_network.reset_mock()
# Test member network on vip port
port = network_models.Port(id=self.amp.vrrp_port_id,
mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[self.fixed_ip],
network=self.network)
self.driver.post_network_plug(self.amp, port, self.amp_net_config)
self.driver.clients[API_VERSION].plug_network.assert_called_once_with(
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[dict(ip_address='198.51.100.5',
subnet_cidr='198.51.100.0/24',
host_routes=[],
gateway=FAKE_GATEWAY)],
mtu=FAKE_MTU,
vip_net_info=dict(
vip=self.amp.ha_ip,
subnet_cidr=FAKE_VIP_SUBNET,
mac_address=FAKE_MAC_ADDRESS,
gateway=None,
vrrp_ip=self.amp.vrrp_ip,
host_routes=[],
mtu=FAKE_MTU
)))
def test_post_network_plug_with_host_routes(self):
SUBNET_ID = 'SUBNET_ID'
FIXED_IP1 = '192.0.2.2'
@ -734,6 +774,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
network_models.HostRoute(destination=DEST2,
nexthop=NEXTHOP)]
subnet = network_models.Subnet(id=SUBNET_ID, cidr=SUBNET_CIDR,
gateway_ip=FAKE_GATEWAY,
ip_version=4, host_routes=host_routes)
fixed_ips = [
network_models.FixedIP(subnet_id=subnet.id, ip_address=FIXED_IP1,
@ -744,12 +785,14 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
port = network_models.Port(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=fixed_ips,
network=self.network)
self.driver.post_network_plug(self.amp, port)
self.driver.post_network_plug(self.amp, port, self.amp_net_config)
expected_fixed_ips = [
{'ip_address': FIXED_IP1, 'subnet_cidr': SUBNET_CIDR,
'gateway': FAKE_GATEWAY,
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
{'destination': DEST2, 'nexthop': NEXTHOP}]},
{'ip_address': FIXED_IP2, 'subnet_cidr': SUBNET_CIDR,
'gateway': FAKE_GATEWAY,
'host_routes': [{'destination': DEST1, 'nexthop': NEXTHOP},
{'destination': DEST2, 'nexthop': NEXTHOP}]}
]

View File

@ -115,7 +115,9 @@ class TestNoopAmphoraLoadBalancerDriver(base.TestCase):
self.amphora.id])
def test_post_network_plug(self):
self.driver.post_network_plug(self.amphora, self.port)
self.driver.post_network_plug(
self.amphora, self.port,
self.amphorae_net_configs[self.amphora.id])
self.assertEqual((self.amphora.id, self.port.id, 'post_network_plug'),
self.driver.driver.amphoraconfig[(
self.amphora.id, self.port.id)])

View File

@ -24,16 +24,20 @@ from octavia.tests.common import sample_certs
CONF = cfg.CONF
class AmphoraTuple(collections.namedtuple(
'amphora', 'id, lb_network_ip, vrrp_ip, ha_ip, vrrp_port_id, '
'ha_port_id, role, status, vrrp_interface,'
'vrrp_priority, api_version')):
def to_dict(self):
return self._asdict()
def sample_amphora_tuple(id='sample_amphora_id_1', lb_network_ip='10.0.1.1',
vrrp_ip='10.1.1.1', ha_ip='192.168.10.1',
vrrp_port_id='1234', ha_port_id='1234', role=None,
status='ACTIVE', vrrp_interface=None,
vrrp_priority=None, api_version='1.0'):
in_amphora = collections.namedtuple(
'amphora', 'id, lb_network_ip, vrrp_ip, ha_ip, vrrp_port_id, '
'ha_port_id, role, status, vrrp_interface,'
'vrrp_priority, api_version')
return in_amphora(
amp = AmphoraTuple(
id=id,
lb_network_ip=lb_network_ip,
vrrp_ip=vrrp_ip,
@ -45,6 +49,7 @@ def sample_amphora_tuple(id='sample_amphora_id_1', lb_network_ip='10.0.1.1',
vrrp_interface=vrrp_interface,
vrrp_priority=vrrp_priority,
api_version=api_version)
return amp
RET_PERSISTENCE = {
@ -648,9 +653,9 @@ def sample_vrrp_group_tuple():
smtp_connect_timeout='')
def sample_vip_tuple():
vip = collections.namedtuple('vip', 'ip_address')
return vip(ip_address='10.0.0.2')
def sample_vip_tuple(ip_address='10.0.0.2', subnet_id='vip_subnet_uuid'):
vip = collections.namedtuple('vip', ('ip_address', 'subnet_id'))
return vip(ip_address=ip_address, subnet_id=subnet_id)
def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,

View File

@ -23,16 +23,20 @@ from octavia.tests.common import sample_certs
CONF = cfg.CONF
class AmphoraTuple(collections.namedtuple(
'amphora', 'id, lb_network_ip, vrrp_ip, ha_ip, vrrp_port_id, '
'ha_port_id, role, status, vrrp_interface,'
'vrrp_priority, api_version')):
def to_dict(self):
return self._asdict()
def sample_amphora_tuple(id='sample_amphora_id_1', lb_network_ip='10.0.1.1',
vrrp_ip='10.1.1.1', ha_ip='192.168.10.1',
vrrp_port_id='1234', ha_port_id='1234', role=None,
status='ACTIVE', vrrp_interface=None,
vrrp_priority=None, api_version='0.5'):
in_amphora = collections.namedtuple(
'amphora', 'id, lb_network_ip, vrrp_ip, ha_ip, vrrp_port_id, '
'ha_port_id, role, status, vrrp_interface,'
'vrrp_priority, api_version')
return in_amphora(
return AmphoraTuple(
id=id,
lb_network_ip=lb_network_ip,
vrrp_ip=vrrp_ip,
@ -599,9 +603,9 @@ def sample_vrrp_group_tuple():
smtp_connect_timeout='')
def sample_vip_tuple():
vip = collections.namedtuple('vip', 'ip_address')
return vip(ip_address='10.0.0.2')
def sample_vip_tuple(ip_address='10.0.0.2', subnet_id='vip_subnet_uuid'):
vip = collections.namedtuple('vip', ('ip_address', 'subnet_id'))
return vip(ip_address=ip_address, subnet_id=subnet_id)
def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,

View File

@ -238,7 +238,7 @@ class TestAmphoraFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
self.assertIn(constants.VIP, amp_flow.requires)
self.assertIn(constants.ADDED_PORTS, amp_flow.provides)
self.assertIn(constants.UPDATED_PORTS, amp_flow.provides)
self.assertIn(constants.AMP_VRRP_INT, amp_flow.provides)
self.assertIn(constants.AMPHORA, amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
@ -272,7 +272,7 @@ class TestAmphoraFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
self.assertIn(constants.VIP, amp_flow.requires)
self.assertIn(constants.ADDED_PORTS, amp_flow.provides)
self.assertIn(constants.UPDATED_PORTS, amp_flow.provides)
self.assertIn(constants.AMPHORA, amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
self.assertIn(constants.AMPHORAE, amp_flow.provides)
@ -430,7 +430,7 @@ class TestAmphoraFlows(base.TestCase):
self.assertIn(constants.VIP, get_amp_flow.requires)
self.assertIn(constants.VIP_SG_ID, get_amp_flow.requires)
self.assertIn(constants.ADDED_PORTS, get_amp_flow.provides)
self.assertIn(constants.UPDATED_PORTS, get_amp_flow.provides)
self.assertIn(constants.AMPHORA, get_amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, get_amp_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, get_amp_flow.provides)
@ -460,7 +460,7 @@ class TestAmphoraFlows(base.TestCase):
self.assertIn(constants.VIP, get_amp_flow.requires)
self.assertIn(constants.VIP_SG_ID, get_amp_flow.requires)
self.assertIn(constants.ADDED_PORTS, get_amp_flow.provides)
self.assertIn(constants.UPDATED_PORTS, get_amp_flow.provides)
self.assertIn(constants.AMPHORA, get_amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, get_amp_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, get_amp_flow.provides)

View File

@ -183,22 +183,28 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIsInstance(create_flow, flow.Flow)
self.assertIn(constants.LOADBALANCER_ID, create_flow.requires)
self.assertIn(constants.UPDATE_DICT, create_flow.requires)
self.assertIn(constants.BUILD_TYPE_PRIORITY, create_flow.requires)
self.assertIn(constants.FLAVOR, create_flow.requires)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, create_flow.requires)
self.assertIn(constants.AVAILABILITY_ZONE, create_flow.requires)
self.assertIn(constants.SERVER_GROUP_ID, create_flow.requires)
self.assertIn(constants.LISTENERS, create_flow.provides)
self.assertIn(constants.SUBNET, create_flow.provides)
self.assertIn(constants.AMPHORA, create_flow.provides)
self.assertIn(constants.AMPHORA_ID, create_flow.provides)
self.assertIn(constants.COMPUTE_ID, create_flow.provides)
self.assertIn(constants.COMPUTE_OBJ, create_flow.provides)
self.assertIn(constants.LOADBALANCER, create_flow.provides)
self.assertIn(constants.DELTAS, create_flow.provides)
self.assertIn(constants.ADDED_PORTS, create_flow.provides)
self.assertIn(constants.UPDATED_PORTS, create_flow.provides)
self.assertIn(constants.VIP, create_flow.provides)
self.assertIn(constants.AMP_DATA, create_flow.provides)
self.assertIn(constants.SERVER_PEM, create_flow.provides)
self.assertIn(constants.AMPHORA_NETWORK_CONFIG, create_flow.provides)
self.assertEqual(6, len(create_flow.requires))
self.assertEqual(13, len(create_flow.provides),
create_flow.provides)
self.assertEqual(7, len(create_flow.requires))
self.assertEqual(13, len(create_flow.provides))
def test_get_create_load_balancer_flows_active_standby_listeners(
self, mock_get_net_driver):
@ -218,7 +224,7 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIn(constants.COMPUTE_OBJ, create_flow.provides)
self.assertIn(constants.LOADBALANCER, create_flow.provides)
self.assertIn(constants.DELTAS, create_flow.provides)
self.assertIn(constants.ADDED_PORTS, create_flow.provides)
self.assertIn(constants.UPDATED_PORTS, create_flow.provides)
self.assertIn(constants.VIP, create_flow.provides)
self.assertIn(constants.AMP_DATA, create_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG,
@ -243,7 +249,7 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER, failover_flow.requires)
self.assertIn(constants.LOADBALANCER_ID, failover_flow.requires)
self.assertIn(constants.ADDED_PORTS, failover_flow.provides)
self.assertIn(constants.UPDATED_PORTS, failover_flow.provides)
self.assertIn(constants.AMPHORA, failover_flow.provides)
self.assertIn(constants.AMPHORA_ID, failover_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG,
@ -312,7 +318,7 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER, failover_flow.requires)
self.assertIn(constants.LOADBALANCER_ID, failover_flow.requires)
self.assertIn(constants.ADDED_PORTS, failover_flow.provides)
self.assertIn(constants.UPDATED_PORTS, failover_flow.provides)
self.assertIn(constants.AMP_VRRP_INT, failover_flow.provides)
self.assertIn(constants.AMPHORA, failover_flow.provides)
self.assertIn(constants.AMPHORA_ID, failover_flow.provides)

View File

@ -37,6 +37,7 @@ class TestMemberFlows(base.TestCase):
self.assertIsInstance(member_flow, flow.Flow)
self.assertIn(constants.MEMBER, member_flow.requires)
self.assertIn(constants.LISTENERS, member_flow.requires)
self.assertIn(constants.LOADBALANCER, member_flow.requires)
self.assertIn(constants.POOL, member_flow.requires)
@ -44,10 +45,11 @@ class TestMemberFlows(base.TestCase):
self.assertIn(constants.AVAILABILITY_ZONE, member_flow.requires)
self.assertIn(constants.DELTAS, member_flow.provides)
self.assertIn(constants.ADDED_PORTS, member_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, member_flow.provides)
self.assertIn(constants.UPDATED_PORTS, member_flow.provides)
self.assertEqual(5, len(member_flow.requires))
self.assertEqual(2, len(member_flow.provides))
self.assertEqual(6, len(member_flow.requires))
self.assertEqual(3, len(member_flow.provides))
def test_get_delete_member_flow(self, mock_get_net_driver):
@ -58,10 +60,16 @@ class TestMemberFlows(base.TestCase):
self.assertIn(constants.MEMBER, member_flow.requires)
self.assertIn(constants.LISTENERS, member_flow.requires)
self.assertIn(constants.LOADBALANCER, member_flow.requires)
self.assertIn(constants.LOADBALANCER_ID, member_flow.requires)
self.assertIn(constants.POOL, member_flow.requires)
self.assertIn(constants.AVAILABILITY_ZONE, member_flow.requires)
self.assertEqual(4, len(member_flow.requires))
self.assertEqual(0, len(member_flow.provides))
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, member_flow.provides)
self.assertIn(constants.DELTAS, member_flow.provides)
self.assertIn(constants.UPDATED_PORTS, member_flow.provides)
self.assertEqual(6, len(member_flow.requires))
self.assertEqual(3, len(member_flow.provides))
def test_get_update_member_flow(self, mock_get_net_driver):
@ -91,7 +99,8 @@ class TestMemberFlows(base.TestCase):
self.assertIn(constants.AVAILABILITY_ZONE, member_flow.requires)
self.assertIn(constants.DELTAS, member_flow.provides)
self.assertIn(constants.ADDED_PORTS, member_flow.provides)
self.assertIn(constants.UPDATED_PORTS, member_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, member_flow.provides)
self.assertEqual(4, len(member_flow.requires))
self.assertEqual(2, len(member_flow.provides))
self.assertEqual(5, len(member_flow.requires))
self.assertEqual(3, len(member_flow.provides))

View File

@ -50,6 +50,9 @@ _vip_mock = mock.MagicMock()
_load_balancer_mock.vip = _vip_mock
_LB_mock = mock.MagicMock()
_amphorae_mock = [_amphora_mock]
_amphora_network_config_mock = mock.MagicMock()
_amphorae_network_config_mock = {
_amphora_mock.id: _amphora_network_config_mock}
_network_mock = mock.MagicMock()
_port_mock = mock.MagicMock()
_ports_mock = [_port_mock]
@ -379,10 +382,12 @@ class TestAmphoraDriverTasks(base.TestCase):
amphora_post_network_plug_obj = (amphora_driver_tasks.
AmphoraPostNetworkPlug())
amphora_post_network_plug_obj.execute(_amphora_mock, _ports_mock)
amphora_post_network_plug_obj.execute(_amphora_mock, _ports_mock,
_amphora_network_config_mock)
(mock_driver.post_network_plug.
assert_called_once_with)(_amphora_mock, _port_mock)
assert_called_once_with)(_amphora_mock, _port_mock,
_amphora_network_config_mock)
# Test revert
amp = amphora_post_network_plug_obj.revert(None, _amphora_mock)
@ -428,17 +433,20 @@ class TestAmphoraDriverTasks(base.TestCase):
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,
_amphorae_network_config_mock)
(mock_driver.post_network_plug.
assert_called_once_with(_amphora_mock, port_mock))
assert_called_once_with(_amphora_mock, port_mock,
_amphora_network_config_mock))
# Test with no ports to plug
mock_driver.post_network_plug.reset_mock()
_deltas_mock = {'0': [port_mock]}
amphora_post_network_plug_obj.execute(_LB_mock, _deltas_mock)
amphora_post_network_plug_obj.execute(_LB_mock, _deltas_mock,
_amphora_network_config_mock)
mock_driver.post_network_plug.assert_not_called()
# Test revert

View File

@ -731,6 +731,7 @@ class TestControllerWorker(base.TestCase):
store={constants.MEMBER: _member_mock,
constants.LISTENERS: [_listener_mock],
constants.LOADBALANCER: _load_balancer_mock,
constants.LOADBALANCER_ID: _load_balancer_mock.id,
constants.POOL: _pool_mock,
constants.AVAILABILITY_ZONE: {}}))
@ -769,6 +770,8 @@ class TestControllerWorker(base.TestCase):
[_listener_mock],
constants.LOADBALANCER:
_load_balancer_mock,
constants.LOADBALANCER_ID:
_load_balancer_mock.id,
constants.POOL:
_pool_mock,
constants.AVAILABILITY_ZONE: {}}))
@ -849,6 +852,8 @@ class TestControllerWorker(base.TestCase):
constants.LISTENERS: [_listener_mock],
constants.LOADBALANCER:
_load_balancer_mock,
constants.LOADBALANCER_ID:
_load_balancer_mock.id,
constants.POOL: _pool_mock,
constants.AVAILABILITY_ZONE: {}}))

View File

@ -281,7 +281,7 @@ class TestAmphoraFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
self.assertIn(constants.VIP, amp_flow.requires)
self.assertIn(constants.ADDED_PORTS, amp_flow.provides)
self.assertIn(constants.UPDATED_PORTS, amp_flow.provides)
self.assertIn(constants.AMP_VRRP_INT, amp_flow.provides)
self.assertIn(constants.AMPHORA, amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
@ -316,7 +316,7 @@ class TestAmphoraFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires)
self.assertIn(constants.VIP, amp_flow.requires)
self.assertIn(constants.ADDED_PORTS, amp_flow.provides)
self.assertIn(constants.UPDATED_PORTS, amp_flow.provides)
self.assertIn(constants.AMPHORA, amp_flow.provides)
self.assertIn(constants.AMPHORA_ID, amp_flow.provides)
self.assertIn(constants.AMPHORAE, amp_flow.provides)

View File

@ -210,8 +210,14 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIsInstance(create_flow, flow.Flow)
self.assertIn(constants.LOADBALANCER_ID, create_flow.requires)
self.assertIn(constants.UPDATE_DICT, create_flow.requires)
self.assertIn(constants.BUILD_TYPE_PRIORITY, create_flow.requires)
self.assertIn(constants.FLAVOR, create_flow.requires)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, create_flow.requires)
self.assertIn(constants.AVAILABILITY_ZONE, create_flow.requires)
self.assertIn(constants.SERVER_GROUP_ID, create_flow.requires)
self.assertIn(constants.LISTENERS, create_flow.provides)
self.assertIn(constants.SUBNET, create_flow.provides)
self.assertIn(constants.AMPHORA, create_flow.provides)
self.assertIn(constants.AMPHORA_ID, create_flow.provides)
self.assertIn(constants.AMPHORA_NETWORK_CONFIG, create_flow.provides)
@ -220,12 +226,11 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIn(constants.COMPUTE_OBJ, create_flow.provides)
self.assertIn(constants.LOADBALANCER, create_flow.provides)
self.assertIn(constants.DELTAS, create_flow.provides)
self.assertIn(constants.ADDED_PORTS, create_flow.provides)
self.assertIn(constants.UPDATED_PORTS, create_flow.provides)
self.assertIn(constants.SERVER_PEM, create_flow.provides)
self.assertIn(constants.SUBNET, create_flow.provides)
self.assertIn(constants.VIP, create_flow.provides)
self.assertEqual(6, len(create_flow.requires))
self.assertEqual(7, len(create_flow.requires))
self.assertEqual(13, len(create_flow.provides),
create_flow.provides)
@ -244,7 +249,7 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIn(constants.SERVER_GROUP_ID, create_flow.requires)
self.assertIn(constants.UPDATE_DICT, create_flow.requires)
self.assertIn(constants.ADDED_PORTS, create_flow.provides)
self.assertIn(constants.UPDATED_PORTS, create_flow.provides)
self.assertIn(constants.AMP_DATA, create_flow.provides)
self.assertIn(constants.AMP_VRRP_INT, create_flow.provides)
self.assertIn(constants.AMPHORA, create_flow.provides)
@ -280,7 +285,7 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER, failover_flow.requires)
self.assertIn(constants.LOADBALANCER_ID, failover_flow.requires)
self.assertIn(constants.ADDED_PORTS, failover_flow.provides)
self.assertIn(constants.UPDATED_PORTS, failover_flow.provides)
self.assertIn(constants.AMPHORA, failover_flow.provides)
self.assertIn(constants.AMPHORA_ID, failover_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG,
@ -343,7 +348,7 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER, failover_flow.requires)
self.assertIn(constants.LOADBALANCER_ID, failover_flow.requires)
self.assertIn(constants.ADDED_PORTS, failover_flow.provides)
self.assertIn(constants.UPDATED_PORTS, failover_flow.provides)
self.assertIn(constants.AMPHORA, failover_flow.provides)
self.assertIn(constants.AMPHORA_ID, failover_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG,

View File

@ -37,18 +37,19 @@ class TestMemberFlows(base.TestCase):
self.assertIsInstance(member_flow, flow.Flow)
self.assertIn(constants.MEMBER, member_flow.requires)
self.assertIn(constants.LISTENERS, member_flow.requires)
self.assertIn(constants.LOADBALANCER, member_flow.requires)
self.assertIn(constants.LOADBALANCER_ID, member_flow.requires)
self.assertIn(constants.POOL_ID, member_flow.requires)
self.assertIn(constants.MEMBER, member_flow.requires)
self.assertIn(constants.AVAILABILITY_ZONE, member_flow.requires)
self.assertIn(constants.DELTAS, member_flow.provides)
self.assertIn(constants.ADDED_PORTS, member_flow.provides)
self.assertIn(constants.UPDATED_PORTS, member_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, member_flow.provides)
self.assertEqual(6, len(member_flow.requires))
self.assertEqual(2, len(member_flow.provides))
self.assertEqual(3, len(member_flow.provides))
def test_get_delete_member_flow(self, mock_get_net_driver):
@ -62,9 +63,14 @@ class TestMemberFlows(base.TestCase):
self.assertIn(constants.LOADBALANCER_ID, member_flow.requires)
self.assertIn(constants.POOL_ID, member_flow.requires)
self.assertIn(constants.PROJECT_ID, member_flow.requires)
self.assertIn(constants.AVAILABILITY_ZONE, member_flow.requires)
self.assertEqual(6, len(member_flow.requires))
self.assertEqual(0, len(member_flow.provides))
self.assertIn(constants.DELTAS, member_flow.provides)
self.assertIn(constants.UPDATED_PORTS, member_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, member_flow.provides)
self.assertEqual(7, len(member_flow.requires))
self.assertEqual(3, len(member_flow.provides))
def test_get_update_member_flow(self, mock_get_net_driver):
@ -96,7 +102,8 @@ class TestMemberFlows(base.TestCase):
self.assertIn(constants.AVAILABILITY_ZONE, member_flow.requires)
self.assertIn(constants.DELTAS, member_flow.provides)
self.assertIn(constants.ADDED_PORTS, member_flow.provides)
self.assertIn(constants.UPDATED_PORTS, member_flow.provides)
self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, member_flow.provides)
self.assertEqual(5, len(member_flow.requires))
self.assertEqual(2, len(member_flow.provides))
self.assertEqual(3, len(member_flow.provides))

View File

@ -58,6 +58,9 @@ _LB_mock = {
constants.LOADBALANCER_ID: LB_ID,
}
_amphorae_mock = [_db_amphora_mock]
_amphora_network_config_mock = mock.MagicMock()
_amphorae_network_config_mock = {
_amphora_mock[constants.ID]: _amphora_network_config_mock}
_network_mock = mock.MagicMock()
_session_mock = mock.MagicMock()
@ -68,7 +71,7 @@ _session_mock = mock.MagicMock()
@mock.patch('octavia.db.repositories.ListenerRepository.get',
return_value=_listener_mock)
@mock.patch('octavia.db.api.get_session', return_value=_session_mock)
@mock.patch('octavia.controller.worker.v2.tasks.amphora_driver_tasks.LOG')
@mock.patch('octavia.controller.worker.v1.tasks.amphora_driver_tasks.LOG')
@mock.patch('oslo_utils.uuidutils.generate_uuid', return_value=AMP_ID)
@mock.patch('stevedore.driver.DriverManager.driver')
class TestAmphoraDriverTasks(base.TestCase):
@ -385,11 +388,13 @@ class TestAmphoraDriverTasks(base.TestCase):
port_mock = {constants.NETWORK: mock.MagicMock(),
constants.FIXED_IPS: fixed_ips,
constants.ID: uuidutils.generate_uuid()}
amphora_post_network_plug_obj.execute(_amphora_mock, [port_mock])
amphora_post_network_plug_obj.execute(_amphora_mock, [port_mock],
_amphora_network_config_mock)
(mock_driver.post_network_plug.
assert_called_once_with)(_db_amphora_mock,
network_data_models.Port(**port_mock))
network_data_models.Port(**port_mock),
_amphora_network_config_mock)
# Test revert
amp = amphora_post_network_plug_obj.revert(None, _amphora_mock)
@ -434,11 +439,13 @@ class TestAmphoraDriverTasks(base.TestCase):
port_mock = {constants.NETWORK: mock.MagicMock(),
constants.FIXED_IPS: fixed_ips,
constants.ID: uuidutils.generate_uuid()}
amphora_post_network_plug_obj.execute(_amphora_mock, [port_mock])
amphora_post_network_plug_obj.execute(_amphora_mock, [port_mock],
_amphora_network_config_mock)
(mock_driver.post_network_plug.
assert_called_once_with)(_db_amphora_mock,
network_data_models.Port(**port_mock))
network_data_models.Port(**port_mock),
_amphora_network_config_mock)
call_args = mock_driver.post_network_plug.call_args[0]
port_arg = call_args[1]
@ -472,18 +479,21 @@ class TestAmphoraDriverTasks(base.TestCase):
constants.ID: uuidutils.generate_uuid()}
_deltas_mock = {_db_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,
_amphorae_network_config_mock)
(mock_driver.post_network_plug.
assert_called_once_with(_db_amphora_mock,
network_data_models.Port(**port_mock)))
network_data_models.Port(**port_mock),
_amphora_network_config_mock))
# Test with no ports to plug
mock_driver.post_network_plug.reset_mock()
_deltas_mock = {'0': [port_mock]}
amphora_post_network_plug_obj.execute(_LB_mock, _deltas_mock)
amphora_post_network_plug_obj.execute(_LB_mock, _deltas_mock,
_amphora_network_config_mock)
mock_driver.post_network_plug.assert_not_called()
# Test revert

View File

@ -871,7 +871,10 @@ class TestAllowedAddressPairsDriver(base.TestCase):
@mock.patch("time.time")
@mock.patch("time.sleep")
def test_unplug_aap_port(self, mock_time_sleep, mock_time_time):
@mock.patch("octavia.network.drivers.neutron.allowed_address_pairs."
"AllowedAddressPairsDriver.unplug_network")
def test_unplug_aap_port(self, mock_unplug_network,
mock_time_sleep, mock_time_time):
lb = dmh.generate_load_balancer_tree()
update_port = self.driver.neutron_client.update_port
port1 = t_constants.MOCK_NEUTRON_PORT['port']
@ -894,6 +897,8 @@ class TestAllowedAddressPairsDriver(base.TestCase):
self.driver.unplug_aap_port(lb.vip, lb.amphorae[0], subnet)
clear_aap = {'port': {'allowed_address_pairs': []}}
update_port.assert_called_once_with(port2.get('id'), clear_aap)
mock_unplug_network.assert_called_once_with(
lb.amphorae[0].compute_id, subnet.network_id)
def test_plug_network_when_compute_instance_cant_be_found(self):
net_id = t_constants.MOCK_NOVA_INTERFACE.net_id

View File

@ -563,3 +563,144 @@ class TestBaseNeutronNetworkDriver(base.TestCase):
self.assertEqual(t_constants.MOCK_NETWORK_ID, ip_avail.network_id)
self.assertEqual(t_constants.MOCK_SUBNET_IP_AVAILABILITY,
ip_avail.subnet_ip_availability)
def test_plug_fixed_ip(self):
show_port = self.driver.neutron_client.show_port
show_port.return_value = {
'id': t_constants.MOCK_PORT_ID,
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID,
'ip_address': t_constants.MOCK_IP_ADDRESS,
'subnet': None
}]
}
self.driver.plug_fixed_ip(t_constants.MOCK_PORT_ID,
t_constants.MOCK_SUBNET_ID2,
t_constants.MOCK_IP_ADDRESS2)
expected_body = {
'port': {
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID,
'ip_address': t_constants.MOCK_IP_ADDRESS,
'subnet': None
}, {
'subnet_id': t_constants.MOCK_SUBNET_ID2,
'ip_address': t_constants.MOCK_IP_ADDRESS2
}
]
}
}
self.driver.neutron_client.update_port.assert_called_once_with(
t_constants.MOCK_PORT_ID,
expected_body)
def test_plug_fixed_ip_no_ip_address(self):
show_port = self.driver.neutron_client.show_port
show_port.return_value = {
'id': t_constants.MOCK_PORT_ID,
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID,
'ip_address': t_constants.MOCK_IP_ADDRESS,
'subnet': None
}]
}
self.driver.plug_fixed_ip(t_constants.MOCK_PORT_ID,
t_constants.MOCK_SUBNET_ID2)
expected_body = {
'port': {
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID,
'ip_address': t_constants.MOCK_IP_ADDRESS,
'subnet': None
}, {
'subnet_id': t_constants.MOCK_SUBNET_ID2,
}
]
}
}
self.driver.neutron_client.update_port.assert_called_once_with(
t_constants.MOCK_PORT_ID,
expected_body)
def test_plug_fixed_ip_exception(self):
show_port = self.driver.neutron_client.show_port
show_port.return_value = {
'id': t_constants.MOCK_PORT_ID,
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID,
'ip_address': t_constants.MOCK_IP_ADDRESS,
'subnet': None
}]
}
self.driver.neutron_client.update_port.side_effect = Exception
self.assertRaises(network_base.NetworkException,
self.driver.plug_fixed_ip,
t_constants.MOCK_PORT_ID,
t_constants.MOCK_SUBNET_ID2)
def test_unplug_fixed_ip(self):
show_port = self.driver.neutron_client.show_port
show_port.return_value = {
'id': t_constants.MOCK_PORT_ID,
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID,
'ip_address': t_constants.MOCK_IP_ADDRESS,
'subnet': None
}, {
'subnet_id': t_constants.MOCK_SUBNET_ID2,
'ip_address': t_constants.MOCK_IP_ADDRESS2,
'subnet': None
}]
}
self.driver.unplug_fixed_ip(t_constants.MOCK_PORT_ID,
t_constants.MOCK_SUBNET_ID)
expected_body = {
'port': {
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID2,
'ip_address': t_constants.MOCK_IP_ADDRESS2,
'subnet': None
}
]
}
}
self.driver.neutron_client.update_port.assert_called_once_with(
t_constants.MOCK_PORT_ID,
expected_body)
def test_unplug_fixed_ip_exception(self):
show_port = self.driver.neutron_client.show_port
show_port.return_value = {
'id': t_constants.MOCK_PORT_ID,
'fixed_ips': [
{
'subnet_id': t_constants.MOCK_SUBNET_ID,
'ip_address': t_constants.MOCK_IP_ADDRESS,
'subnet': None
}]
}
self.driver.neutron_client.update_port.side_effect = Exception
self.assertRaises(network_base.NetworkException,
self.driver.unplug_fixed_ip,
t_constants.MOCK_PORT_ID,
t_constants.MOCK_SUBNET_ID)

View File

@ -28,6 +28,7 @@ class TestNoopNetworkDriver(base.TestCase):
FAKE_UUID_4 = uuidutils.generate_uuid()
FAKE_UUID_5 = uuidutils.generate_uuid()
FAKE_UUID_6 = uuidutils.generate_uuid()
FAKE_UUID_7 = uuidutils.generate_uuid()
def setUp(self):
super().setUp()
@ -49,6 +50,7 @@ class TestNoopNetworkDriver(base.TestCase):
self.vip.port_id = uuidutils.generate_uuid()
self.amphora_id = self.FAKE_UUID_1
self.compute_id = self.FAKE_UUID_2
self.compute2_id = self.FAKE_UUID_2
self.subnet_id = self.FAKE_UUID_3
self.subnet_name = 'subnet1'
self.qos_policy_id = self.FAKE_UUID_5
@ -56,12 +58,14 @@ class TestNoopNetworkDriver(base.TestCase):
self.amphora1 = models.Amphora()
self.amphora1.id = uuidutils.generate_uuid()
self.amphora1.compute_id = self.compute_id
self.amphora1.vrrp_port_id = uuidutils.generate_uuid()
self.amphora1.ha_port_id = uuidutils.generate_uuid()
self.amphora1.vrrp_ip = '10.0.1.10'
self.amphora1.ha_ip = '10.0.1.11'
self.amphora2 = models.Amphora()
self.amphora2.id = uuidutils.generate_uuid()
self.amphora2.compute_id = self.compute2_id
self.amphora2.vrrp_port_id = uuidutils.generate_uuid()
self.amphora2.ha_port_id = uuidutils.generate_uuid()
self.amphora2.vrrp_ip = '10.0.2.10'
@ -69,6 +73,7 @@ class TestNoopNetworkDriver(base.TestCase):
self.load_balancer.amphorae = [self.amphora1, self.amphora2]
self.load_balancer.vip = self.vip
self.subnet = mock.MagicMock()
self.subnet.id = self.subnet_id
def test_allocate_vip(self):
self.driver.allocate_vip(self.load_balancer)
@ -105,28 +110,51 @@ class TestNoopNetworkDriver(base.TestCase):
self.load_balancer.id, self.vip.ip_address)])
def test_plug_network(self):
self.driver.plug_network(self.amphora_id, self.network_id,
self.ip_address)
self.assertEqual((self.amphora_id, self.network_id, self.ip_address,
'plug_network'),
self.driver.plug_network(self.compute_id, self.network_id)
self.assertEqual((self.compute_id, self.network_id, 'plug_network'),
self.driver.driver.networkconfigconfig[(
self.amphora_id, self.network_id,
self.ip_address)])
self.compute_id, self.network_id)])
def test_unplug_network(self):
self.driver.unplug_network(self.amphora_id, self.network_id,
ip_address=self.ip_address)
self.assertEqual((self.amphora_id, self.network_id, self.ip_address,
'unplug_network'),
self.driver.unplug_network(self.compute_id, self.network_id)
self.assertEqual((self.compute_id, self.network_id, 'unplug_network'),
self.driver.driver.networkconfigconfig[(
self.amphora_id, self.network_id,
self.ip_address)])
self.compute_id, self.network_id)])
def test_get_plugged_networks(self):
self.driver.get_plugged_networks(self.amphora_id)
self.assertEqual((self.amphora_id, 'get_plugged_networks'),
self.driver.get_plugged_networks(self.compute_id)
self.assertEqual((self.compute_id, 'get_plugged_networks'),
self.driver.driver.networkconfigconfig[(
self.amphora_id)])
self.compute_id)])
def test_plug_unplug_and_get_plugged_networks(self):
amphora = mock.MagicMock()
amphora.compute_id = uuidutils.generate_uuid()
network = self.driver.plug_network(amphora.compute_id,
self.network_id)
self.assertEqual(
network,
network_models.Interface(
id=mock.ANY,
compute_id=amphora.compute_id,
network_id=self.network_id,
fixed_ips=[],
port_id=mock.ANY
))
networks = self.driver.get_plugged_networks(amphora.compute_id)
self.assertEqual(
networks,
[network_models.Interface(
id=mock.ANY,
compute_id=amphora.compute_id,
network_id=self.network_id,
fixed_ips=[],
port_id=mock.ANY
)])
self.driver.unplug_network(amphora.compute_id,
self.network_id)
networks = self.driver.get_plugged_networks(amphora.compute_id)
self.assertEqual([], networks)
def test_update_vip(self):
self.driver.update_vip(self.load_balancer)
@ -293,3 +321,12 @@ class TestNoopNetworkDriver(base.TestCase):
self.assertEqual(SUBNET_ID, result.fixed_ips[1].subnet_id)
self.assertEqual(QOS_POLICY_ID, result.qos_policy_id)
self.assertFalse(result.admin_state_up)
def test_plug_fixed_ip(self):
self.driver.plug_fixed_ip(self.port_id, self.subnet_id,
self.ip_address)
self.assertEqual(
(self.port_id, self.subnet_id, self.ip_address, 'plug_fixed_ip'),
self.driver.driver.networkconfigconfig[
self.port_id, self.subnet_id]
)

View File

@ -0,0 +1,8 @@
---
fixes:
- |
Fix a bug when adding a member on a subnet that belongs to a network with
multiple subnets, an incorrect subnet may have been plugged in the amphora.
- |
Fix a bug when deleting the last member plugged on a network, the port that
was no longer used was not deleted.