Merge "Fix plugging member subnets on existing networks" into stable/wallaby

This commit is contained in:
Zuul 2022-12-07 14:02:43 +00:00 committed by Gerrit Code Review
commit b0379d6a84
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'
@ -926,5 +934,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

@ -457,6 +457,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] = (
@ -489,6 +490,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] = (
@ -540,6 +542,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

@ -507,18 +507,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

@ -203,12 +203,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

@ -548,18 +548,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

@ -197,12 +197,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

@ -256,7 +256,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)
@ -290,7 +290,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)
@ -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)
@ -491,7 +491,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,
@ -323,7 +329,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

@ -810,6 +810,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: {}}))
@ -848,6 +849,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: {}}))
@ -928,6 +931,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

@ -301,7 +301,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)
@ -336,7 +336,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,
@ -352,7 +357,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.