Merge "Add support for SR-IOV member ports"

This commit is contained in:
Zuul
2025-02-26 21:01:14 +00:00
committed by Gerrit Code Review
48 changed files with 674 additions and 332 deletions
+16
View File
@@ -1215,6 +1215,14 @@ member-ids:
in: body
required: true
type: array
member_vnic_type:
description: |
The member vNIC type used for the member port. One of ``normal`` or
``direct``.
in: body
required: true
type: string
min_version: 2.29
members-status-object-list:
description: |
A list of members status objects.
@@ -1479,6 +1487,14 @@ request_errors:
in: body
required: true
type: integer
request_sriov:
description: |
Request that an SR-IOV VF be used for the member network port. Defaults to
``false``.
in: body
required: false
type: boolean
min_version: 2.29
session_persistence:
description: |
A JSON object specifying the session persistence for the pool or ``null``
@@ -15,6 +15,7 @@
"protocol_port": 80,
"id": "957a1ace-1bd2-449b-8455-820b6e4b63f3",
"operating_status": "NO_MONITOR",
"tags": ["test_tag"]
"tags": ["test_tag"],
"vnic_type": "normal"
}
}
@@ -15,6 +15,7 @@
"protocol_port": 80,
"id": "957a1ace-1bd2-449b-8455-820b6e4b63f3",
"operating_status": "NO_MONITOR",
"tags": ["test_tag"]
"tags": ["test_tag"],
"vnic_type": "normal"
}
}
@@ -15,6 +15,7 @@
"protocol_port": 80,
"id": "957a1ace-1bd2-449b-8455-820b6e4b63f3",
"operating_status": "NO_MONITOR",
"tags": ["updated_tag"]
"tags": ["updated_tag"],
"vnic_type": "normal"
}
}
@@ -16,7 +16,8 @@
"protocol_port": 80,
"id": "957a1ace-1bd2-449b-8455-820b6e4b63f3",
"operating_status": "NO_MONITOR",
"tags": ["test_tag"]
"tags": ["test_tag"],
"vnic_type": "normal"
}
]
}
+6
View File
@@ -62,6 +62,7 @@ Response Parameters
- tags: tags
- updated_at: updated_at
- weight: weight
- vnic_type: member_vnic_type
Response Example
----------------
@@ -156,6 +157,7 @@ Request
- pool_id: path-pool-id
- project_id: project_id-optional-deprecated
- protocol_port: protocol_port
- request_sriov: request_sriov
- subnet_id: subnet_id-optional
- tags: tags-optional
- weight: weight-optional
@@ -193,6 +195,7 @@ Response Parameters
- tags: tags
- updated_at: updated_at
- weight: weight
- vnic_type: member_vnic_type
Response Example
----------------
@@ -260,6 +263,7 @@ Response Parameters
- tags: tags
- updated_at: updated_at
- weight: weight
- vnic_type: member_vnic_type
Response Example
----------------
@@ -347,6 +351,7 @@ Response Parameters
- tags: tags
- updated_at: updated_at
- weight: weight
- vnic_type: member_vnic_type
Response Example
----------------
@@ -414,6 +419,7 @@ Request
- pool_id: path-pool-id
- project_id: project_id-optional-deprecated
- protocol_port: protocol_port
- request_sriov: request_sriov
- subnet_id: subnet_id-optional
- tags: tags-optional
- weight: weight-optional
+12 -1
View File
@@ -83,9 +83,20 @@ Octavia flavor that will use the compute flavor.
.. code-block:: bash
$ openstack loadbalancer flavorprofile create --name amphora-sriov-profile --provider amphora --flavor-data '{"compute_flavor": "amphora-sriov-flavor", "sriov_vip": true}'
$ openstack loadbalancer flavorprofile create --name amphora-sriov-profile --provider amphora --flavor-data '{"compute_flavor": "amphora-sriov-flavor", "sriov_vip": true, "allow_member_sriov": true}'
$ openstack loadbalancer flavor create --name SRIOV-public-members --flavorprofile amphora-sriov-profile --description "A load balancer that uses SR-IOV for the 'public' network and 'members' network." --enable
When the `allow_member_sriov` Octavia flavor setting is true, users can request
Octavia to attach the member ports using SR-IOV VFs. If Octavia is not able to
successfully attach the member port as an SR-IOV VF, the member will be marked
as `provisioning_status` of `ERROR` as we could not acquire a networking port
for the requested member network. If the member network is already attached
using a non-SR-IOV port, the member will also be marked with
`provisioning_status` of `ERROR`.
.. note::
By default, both `sriov_vip` and `allow_member_sriov` are false.
Building the Amphora Image
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -984,6 +984,12 @@ contain the following:
| | | service existing connections. A valid |
| | | value is from 0 to 256. Default is 1. |
+-----------------------+--------+------------------------------------------+
| vnic_type | string | The member vNIC type used for the member |
| | | port. One of normal or direct. |
+-----------------------+--------+------------------------------------------+
..note:: The vnic_type of normal and direct are the same as those defined by
neutron ports.
Delete
^^^^^^
@@ -406,7 +406,10 @@ class HaproxyAmphoraLoadBalancerDriver(
fixed_ips.append(ip)
port_info = {'mac_address': port.mac_address,
'fixed_ips': fixed_ips,
'mtu': port.network.mtu}
'mtu': port.network.mtu,
'is_sriov': False}
if port.vnic_type == consts.VNIC_TYPE_DIRECT:
port_info['is_sriov'] = True
if port.id == amphora.vrrp_port_id:
# We have to special-case sharing the vrrp port and pass through
# enough extra information to populate the whole VIP port
@@ -53,5 +53,10 @@ SUPPORTED_FLAVOR_SCHEMA = {
"description": "When true, the VIP port will be created using an "
"SR-IOV VF port."
},
consts.ALLOW_MEMBER_SRIOV: {
"type": "boolean",
"description": "When true, users can request a member port be "
"SR-IOV enabled at member creation time."
}
}
}
+6
View File
@@ -462,6 +462,12 @@ def db_members_to_provider_members(db_members):
def db_member_to_provider_member(db_member):
new_member_dict = member_dict_to_provider_dict(db_member.to_dict())
if constants.REQUEST_SRIOV in new_member_dict:
request_sriov = new_member_dict.pop(constants.REQUEST_SRIOV)
if request_sriov:
new_member_dict[constants.VNIC_TYPE] = constants.VNIC_TYPE_DIRECT
else:
new_member_dict[constants.VNIC_TYPE] = constants.VNIC_TYPE_NORMAL
return driver_dm.Member.from_dict(new_member_dict)
+5 -2
View File
@@ -148,10 +148,13 @@ class RootController:
# HTTP Strict Transport Security (HSTS)
self._add_a_version(versions, 'v2.27', 'v2', 'SUPPORTED',
'2023-05-05T00:00:00Z', host_url)
# Add port vnic_type for SR-IOV
# Add VIP port vnic_type for SR-IOV
self._add_a_version(versions, 'v2.28', 'v2', 'SUPPORTED',
'2023-11-08T00:00:00Z', host_url)
# Add VIP SGs
self._add_a_version(versions, 'v2.29', 'v2', 'CURRENT',
self._add_a_version(versions, 'v2.29', 'v2', 'SUPPORTED',
'2024-10-15T00:00:00Z', host_url)
# Add member port SR-IOV support
self._add_a_version(versions, 'v2.30', 'v2', 'CURRENT',
'2025-02-26T00:00:00Z', host_url)
return {'versions': versions}
+55 -2
View File
@@ -19,6 +19,7 @@ from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import strutils
from pecan import request as pecan_request
from sqlalchemy.orm import exc as sa_exception
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
@@ -146,10 +147,20 @@ class MemberController(base.BaseController):
member = member_.member
context = pecan_request.context.get('octavia_context')
flavor_dict = {}
with context.session.begin():
pool = self.repositories.pool.get(context.session, id=self.pool_id)
member.project_id, provider = self._get_lb_project_id_provider(
context.session, pool.load_balancer_id)
if pool.load_balancer.flavor_id:
try:
flavor_dict = (
self.repositories.flavor.get_flavor_metadata_dict(
context.session, pool.load_balancer.flavor_id))
except sa_exception.NoResultFound:
LOG.error("load balancer has a flavor ID: %s that was not "
"found in the database. Assuming no flavor.",
pool.load_balancer.flavor_id)
self._auth_validate_action(context, member.project_id,
constants.RBAC_POST)
@@ -173,8 +184,23 @@ class MemberController(base.BaseController):
raise exceptions.QuotaException(
resource=data_models.Member._name())
member_dict = db_prepare.create_member(member.to_dict(
render_unsets=True), self.pool_id, bool(pool.health_monitor))
db_member_dict = member.to_dict(render_unsets=True)
# Validate and store port SR-IOV vnic_type
request_sriov = db_member_dict.pop('request_sriov')
if (request_sriov and not
flavor_dict.get(constants.ALLOW_MEMBER_SRIOV, False)):
raise exceptions.MemberSRIOVDisabled
if request_sriov:
db_member_dict[constants.VNIC_TYPE] = (
constants.VNIC_TYPE_DIRECT)
else:
db_member_dict[constants.VNIC_TYPE] = (
constants.VNIC_TYPE_NORMAL)
member_dict = db_prepare.create_member(db_member_dict,
self.pool_id,
bool(pool.health_monitor))
self._test_lb_and_listener_and_pool_statuses(context.session)
@@ -204,6 +230,28 @@ class MemberController(base.BaseController):
def _graph_create(self, lock_session, member_dict):
pool = self.repositories.pool.get(lock_session, id=self.pool_id)
# Validate and store port SR-IOV vnic_type
request_sriov = member_dict.pop('request_sriov')
flavor_dict = {}
if pool.load_balancer.flavor_id:
try:
flavor_dict = (
self.repositories.flavor.get_flavor_metadata_dict(
lock_session, pool.load_balancer.flavor_id))
except sa_exception.NoResultFound:
LOG.error("load balancer has a flavor ID: %s that was not "
"found in the database. Assuming no flavor.",
pool.load_balancer.flavor_id)
if (request_sriov and not
flavor_dict.get(constants.ALLOW_MEMBER_SRIOV, False)):
raise exceptions.MemberSRIOVDisabled
if request_sriov:
member_dict[constants.VNIC_TYPE] = constants.VNIC_TYPE_DIRECT
else:
member_dict[constants.VNIC_TYPE] = constants.VNIC_TYPE_NORMAL
member_dict = db_prepare.create_member(
member_dict, self.pool_id, bool(pool.health_monitor))
db_member = self._validate_create_member(lock_session, member_dict)
@@ -440,6 +488,11 @@ class MembersController(MemberController):
m.project_id = db_pool.project_id
db_member_dict = m.to_dict(render_unsets=False)
db_member_dict.pop('id')
# We don't allow updating the vnic_type
# TODO(johnsom) Give the user an error once we change the
# wsme type for batch member update to not use
# the MemberPOST type
db_member_dict.pop(constants.REQUEST_SRIOV)
self.repositories.member.update(
context.session, m.id, **db_member_dict)
+3
View File
@@ -42,6 +42,7 @@ class MemberResponse(BaseMemberType):
monitor_address = wtypes.wsattr(types.IPAddressType())
monitor_port = wtypes.wsattr(wtypes.IntegerType())
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType()))
vnic_type = wtypes.wsattr(wtypes.StringType())
@classmethod
def from_data_model(cls, data_model, children=False):
@@ -85,6 +86,7 @@ class MemberPOST(BaseMemberType):
default=None)
monitor_address = wtypes.wsattr(types.IPAddressType(), default=None)
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
request_sriov = wtypes.wsattr(bool, default=False)
class MemberRootPOST(types.BaseType):
@@ -129,6 +131,7 @@ class MemberSingleCreate(BaseMemberType):
minimum=constants.MIN_PORT_NUMBER, maximum=constants.MAX_PORT_NUMBER))
monitor_address = wtypes.wsattr(types.IPAddressType())
tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255)))
request_sriov = wtypes.wsattr(bool, default=False)
class MemberStatusResponse(BaseMemberType):
+3
View File
@@ -423,6 +423,7 @@ REQ_CONN_TIMEOUT = 'req_conn_timeout'
REQ_READ_TIMEOUT = 'req_read_timeout'
REQUEST_ERRORS = 'request_errors'
REQUEST_ID = 'request_id'
REQUEST_SRIOV = 'request_sriov'
ROLE = 'role'
SECURITY_GROUP_IDS = 'security_group_ids'
SECURITY_GROUPS = 'security_groups'
@@ -588,6 +589,7 @@ ATTACH_PORT = 'attach-port'
CALCULATE_AMPHORA_DELTA = 'calculate-amphora-delta'
CREATE_VIP_BASE_PORT = 'create-vip-base-port'
DELETE_AMPHORA = 'delete-amphora'
DELETE_AMPHORA_MEMBER_PORTS = 'delete-amphora-member-ports'
DELETE_PORT = 'delete-port'
DISABLE_AMP_HEALTH_MONITORING = 'disable-amphora-health-monitoring'
GET_AMPHORA_FIREWALL_RULES = 'get-amphora-firewall-rules'
@@ -929,6 +931,7 @@ AMPHORA_SUPPORTED_ALPN_PROTOCOLS = [lib_consts.ALPN_PROTOCOL_HTTP_2,
lib_consts.ALPN_PROTOCOL_HTTP_1_0]
SRIOV_VIP = 'sriov_vip'
ALLOW_MEMBER_SRIOV = 'allow_member_sriov'
# Amphora interface fields
IF_TYPE = 'if_type'
+13 -1
View File
@@ -378,7 +378,7 @@ class Member(BaseDataModel):
subnet_id=None, operating_status=None, pool=None,
created_at=None, updated_at=None, provisioning_status=None,
name=None, monitor_address=None, monitor_port=None,
tags=None):
tags=None, vnic_type=None):
self.id = id
self.project_id = project_id
self.pool_id = pool_id
@@ -397,6 +397,7 @@ class Member(BaseDataModel):
self.monitor_address = monitor_address
self.monitor_port = monitor_port
self.tags = tags
self.vnic_type = vnic_type
def delete(self):
for mem in self.pool.members:
@@ -904,3 +905,14 @@ class VipSecurityGroup(BaseDataModel):
def __init__(self, load_balancer_id: str = None, sg_id: str = None):
self.load_balancer_id = load_balancer_id
self.sg_id = sg_id
class AmphoraMemberPort(BaseDataModel):
def __init__(self, port_id=None, amphora_id=None, network_id=None,
created_at=None, updated_at=None):
self.port_id = port_id
self.amphora_id = amphora_id
self.network_id = network_id
self.created_at = created_at
self.updated_at = updated_at
+5
View File
@@ -435,3 +435,8 @@ class AmphoraNetworkConfigException(OctaviaException):
class ListenerNoChildren(APIException):
msg = _('Protocol %(protocol)s listeners cannot have child objects.')
code = 400
class MemberSRIOVDisabled(APIException):
msg = _('The load balancer flavor does not allow SR-IOV member ports.')
code = 400
@@ -125,13 +125,13 @@ class NoopManager:
compute_id=compute_id))
connection.commit()
# TODO(johnsom) Add vnic_type here
return network_models.Interface(
id=uuidutils.generate_uuid(),
compute_id=compute_id,
network_id=network_id,
fixed_ips=[],
port_id=uuidutils.generate_uuid(),
vnic_type=constants.VNIC_TYPE_NORMAL
)
def detach_port(self, compute_id, port_id):
@@ -207,6 +207,9 @@ class AmphoraFlows:
name=constants.DELETE_AMPHORA + '-' + amphora_id,
inject={constants.AMPHORA: amphora,
constants.PASSIVE_FAILURE: True}))
delete_amphora_flow.add(network_tasks.DeleteAmphoraMemberPorts(
name=constants.DELETE_AMPHORA_MEMBER_PORTS + '-' + amphora_id,
inject={constants.AMPHORA_ID: amphora[constants.ID]}))
delete_amphora_flow.add(database_tasks.DisableAmphoraHealthMonitoring(
name=constants.DISABLE_AMP_HEALTH_MONITORING + '-' + amphora_id,
inject={constants.AMPHORA: amphora}))
@@ -219,11 +222,6 @@ class AmphoraFlows:
str(amphora[constants.VRRP_PORT_ID])),
inject={constants.PORT_ID: amphora[constants.VRRP_PORT_ID],
constants.PASSIVE_FAILURE: True}))
# TODO(johnsom) What about cleaning up any member ports?
# maybe we should get the list of attached ports prior to delete
# and call delete on them here. Fix this as part of
# https://storyboard.openstack.org/#!/story/2007077
return delete_amphora_flow
def get_vrrp_subflow(self, prefix, timeout_dict=None,
@@ -176,7 +176,8 @@ class ListenerFlows:
fw_rules_subflow.add(
amphora_driver_tasks.AmphoraeGetConnectivityStatus(
name=constants.AMPHORAE_GET_CONNECTIVITY_STATUS,
name=(sf_name + '-' +
constants.AMPHORAE_GET_CONNECTIVITY_STATUS),
requires=constants.AMPHORAE,
inject={constants.TIMEOUT_DICT: timeout_dict,
constants.NEW_AMPHORA_ID: constants.NIL_UUID},
@@ -31,6 +31,7 @@ from octavia.controller.worker.v2.tasks import database_tasks
from octavia.controller.worker.v2.tasks import lifecycle_tasks
from octavia.controller.worker.v2.tasks import network_tasks
from octavia.controller.worker.v2.tasks import notification_tasks
from octavia.db import api as db_apis
from octavia.db import repositories as repo
CONF = cfg.CONF
@@ -41,6 +42,8 @@ class LoadBalancerFlows:
def __init__(self):
self.amp_flows = amphora_flows.AmphoraFlows()
self.amphora_repo = repo.AmphoraRepository()
self.amphora_member_port_repo = repo.AmphoraMemberPortRepository()
self.listener_flows = listener_flows.ListenerFlows()
self.pool_flows = pool_flows.PoolFlows()
self.member_flows = member_flows.MemberFlows()
@@ -336,6 +339,9 @@ class LoadBalancerFlows:
pools_delete = self._get_delete_pools_flow(pools)
delete_LB_flow.add(pools_delete)
delete_LB_flow.add(listeners_delete)
member_ports_delete = self.get_delete_member_ports_subflow(
lb[constants.LOADBALANCER_ID])
delete_LB_flow.add(member_ports_delete)
delete_LB_flow.add(network_tasks.UnplugVIP(
requires=constants.LOADBALANCER))
delete_LB_flow.add(network_tasks.DeallocateVIP(
@@ -749,3 +755,30 @@ class LoadBalancerFlows:
requires=constants.LOADBALANCER))
return failover_LB_flow
def get_delete_member_ports_subflow(self, load_balancer_id):
"""A subflow that will delete all of the member ports on an LB
:param load_balancer_id: A load balancer ID
:returns: A Taskflow flow
"""
port_delete_flow = unordered_flow.Flow('delete_member_ports')
session = db_apis.get_session()
with session.begin():
amps = self.amphora_repo.get_amphorae_ids_on_lb(session,
load_balancer_id)
for amp in amps:
with session.begin():
ports = self.amphora_member_port_repo.get_port_ids(session,
amp)
for port in ports:
port_delete_flow.add(
network_tasks.DeletePort(
name='delete_member_port' + '-' + port,
inject={constants.PORT_ID: port}))
port_delete_flow.add(
database_tasks.DeleteAmpMemberPortInDB(
name='delete_member_port_in_db' + '-' + port,
inject={constants.PORT_ID: port}))
return port_delete_flow
@@ -55,6 +55,7 @@ class BaseDatabaseTask(task.Task):
self.l7policy_repo = repo.L7PolicyRepository()
self.l7rule_repo = repo.L7RuleRepository()
self.task_utils = task_utilities.TaskUtils()
self.amphora_member_port_repo = repo.AmphoraMemberPortRepository()
super().__init__(**kwargs)
def _delete_from_amp_health(self, session, amphora_id):
@@ -420,6 +421,21 @@ class DeleteL7RuleInDB(BaseDatabaseTask):
'except': str(e)})
class DeleteAmpMemberPortInDB(BaseDatabaseTask):
"""Delete an amphora member port record in the DB."""
def execute(self, port_id):
"""Delete the amphora member port in DB
:param port_id: The port_id to be deleted
:returns: None
"""
LOG.debug("Delete in DB for amphora member port %s", port_id)
with db_apis.session().begin() as session:
self.amphora_member_port_repo.delete(session, port_id=port_id)
class ReloadAmphora(BaseDatabaseTask):
"""Get an amphora object from the database."""
@@ -17,16 +17,19 @@ import time
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from sqlalchemy.orm import exc as sa_exception
from taskflow import task
from taskflow.types import failure
import tenacity
from octavia.common import constants
from octavia.common import data_models
from octavia.common import exceptions
from octavia.common import utils
from octavia.controller.worker import task_utils
from octavia.db import api as db_apis
from octavia.db import repositories as repo
from octavia.i18n import _
from octavia.network import base
from octavia.network import data_models as n_data_models
@@ -43,6 +46,7 @@ class BaseNetworkTask(task.Task):
self.task_utils = task_utils.TaskUtils()
self.loadbalancer_repo = repo.LoadBalancerRepository()
self.amphora_repo = repo.AmphoraRepository()
self.amphora_member_port_repo = repo.AmphoraMemberPortRepository()
@property
def network_driver(self):
@@ -78,6 +82,7 @@ class CalculateAmphoraDelta(BaseNetworkTask):
loadbalancer[constants.VIP_NETWORK_ID]
}
net_vnic_type_map = {}
for pool in db_lb.pools:
for member in pool.members:
if (member.subnet_id and
@@ -85,6 +90,8 @@ class CalculateAmphoraDelta(BaseNetworkTask):
constants.PENDING_DELETE):
member_network = self.network_driver.get_subnet(
member.subnet_id).network_id
net_vnic_type_map[member_network] = getattr(
member, 'vnic_type', constants.VNIC_TYPE_NORMAL)
desired_subnet_to_net_map[member.subnet_id] = (
member_network)
@@ -117,7 +124,8 @@ class CalculateAmphoraDelta(BaseNetworkTask):
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])
if net_id == add_net_id],
vnic_type=net_vnic_type_map[add_net_id])
for add_net_id in add_ids]
# Calculate member Subnet deltas
@@ -215,45 +223,6 @@ class GetPlumbedNetworks(BaseNetworkTask):
amphora[constants.COMPUTE_ID])
class PlugNetworks(BaseNetworkTask):
"""Task to plug the networks.
This uses the delta to add all missing networks/nics
"""
def execute(self, amphora, delta):
"""Update the amphora networks for the delta."""
LOG.debug("Plug or unplug networks for amphora id: %s",
amphora[constants.ID])
if not delta:
LOG.debug("No network deltas for amphora id: %s",
amphora[constants.ID])
return
# add nics
for nic in delta[constants.ADD_NICS]:
self.network_driver.plug_network(amphora[constants.COMPUTE_ID],
nic[constants.NETWORK_ID])
def revert(self, amphora, delta, *args, **kwargs):
"""Handle a failed network plug by removing all nics added."""
LOG.warning("Unable to plug networks for amp id %s",
amphora[constants.ID])
if not delta:
return
for nic in delta[constants.ADD_NICS]:
try:
self.network_driver.unplug_network(
amphora[constants.COMPUTE_ID],
nic[constants.NETWORK_ID])
except base.NetworkNotFound:
pass
class UnPlugNetworks(BaseNetworkTask):
"""Task to unplug the networks
@@ -316,6 +285,14 @@ class HandleNetworkDelta(BaseNetworkTask):
fixed_ip.subnet = self.network_driver.get_subnet(
fixed_ip.subnet_id)
def _cleanup_port(self, port_id, compute_id):
try:
self.network_driver.delete_port(port_id)
except Exception:
LOG.error(f'Unable to delete port {port_id} after failing to plug '
f'the port into compute {compute_id}. This port '
f'may now be abandoned in neutron.')
def execute(self, amphora, delta):
"""Handle network plugging based off deltas."""
session = db_apis.get_session()
@@ -324,20 +301,43 @@ class HandleNetworkDelta(BaseNetworkTask):
id=amphora.get(constants.ID))
updated_ports = {}
for nic in delta[constants.ADD_NICS]:
network_id = nic[constants.NETWORK_ID]
subnet_id = nic[constants.FIXED_IPS][0][constants.SUBNET_ID]
interface = self.network_driver.plug_network(
db_amp.compute_id, nic[constants.NETWORK_ID])
port = self.network_driver.get_port(interface.port_id)
# 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)
try:
port = self.network_driver.create_port(
network_id,
name=f'octavia-lb-member-{amphora.get(constants.ID)}',
vnic_type=nic[constants.VNIC_TYPE])
except exceptions.NotFound as e:
if 'Network' in str(e):
raise base.NetworkNotFound(str(e))
raise base.CreatePortException(str(e))
except Exception as e:
message = _(f'Error creating a port on network {network_id}. ')
LOG.exception(message)
raise base.CreatePortException(message) from e
try:
self.network_driver.plug_port(db_amp, port)
except exceptions.NotFound as e:
self._cleanup_port(port.id, db_amp.compute_id)
if 'Instance' in str(e):
raise base.AmphoraNotFound(str(e))
raise base.PlugNetworkException(str(e))
except Exception as e:
self._cleanup_port(port.id, db_amp.compute_id)
message = _('Error plugging amphora (compute_id: '
'{compute_id}) into network {network_id}.').format(
compute_id=db_amp.compute_id, network_id=network_id)
LOG.exception(message)
raise base.PlugNetworkException(message) from e
with session.begin():
self.amphora_member_port_repo.create(
session, port_id=port.id,
amphora_id=amphora.get(constants.ID),
network_id=network_id)
self._fill_port_info(port)
updated_ports[port.network_id] = port.to_dict(recurse=True)
@@ -395,6 +395,14 @@ class HandleNetworkDelta(BaseNetworkTask):
self.network_driver.delete_port(port_id)
except Exception:
LOG.exception("Unable to delete the port")
try:
with session.begin():
self.amphora_member_port_repo.delete(session,
port_id=port_id)
except sa_exception.NoResultFound:
# Passively fail here for upgrade compatibility
LOG.warning("No Amphora member port records found for "
"port_id: %s", port_id)
updated_ports.pop(network_id, None)
return {amphora[constants.ID]: list(updated_ports.values())}
@@ -969,6 +977,22 @@ class DeletePort(BaseNetworkTask):
raise
class DeleteAmphoraMemberPorts(BaseNetworkTask):
"""Task to delete all of the member ports on an Amphora."""
def execute(self, amphora_id, passive_failure=False):
delete_port = DeletePort()
session = db_apis.get_session()
with session.begin():
ports = self.amphora_member_port_repo.get_port_ids(
session, amphora_id)
for port in ports:
delete_port.execute(port, passive_failure)
with session.begin():
self.amphora_member_port_repo.delete(session, port_id=port)
class CreateVIPBasePort(BaseNetworkTask):
"""Task to create the VIP base port for an amphora."""
+2
View File
@@ -61,6 +61,8 @@ class OctaviaBase(models.ModelBase):
obj.load_balancer_id + obj.subnet_id)
if obj.__class__.__name__ in ['VipSecurityGroup']:
return obj.__class__.__name__ + obj.load_balancer_id + obj.sg_id
if obj.__class__.__name__ in ['AmphoraMemberPort']:
return obj.__class__.__name__ + obj.port_id
raise NotImplementedError
def to_data_model(
@@ -0,0 +1,36 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Add member vnic_type
Revision ID: 8db7a6443785
Revises: 3097e55493ae
Create Date: 2024-03-29 20:34:37.263847
"""
from alembic import op
import sqlalchemy as sa
from octavia.common import constants
# revision identifiers, used by Alembic.
revision = '8db7a6443785'
down_revision = '3097e55493ae'
def upgrade():
op.add_column(
u'member',
sa.Column(u'vnic_type', sa.String(64), nullable=False,
server_default=constants.VNIC_TYPE_NORMAL)
)
@@ -0,0 +1,40 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""add_member_port_table
Revision ID: fabf4983846b
Revises: 8db7a6443785
Create Date: 2024-08-30 23:12:01.713217
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'fabf4983846b'
down_revision = '8db7a6443785'
def upgrade():
op.create_table(
'amphora_member_port',
sa.Column('port_id', sa.String(36), primary_key=True),
sa.Column('amphora_id', sa.String(36), nullable=False, index=True),
sa.Column('network_id', sa.String(36)),
sa.Column('created_at', sa.DateTime()),
sa.Column('updated_at', sa.DateTime())
)
op.create_foreign_key(
'fk_member_port_amphora_id', 'amphora_member_port',
'amphora', ['amphora_id'], ['id']
)
+19 -1
View File
@@ -229,6 +229,7 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
nullable=False)
enabled = sa.Column(sa.Boolean(), nullable=False)
pool = orm.relationship("Pool", back_populates="members")
vnic_type = sa.Column(sa.String(64), nullable=True)
_tags = orm.relationship(
'Tags',
@@ -246,7 +247,7 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
f"ip_address={self.ip_address!r}, "
f"protocol_port={self.protocol_port!r}, "
f"operating_status={self.operating_status!r}, "
f"weight={self.weight!r})")
f"weight={self.weight!r}, vnic_type={self.vnic_type!r})")
class HealthMonitor(base_models.BASE, base_models.IdMixin,
@@ -1000,3 +1001,20 @@ class VipSecurityGroup(base_models.BASE):
sa.ForeignKey("vip.load_balancer_id", name="fk_vip_sg_vip_lb_id"),
nullable=False)
sg_id = sa.Column(sa.String(64), nullable=False)
class AmphoraMemberPort(base_models.BASE, models.TimestampMixin):
__data_model__ = data_models.AmphoraMemberPort
__tablename__ = "amphora_member_port"
port_id = sa.Column(
sa.String(36),
primary_key=True)
amphora_id = sa.Column(
sa.String(36),
sa.ForeignKey("amphora.id", name="fk_member_port_amphora_id"),
nullable=False)
network_id = sa.Column(
sa.String(36))
+27
View File
@@ -256,6 +256,7 @@ class Repositories:
self.flavor_profile = FlavorProfileRepository()
self.availability_zone = AvailabilityZoneRepository()
self.availability_zone_profile = AvailabilityZoneProfileRepository()
self.amphora_member_port = AmphoraMemberPortRepository()
def create_load_balancer_and_vip(self, session, lb_dict, vip_dict,
additional_vip_dicts=None):
@@ -1454,6 +1455,20 @@ class AmphoraRepository(BaseRepository):
amp.status = consts.PENDING_DELETE
lock_session.flush()
def get_amphorae_ids_on_lb(self, session, lb_id):
"""Returns a list of amphora IDs associated with the load balancer
:param session: A Sql Alchemy database session.
:param lb_id: A load balancer ID.
:returns: A list of amphora IDs
"""
return session.scalars(
select(
self.model_class.id
).where(
self.model_class.load_balancer_id == lb_id
)).all()
class AmphoraBuildReqRepository(BaseRepository):
model_class = models.AmphoraBuildRequest
@@ -2133,3 +2148,15 @@ class AvailabilityZoneRepository(_GetALLExceptDELETEDIdMixin, BaseRepository):
class AvailabilityZoneProfileRepository(_GetALLExceptDELETEDIdMixin,
BaseRepository):
model_class = models.AvailabilityZoneProfile
class AmphoraMemberPortRepository(BaseRepository):
model_class = models.AmphoraMemberPort
def get_port_ids(self, session, amphora_id):
return session.scalars(
select(
self.model_class.port_id
).where(
self.model_class.amphora_id == amphora_id
)).all()
-10
View File
@@ -163,16 +163,6 @@ class AbstractNetworkDriver(metaclass=abc.ABCMeta):
:raises: UnplugVIPException, PluggedVIPNotFound
"""
@abc.abstractmethod
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
:return: octavia.network.data_models.Interface instance
:raises: PlugNetworkException, AmphoraNotFound, NetworkNotFound
"""
@abc.abstractmethod
def unplug_network(self, compute_id, network_id):
"""Disconnects an existing amphora from an existing network.
+2 -1
View File
@@ -19,12 +19,13 @@ from octavia.common import data_models
class Interface(data_models.BaseDataModel):
def __init__(self, id=None, compute_id=None, network_id=None,
fixed_ips=None, port_id=None):
fixed_ips=None, port_id=None, vnic_type=None):
self.id = id
self.compute_id = compute_id
self.network_id = network_id
self.port_id = port_id
self.fixed_ips = fixed_ips
self.vnic_type = vnic_type
class Delta(data_models.BaseDataModel):
@@ -655,26 +655,6 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
load_balancer.amphorae):
self.unplug_aap_port(vip, amphora, subnet)
def plug_network(self, compute_id, network_id):
try:
interface = self.compute.attach_network_or_port(
compute_id=compute_id, network_id=network_id)
except exceptions.NotFound as e:
if 'Instance' in str(e):
raise base.AmphoraNotFound(str(e))
if 'Network' in str(e):
raise base.NetworkNotFound(str(e))
raise base.PlugNetworkException(str(e))
except Exception as e:
message = _('Error plugging amphora (compute_id: {compute_id}) '
'into network {network_id}.').format(
compute_id=compute_id,
network_id=network_id)
LOG.exception(message)
raise base.PlugNetworkException(message) from e
return self._nova_interface_to_octavia_interface(compute_id, interface)
def unplug_network(self, compute_id, network_id):
interfaces = self.get_plugged_networks(compute_id)
if not interfaces:
+2 -2
View File
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack.network.v2.network_ip_availability import NetworkIPAvailability
from octavia.network import data_models as network_models
@@ -52,7 +51,8 @@ def convert_port_to_model(port):
admin_state_up=port.is_admin_state_up,
fixed_ips=fixed_ips,
qos_policy_id=port.qos_policy_id,
security_group_ids=port.security_group_ids
security_group_ids=port.security_group_ids,
vnic_type=port.binding_vnic_type
)
+3 -28
View File
@@ -198,37 +198,15 @@ class NoopManager:
vip.ip_address)] = (vip, amphora, subnet,
'unplug_aap_port')
def plug_network(self, compute_id, network_id):
LOG.debug("Network %s no-op, plug_network compute_id %s, network_id "
"%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()
)
_NOOP_MANAGER_VARS['ports'][interface.port_id] = (
network_models.Port(
id=interface.port_id,
network_id=network_id))
_NOOP_MANAGER_VARS['interfaces'][(network_id, compute_id)] = (
interface)
return interface
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)] = (
compute_id, network_id, 'unplug_network')
_NOOP_MANAGER_VARS['interfaces'].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",
LOG.debug("Network %s no-op, get_plugged_networks compute_id %s",
self.__class__.__name__, compute_id)
self.networkconfigconfig[compute_id] = (
compute_id, 'get_plugged_networks')
@@ -253,11 +231,11 @@ class NoopManager:
subnet_id=fixed_ip.subnet_id,
ip_address=fixed_ip.ip_address))
# Add the interface object to the list
# TODO(johnsom) Fix this to have the vnic_type
interfaces.append(network_models.Interface(
compute_id=interface.compute_id,
network_id=interface.network_id,
port_id=interface.port_id, fixed_ips=fixed_ips))
port_id=interface.port_id, fixed_ips=fixed_ips,
vnic_type=interface.vnic_type))
return interfaces
def update_vip(self, loadbalancer, for_delete=False):
@@ -589,9 +567,6 @@ class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
def unplug_vip(self, loadbalancer, vip):
self.driver.unplug_vip(loadbalancer, vip)
def plug_network(self, compute_id, network_id):
return self.driver.plug_network(compute_id, network_id)
def unplug_network(self, compute_id, network_id):
self.driver.unplug_network(compute_id, network_id)
+2 -1
View File
@@ -139,7 +139,8 @@ MOCK_NEUTRON_PORT = Port(**{'network_id': MOCK_NETWORK_ID,
'mac_address': MOCK_MAC_ADDR,
'fixed_ips': [{'ip_address': MOCK_IP_ADDRESS,
'subnet_id': MOCK_SUBNET_ID}],
'security_groups': [MOCK_SECURITY_GROUP_ID]})
'security_groups': [MOCK_SECURITY_GROUP_ID],
'binding_vnic_type': constants.VNIC_TYPE_NORMAL})
MOCK_NEUTRON_QOS_POLICY_ID = 'mock-qos-id'
MOCK_QOS_POLICY_ID1 = 'qos1-id'
MOCK_QOS_POLICY_ID2 = 'qos2-id'
+20 -13
View File
@@ -148,7 +148,8 @@ class SampleDriverDataModels:
constants.CREATED_AT: self.created_at,
constants.UPDATED_AT: self.updated_at,
lib_consts.MONITOR_ADDRESS: '192.0.2.26',
lib_consts.MONITOR_PORT: 81}
lib_consts.MONITOR_PORT: 81,
lib_consts.VNIC_TYPE: lib_consts.VNIC_TYPE_NORMAL}
self.test_member1_dict.update(self._common_test_dict)
@@ -157,6 +158,8 @@ class SampleDriverDataModels:
self.test_member2_dict[constants.IP_ADDRESS] = '192.0.2.17'
self.test_member2_dict[lib_consts.MONITOR_ADDRESS] = '192.0.2.27'
self.test_member2_dict[lib_consts.NAME] = 'member2'
self.test_member2_dict[
lib_consts.VNIC_TYPE] = lib_consts.VNIC_TYPE_DIRECT
self.test_member3_dict = copy.deepcopy(self.test_member1_dict)
self.test_member3_dict[lib_consts.ID] = self.member3_id
@@ -185,24 +188,28 @@ class SampleDriverDataModels:
self.db_pool1_members = [self.db_member1, self.db_member2]
self.db_pool2_members = [self.db_member3, self.db_member4]
self.provider_member1_dict = {lib_consts.ADDRESS: '192.0.2.16',
lib_consts.ADMIN_STATE_UP: True,
lib_consts.MEMBER_ID: self.member1_id,
lib_consts.MONITOR_ADDRESS: '192.0.2.26',
lib_consts.MONITOR_PORT: 81,
lib_consts.NAME: 'member1',
lib_consts.POOL_ID: self.pool1_id,
lib_consts.PROJECT_ID: self.project_id,
lib_consts.PROTOCOL_PORT: 80,
lib_consts.SUBNET_ID: self.subnet_id,
lib_consts.WEIGHT: 0,
lib_consts.BACKUP: False}
self.provider_member1_dict = {
lib_consts.ADDRESS: '192.0.2.16',
lib_consts.ADMIN_STATE_UP: True,
lib_consts.MEMBER_ID: self.member1_id,
lib_consts.MONITOR_ADDRESS: '192.0.2.26',
lib_consts.MONITOR_PORT: 81,
lib_consts.NAME: 'member1',
lib_consts.POOL_ID: self.pool1_id,
lib_consts.PROJECT_ID: self.project_id,
lib_consts.PROTOCOL_PORT: 80,
lib_consts.SUBNET_ID: self.subnet_id,
lib_consts.WEIGHT: 0,
lib_consts.BACKUP: False,
lib_consts.VNIC_TYPE: lib_consts.VNIC_TYPE_NORMAL}
self.provider_member2_dict = copy.deepcopy(self.provider_member1_dict)
self.provider_member2_dict[lib_consts.MEMBER_ID] = self.member2_id
self.provider_member2_dict[lib_consts.ADDRESS] = '192.0.2.17'
self.provider_member2_dict[lib_consts.MONITOR_ADDRESS] = '192.0.2.27'
self.provider_member2_dict[lib_consts.NAME] = 'member2'
self.provider_member2_dict[
lib_consts.VNIC_TYPE] = lib_consts.VNIC_TYPE_DIRECT
self.provider_member3_dict = copy.deepcopy(self.provider_member1_dict)
self.provider_member3_dict[lib_consts.MEMBER_ID] = self.member3_id
@@ -503,7 +503,8 @@ class DriverAgentTest(base.OctaviaDBTestBase):
lib_consts.MONITOR_PORT,
lib_consts.SUBNET_ID,
lib_consts.WEIGHT,
lib_consts.BACKUP):
lib_consts.BACKUP,
lib_consts.VNIC_TYPE):
self.assertEqual(provider_member_dict.get(key),
result_member_dict.get(key))
@@ -2837,7 +2837,7 @@ class TestLoadBalancerGraph(base.BaseAPITest):
def _get_lb_bodies(self, create_listeners, expected_listeners,
create_pools=None, additional_vips=None,
vip_sg_ids=None):
vip_sg_ids=None, flavor_id=None, sriov=False):
create_lb = {
'name': 'lb1',
'project_id': self._project_id,
@@ -2871,6 +2871,11 @@ class TestLoadBalancerGraph(base.BaseAPITest):
'vip_vnic_type': constants.VNIC_TYPE_NORMAL,
'vip_sg_ids': vip_sg_ids or [],
}
if flavor_id:
create_lb['flavor_id'] = flavor_id
expected_lb['flavor_id'] = flavor_id
if sriov:
expected_lb['vip_vnic_type'] = constants.VNIC_TYPE_DIRECT
expected_lb.update(create_lb)
expected_lb['listeners'] = expected_listeners
expected_lb['pools'] = create_pools or []
@@ -3043,7 +3048,7 @@ class TestLoadBalancerGraph(base.BaseAPITest):
expected_pool['healthmonitor'] = expected_hm
return create_pool, expected_pool
def _get_member_bodies(self, protocol_port=80):
def _get_member_bodies(self, protocol_port=80, sriov=False):
create_member = {
'address': '10.0.0.1',
'protocol_port': protocol_port
@@ -3056,6 +3061,9 @@ class TestLoadBalancerGraph(base.BaseAPITest):
'project_id': self._project_id,
'tags': []
}
if sriov:
create_member[constants.REQUEST_SRIOV] = True
expected_member[constants.VNIC_TYPE] = constants.VNIC_TYPE_DIRECT
expected_member.update(create_member)
return create_member, expected_member
@@ -3396,6 +3404,47 @@ class TestLoadBalancerGraph(base.BaseAPITest):
api_lb = response.json.get(self.root_tag)
self._assert_graphs_equal(expected_lb, api_lb)
def test_with_one_listener_one_member_sriov(self):
flavor_profile = self.create_flavor_profile(
'sriov-graph-create', 'noop_driver',
f'{{"{constants.ALLOW_MEMBER_SRIOV}": true, '
f'"{constants.SRIOV_VIP}": true}}')
flavor = self.create_flavor('sriov-graph-create', '',
flavor_profile['id'], True)
create_member, expected_member = self._get_member_bodies(sriov=True)
create_pool, expected_pool = self._get_pool_bodies(
create_members=[create_member],
expected_members=[expected_member])
create_listener, expected_listener = self._get_listener_bodies(
create_default_pool_name=create_pool['name'])
create_lb, expected_lb = self._get_lb_bodies(
create_listeners=[create_listener],
expected_listeners=[expected_listener],
create_pools=[create_pool], flavor_id=flavor['id'], sriov=True)
body = self._build_body(create_lb)
response = self.post(self.LBS_PATH, body)
api_lb = response.json.get(self.root_tag)
self._assert_graphs_equal(expected_lb, api_lb)
def test_with_one_listener_one_member_sriov_disabled(self):
create_member, expected_member = self._get_member_bodies(sriov=True)
create_pool, expected_pool = self._get_pool_bodies(
create_members=[create_member],
expected_members=[expected_member])
create_listener, expected_listener = self._get_listener_bodies(
create_default_pool_name=create_pool['name'])
create_lb, expected_lb = self._get_lb_bodies(
create_listeners=[create_listener],
expected_listeners=[expected_listener],
create_pools=[create_pool])
body = self._build_body(create_lb)
response = self.post(self.LBS_PATH, body, status=400)
error_text = response.json.get('faultstring')
self.assertIn('flavor does not allow SR-IOV member ports.',
error_text)
def test_with_one_listener_one_hm(self):
create_hm, expected_hm = self._get_hm_bodies()
create_pool, expected_pool = self._get_pool_bodies(
@@ -18,6 +18,7 @@ from octavia_lib.api.drivers import data_models as driver_dm
from oslo_config import cfg
from oslo_config import fixture as oslo_fixture
from oslo_utils import uuidutils
from sqlalchemy.orm import exc as sa_exception
from octavia.api.drivers import utils as driver_utils
from octavia.common import constants
@@ -623,6 +624,55 @@ class TestMember(base.BaseAPITest):
self.assertIn('Provider \'bad_driver\' reports error: broken',
response.get('faultstring'))
def test_create_with_sriov(self):
flavor_profile = self.create_flavor_profile(
'sriov-member-create', 'noop_driver',
f'{{"{constants.ALLOW_MEMBER_SRIOV}": true}}')
flavor = self.create_flavor('sriov-member-create', '',
flavor_profile['id'], True)
vip_subnet_id = uuidutils.generate_uuid()
lb = self.create_load_balancer(vip_subnet_id, flavor_id=flavor['id'])
lb_id = lb.get('loadbalancer').get('id')
self.set_lb_status(lb_id)
listener = self.create_listener(
constants.PROTOCOL_HTTP, 80,
lb_id=lb_id)
listener_id = listener.get('listener').get('id')
self.set_lb_status(lb_id)
pool_with_listener = self.create_pool(
lb_id, constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN, listener_id=listener_id)
pool_with_listener_id = pool_with_listener.get('pool').get('id')
self.set_lb_status(lb_id)
api_member = self.create_member(
pool_with_listener_id, '192.0.2.1',
80, request_sriov=True).get(self.root_tag)
self.assertEqual(constants.VNIC_TYPE_DIRECT,
api_member[constants.VNIC_TYPE])
def test_create_with_sriov_disabled(self):
# Test with no flavor enabling SR-IOV members
response = self.create_member(
self.pool_id, '192.0.2.1',
80, request_sriov=True, status=400)
self.assertIn('flavor does not allow SR-IOV member ports',
response.get('faultstring'))
@mock.patch('octavia.db.repositories.FlavorRepository.'
'get_flavor_metadata_dict')
def test_create_with_sriov_missing_flavor(self, mock_get_flavor):
mock_get_flavor.side_effect = sa_exception.NoResultFound()
response = self.create_member(
self.pool_id, '192.0.2.1',
80, request_sriov=True, status=400)
self.assertIn('flavor does not allow SR-IOV member ports',
response.get('faultstring'))
@mock.patch('octavia.api.drivers.driver_factory.get_driver')
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_full_batch_members(self, mock_provider, mock_get_driver):
+2 -1
View File
@@ -289,7 +289,8 @@ class MemberModelTest(base.OctaviaDBTestBase, ModelTestMixin):
f"project_id={member.project_id!r}, "
f"provisioning_status='ACTIVE', "
f"ip_address='10.0.0.1', protocol_port=80, "
f"operating_status='ONLINE', weight=None)",
f"operating_status='ONLINE', weight=None, "
f"vnic_type=None)",
str(member))
self.assertIsNotNone(member.created_at)
@@ -135,7 +135,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'amp_build_slots', 'amp_build_req', 'quotas',
'flavor', 'flavor_profile', 'listener_cidr',
'availability_zone', 'availability_zone_profile',
'additional_vip')
'additional_vip', 'amphora_member_port')
for repo_attr in repo_attr_names:
single_repo = getattr(self.repos, repo_attr, None)
message = (f"Class Repositories should have {repo_attr} instance "
@@ -780,7 +780,8 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.driver.clients[API_VERSION].plug_network.assert_called_once_with(
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=[],
mtu=FAKE_MTU))
mtu=FAKE_MTU,
is_sriov=False))
self.driver.clients[API_VERSION].plug_network.reset_mock()
@@ -792,7 +793,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
subnet_cidr='198.51.100.0/24',
host_routes=[],
gateway=FAKE_GATEWAY)],
mtu=FAKE_MTU))
mtu=FAKE_MTU, is_sriov=False))
self.driver.clients[API_VERSION].plug_network.reset_mock()
@@ -819,7 +820,7 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
additional_vips=[],
mtu=FAKE_MTU,
is_sriov=False
)))
), is_sriov=False))
def test_post_network_plug_with_host_routes(self):
SUBNET_ID = 'SUBNET_ID'
@@ -859,7 +860,8 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.driver.clients[API_VERSION].plug_network.assert_called_once_with(
self.amp, dict(mac_address=FAKE_MAC_ADDRESS,
fixed_ips=expected_fixed_ips,
mtu=FAKE_MTU))
mtu=FAKE_MTU,
is_sriov=False))
def test_get_haproxy_versions(self):
ref_haproxy_versions = ['1', '6']
@@ -18,6 +18,7 @@ from oslo_config import cfg
from oslo_config import fixture as oslo_fixture
from oslo_utils import uuidutils
from taskflow.patterns import linear_flow as flow
from taskflow.patterns import unordered_flow
from octavia.common import constants
from octavia.common import exceptions
@@ -544,3 +545,27 @@ class TestLoadBalancerFlows(base.TestCase):
self._test_get_failover_LB_flow_no_amps_act_stdby([amphora_dict,
amphora2_dict])
@mock.patch('octavia.db.repositories.AmphoraMemberPortRepository.'
'get_port_ids')
@mock.patch('octavia.db.repositories.AmphoraRepository.'
'get_amphorae_ids_on_lb')
@mock.patch('octavia.db.api.get_session', return_value=mock.MagicMock())
def test_get_delete_member_ports_subflow(self,
mock_session,
mock_amps_on_lb,
mock_get_port_ids,
mock_get_net_driver):
lb_id = uuidutils.generate_uuid()
amps = ['fake_amp1', 'fake_amp2']
port1 = uuidutils.generate_uuid()
port2 = uuidutils.generate_uuid()
ports = [port1, port2]
mock_amps_on_lb.return_value = amps
mock_get_port_ids.return_value = ports
delete_flow = self.LBFlow.get_delete_member_ports_subflow(lb_id)
self.assertIsInstance(delete_flow, unordered_flow.Flow)
self.assertEqual(8, len(delete_flow))
@@ -334,6 +334,7 @@ class TestNetworkTasks(base.TestCase):
mock_driver.reset_mock()
member_mock = mock.MagicMock()
member_mock.subnet_id = member_private_subnet.id
member_mock.vnic_type = constants.VNIC_TYPE_NORMAL
member2_mock = mock.MagicMock()
member2_mock.subnet_id = member_private_subnet2.id
pool_mock.members = [member_mock]
@@ -351,7 +352,8 @@ class TestNetworkTasks(base.TestCase):
network_id=3,
fixed_ips=[
data_models.FixedIP(
subnet_id=member_private_subnet.id)])],
subnet_id=member_private_subnet.id)],
vnic_type=constants.VNIC_TYPE_NORMAL)],
delete_nics=[],
add_subnets=[{
'subnet_id': member_private_subnet.id,
@@ -431,6 +433,7 @@ class TestNetworkTasks(base.TestCase):
mock_driver.reset_mock()
member_mock = mock.MagicMock()
member_mock.subnet_id = member_private_subnet.id
member_mock.vnic_type = constants.VNIC_TYPE_NORMAL
pool_mock.members = [member_mock]
az = {
constants.COMPUTE_ZONE: 'foo'
@@ -451,7 +454,8 @@ class TestNetworkTasks(base.TestCase):
add_nics=[data_models.Interface(
network_id=member_private_net.id,
fixed_ips=[data_models.FixedIP(
subnet_id=member_private_subnet.id)])],
subnet_id=member_private_subnet.id)],
vnic_type=constants.VNIC_TYPE_NORMAL)],
delete_nics=[data_models.Interface(network_id='bad_net')],
add_subnets=[{
'subnet_id': member_private_subnet.id,
@@ -611,63 +615,6 @@ class TestNetworkTasks(base.TestCase):
mock_driver.get_plugged_networks.assert_called_once_with(
COMPUTE_ID)
def test_plug_networks(self, mock_get_net_driver):
mock_driver = mock.MagicMock()
mock_get_net_driver.return_value = mock_driver
def _interface(network_id):
return [data_models.Interface(network_id=network_id)]
net = network_tasks.PlugNetworks()
net.execute(self.amphora_mock, None)
self.assertFalse(mock_driver.plug_network.called)
delta = data_models.Delta(amphora_id=self.db_amphora_mock.id,
compute_id=self.db_amphora_mock.compute_id,
add_nics=[],
delete_nics=[]).to_dict(recurse=True)
net.execute(self.amphora_mock, delta)
self.assertFalse(mock_driver.plug_network.called)
delta = data_models.Delta(amphora_id=self.db_amphora_mock.id,
compute_id=self.db_amphora_mock.compute_id,
add_nics=_interface(1),
delete_nics=[]).to_dict(recurse=True)
net.execute(self.amphora_mock, delta)
mock_driver.plug_network.assert_called_once_with(COMPUTE_ID, 1)
# revert
net.revert(self.amphora_mock, None)
self.assertFalse(mock_driver.unplug_network.called)
delta = data_models.Delta(amphora_id=self.db_amphora_mock.id,
compute_id=self.db_amphora_mock.compute_id,
add_nics=[],
delete_nics=[]).to_dict(recurse=True)
net.revert(self.amphora_mock, delta)
self.assertFalse(mock_driver.unplug_network.called)
delta = data_models.Delta(amphora_id=self.db_amphora_mock.id,
compute_id=self.db_amphora_mock.compute_id,
add_nics=_interface(1),
delete_nics=[]).to_dict(recurse=True)
net.revert(self.amphora_mock, delta)
mock_driver.unplug_network.assert_called_once_with(COMPUTE_ID, 1)
mock_driver.reset_mock()
mock_driver.unplug_network.side_effect = net_base.NetworkNotFound
net.revert(self.amphora_mock, delta) # No exception
mock_driver.unplug_network.assert_called_once_with(COMPUTE_ID, 1)
mock_driver.reset_mock()
mock_driver.unplug_network.side_effect = TestException('test')
self.assertRaises(TestException,
net.revert,
self.amphora_mock,
delta)
mock_driver.unplug_network.assert_called_once_with(COMPUTE_ID, 1)
def test_unplug_networks(self, mock_get_net_driver):
mock_driver = mock.MagicMock()
mock_get_net_driver.return_value = mock_driver
@@ -755,6 +702,7 @@ class TestNetworkTasks(base.TestCase):
nic1.fixed_ips = [data_models.FixedIP(
subnet_id=uuidutils.generate_uuid())]
nic1.network_id = uuidutils.generate_uuid()
nic1.nic_type = constants.VNIC_TYPE_NORMAL
nic2 = data_models.Interface()
nic2.fixed_ips = [data_models.FixedIP(
subnet_id=uuidutils.generate_uuid())]
@@ -779,8 +727,18 @@ class TestNetworkTasks(base.TestCase):
delete_subnets=[]
).to_dict(recurse=True)
mock_net_driver.plug_network.return_value = interface1
mock_net_driver.get_port.return_value = port1
mock_net_driver.plug_port.side_effect = [
interface1,
exceptions.NotFound(resource='Instance', id='1'),
exceptions.NotFound(resource='Boom', id='1'),
Exception("boom")]
mock_net_driver.create_port.side_effect = [
port1,
port1,
exceptions.NotFound(resource='Network', id='1'),
port1,
port1]
fixed_port1 = mock.MagicMock()
fixed_port1.network_id = port1.network_id
fixed_port1.fixed_ips = [fixed_ip]
@@ -795,20 +753,34 @@ class TestNetworkTasks(base.TestCase):
result = handle_net_delta_obj.execute(self.amphora_mock,
delta)
mock_net_driver.plug_network.assert_called_once_with(
self.db_amphora_mock.compute_id, nic1.network_id)
mock_net_driver.unplug_fixed_ip.assert_called_once_with(
port_id=interface1.port_id, subnet_id=fixed_ip2.subnet_id)
mock_net_driver.get_port.assert_called_once_with(interface1.port_id)
mock_net_driver.plug_port.assert_called_once_with(
self.db_amphora_mock, port1)
mock_net_driver.get_network.assert_called_once_with(port1.network_id)
mock_net_driver.get_subnet.assert_called_once_with(fixed_ip.subnet_id)
mock_net_driver.get_subnet.assert_has_calls(
[mock.call(fixed_ip.subnet_id), mock.call(fixed_ip2.subnet_id)])
self.assertEqual({self.db_amphora_mock.id: [fixed_port1.to_dict()]},
self.assertEqual({self.db_amphora_mock.id: [port1.to_dict()]},
result)
mock_net_driver.unplug_network.assert_called_with(
self.db_amphora_mock.compute_id, nic2.network_id)
self.assertRaises(net_base.AmphoraNotFound,
handle_net_delta_obj.execute, self.amphora_mock,
delta)
self.assertRaises(net_base.NetworkNotFound,
handle_net_delta_obj.execute, self.amphora_mock,
delta)
self.assertRaises(net_base.PlugNetworkException,
handle_net_delta_obj.execute, self.amphora_mock,
delta)
self.assertRaises(net_base.PlugNetworkException,
handle_net_delta_obj.execute, self.amphora_mock,
delta)
# Revert
delta2 = data_models.Delta(amphora_id=self.db_amphora_mock.id,
compute_id=self.db_amphora_mock.compute_id,
@@ -857,12 +829,13 @@ class TestNetworkTasks(base.TestCase):
port_id=port_id,
fixed_ips=[
data_models.FixedIP(
subnet_id=subnet_id)])
subnet_id=subnet_id)],
vnic_type=constants.VNIC_TYPE_NORMAL)
net = network_tasks.HandleNetworkDeltas()
net.execute({}, self.load_balancer_mock)
self.assertFalse(mock_driver.plug_network.called)
self.assertFalse(mock_driver.create_port.called)
delta = data_models.Delta(amphora_id=self.db_amphora_mock.id,
compute_id=self.db_amphora_mock.compute_id,
@@ -871,7 +844,7 @@ class TestNetworkTasks(base.TestCase):
add_subnets=[],
delete_subnets=[]).to_dict(recurse=True)
net.execute({self.db_amphora_mock.id: delta}, self.load_balancer_mock)
self.assertFalse(mock_driver.plug_network.called)
self.assertFalse(mock_driver.create_port.called)
# Adding a subnet on a new network
port = data_models.Port(
@@ -879,7 +852,7 @@ class TestNetworkTasks(base.TestCase):
network_id=network1,
fixed_ips=[
data_models.FixedIP(subnet_id=subnet1)])
mock_driver.get_port.return_value = port
mock_driver.create_port.return_value = port
mock_driver.plug_fixed_ip.return_value = port
mock_driver.get_network.return_value = data_models.Network(
id=network1)
@@ -900,8 +873,8 @@ class TestNetworkTasks(base.TestCase):
delete_subnets=[]).to_dict(recurse=True)
updated_ports = net.execute({self.db_amphora_mock.id: delta},
self.load_balancer_mock)
mock_driver.plug_network.assert_called_once_with(
self.db_amphora_mock.compute_id, network1)
mock_driver.plug_port.assert_called_once_with(
self.db_amphora_mock, port)
mock_driver.unplug_network.assert_not_called()
self.assertEqual(1, len(updated_ports))
@@ -949,7 +922,6 @@ class TestNetworkTasks(base.TestCase):
delete_subnets=[]).to_dict(recurse=True)
updated_ports = net.execute({self.db_amphora_mock.id: delta},
self.load_balancer_mock)
mock_driver.plug_network.assert_not_called()
mock_driver.unplug_network.assert_not_called()
mock_driver.get_port.assert_not_called()
mock_driver.plug_fixed_ip.assert_called_once_with(port_id=port1,
@@ -989,7 +961,6 @@ class TestNetworkTasks(base.TestCase):
updated_ports = net.execute({self.db_amphora_mock.id: delta},
self.load_balancer_mock)
mock_driver.delete_port.assert_not_called()
mock_driver.plug_network.assert_not_called()
mock_driver.plug_fixed_ip.assert_not_called()
mock_driver.unplug_fixed_ip.assert_called_once_with(
port_id=port1, subnet_id=subnet1)
@@ -1014,7 +985,6 @@ class TestNetworkTasks(base.TestCase):
net.execute({self.db_amphora_mock.id: delta},
self.load_balancer_mock)
mock_driver.delete_port.assert_not_called()
mock_driver.plug_network.assert_not_called()
mock_driver.plug_fixed_ip.assert_not_called()
mock_driver.unplug_fixed_ip.assert_not_called()
@@ -1043,7 +1013,6 @@ class TestNetworkTasks(base.TestCase):
updated_ports = net.execute({self.db_amphora_mock.id: delta},
self.load_balancer_mock)
mock_driver.delete_port.assert_called_once_with(port1)
mock_driver.plug_network.assert_not_called()
mock_driver.plug_fixed_ip.assert_not_called()
self.assertEqual(1, len(updated_ports))
self.assertEqual(0, len(updated_ports[self.db_amphora_mock.id]))
@@ -996,47 +996,6 @@ class TestAllowedAddressPairsDriver(base.TestCase):
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
network_attach = self.driver.compute.attach_network_or_port
network_attach.side_effect = exceptions.NotFound(
resource='Instance not found', id=1)
self.assertRaises(network_base.AmphoraNotFound,
self.driver.plug_network,
t_constants.MOCK_COMPUTE_ID, net_id)
def test_plug_network_when_network_cant_be_found(self):
net_id = t_constants.MOCK_NOVA_INTERFACE.net_id
network_attach = self.driver.compute.attach_network_or_port
network_attach.side_effect = nova_exceptions.NotFound(
404, message='Network not found')
self.assertRaises(network_base.NetworkException,
self.driver.plug_network,
t_constants.MOCK_COMPUTE_ID, net_id)
def test_plug_network_when_interface_attach_fails(self):
net_id = t_constants.MOCK_NOVA_INTERFACE.net_id
network_attach = self.driver.compute.attach_network_or_port
network_attach.side_effect = TypeError
self.assertRaises(network_base.PlugNetworkException,
self.driver.plug_network,
t_constants.MOCK_COMPUTE_ID, net_id)
def test_plug_network(self):
net_id = t_constants.MOCK_NOVA_INTERFACE.net_id
network_attach = self.driver.compute.attach_network_or_port
network_attach.return_value = t_constants.MOCK_NOVA_INTERFACE
oct_interface = self.driver.plug_network(
t_constants.MOCK_COMPUTE_ID, net_id)
exp_ips = [fixed_ip.get('ip_address')
for fixed_ip in t_constants.MOCK_NOVA_INTERFACE.fixed_ips]
actual_ips = [fixed_ip.ip_address
for fixed_ip in oct_interface.fixed_ips]
self.assertEqual(exp_ips, actual_ips)
self.assertEqual(t_constants.MOCK_COMPUTE_ID,
oct_interface.compute_id)
self.assertEqual(net_id, oct_interface.network_id)
def test_unplug_network_when_compute_port_cant_be_found(self):
net_id = t_constants.MOCK_NOVA_INTERFACE.net_id
list_ports = self.driver.network_proxy.ports
@@ -1614,7 +1573,8 @@ class TestAllowedAddressPairsDriver(base.TestCase):
'fixed_ips': [{'ip_address': IP_ADDRESS1,
'subnet_id': SUBNET1_ID}],
'security_groups': [],
'qos_policy_id': QOS_POLICY_ID})
'qos_policy_id': QOS_POLICY_ID,
'binding_vnic_type': constants.VNIC_TYPE_NORMAL})
reference_port_dict = {'admin_state_up': ADMIN_STATE_UP,
'device_id': t_constants.MOCK_DEVICE_ID,
@@ -15,6 +15,7 @@ from unittest import mock
from oslo_utils import uuidutils
from octavia.common import constants
from octavia.db import models
from octavia.network import data_models as network_models
from octavia.network.drivers.noop_driver import driver
@@ -49,12 +50,12 @@ class TestNoopNetworkDriver(base.TestCase):
self.network_id = self.FAKE_UUID_3
self.network_name = 'net1'
self.device_id = self.FAKE_UUID_4
self.ip_address = "10.0.0.2"
self.ip_address = "192.0.2.2"
self.load_balancer = models.LoadBalancer()
self.load_balancer.id = self.FAKE_UUID_2
self.vip = models.Vip()
self.vip.ip_address = "10.0.0.1"
self.vip.ip_address = "192.0.2.1"
self.vip.subnet_id = uuidutils.generate_uuid()
self.vip.port_id = uuidutils.generate_uuid()
self.amphora_id = self.FAKE_UUID_1
@@ -70,15 +71,15 @@ class TestNoopNetworkDriver(base.TestCase):
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.amphora1.vrrp_ip = '192.0.2.10'
self.amphora1.ha_ip = '192.0.2.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'
self.amphora2.ha_ip = '10.0.2.11'
self.amphora2.vrrp_ip = '192.0.2.20'
self.amphora2.ha_ip = '192.0.2.21'
self.load_balancer.amphorae = [self.amphora1, self.amphora2]
self.load_balancer.vip = self.vip
self.subnet = mock.MagicMock()
@@ -119,12 +120,6 @@ class TestNoopNetworkDriver(base.TestCase):
self.driver.driver.networkconfigconfig[(
self.load_balancer.id, self.vip.ip_address)])
def test_plug_network(self):
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.compute_id, self.network_id)])
def test_unplug_network(self):
self.driver.unplug_network(self.compute_id, self.network_id)
self.assertEqual((self.compute_id, self.network_id, 'unplug_network'),
@@ -132,53 +127,40 @@ class TestNoopNetworkDriver(base.TestCase):
self.compute_id, self.network_id)])
def test_get_plugged_networks(self):
self.driver.get_plugged_networks(self.compute_id)
self.assertEqual((self.compute_id, 'get_plugged_networks'),
self.driver.driver.networkconfigconfig[(
self.compute_id)])
interface_db_mock = mock.MagicMock()
interface_db_mock.port_id = self.port_id
interface_db_mock.network_id = self.network_id
interface_db_mock.compute_id = self.compute_id
interface_db_mock.vnic_type = constants.VNIC_TYPE_NORMAL
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
))
fixed_ips_db_mock = mock.MagicMock()
fixed_ips_db_mock.port_id = self.port_id
fixed_ips_db_mock.subnet_id = self.subnet_id
fixed_ips_db_mock.ip_address = self.ip_address
# mock out the sqlite db calls
connect_mock = mock.MagicMock()
connection_mock = mock.MagicMock()
self.mock_engine.connect.return_value = connect_mock
connect_mock.__enter__.return_value = connection_mock
mock_interface = mock.MagicMock()
mock_interface.compute_id = amphora.compute_id
mock_interface.network_id = self.network_id
mock_interface.port_id = '1'
connection_mock.execute.side_effect = [[mock_interface], [], []]
connection_mock.execute.side_effect = [[interface_db_mock],
[fixed_ips_db_mock]]
networks = self.driver.get_plugged_networks(amphora.compute_id)
result = self.driver.get_plugged_networks(self.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)
self.assertEqual((self.compute_id, 'get_plugged_networks'),
self.driver.driver.networkconfigconfig[(
self.compute_id)])
expected_fixed_ips = [network_models.FixedIP(
subnet_id=self.subnet_id, ip_address=self.ip_address)]
expected_interfaces = [network_models.Interface(
compute_id=self.compute_id, network_id=self.network_id,
port_id=self.port_id, fixed_ips=expected_fixed_ips,
vnic_type=constants.VNIC_TYPE_NORMAL)]
self.assertEqual(expected_interfaces, result)
def test_update_vip(self):
self.driver.update_vip(self.load_balancer)
@@ -0,0 +1,9 @@
---
features:
- |
Octavia Amphora based load balancers now support using SR-IOV virtual
functions (VF) on the member ports.
upgrade:
- |
You must update the amphora image to support the SR-IOV member port
feature.
+1 -1
View File
@@ -45,7 +45,7 @@ castellan>=0.16.0 # Apache-2.0
tenacity>=5.0.4 # Apache-2.0
distro>=1.2.0 # Apache-2.0
jsonschema>=3.2.0 # MIT
octavia-lib>=3.3.0 # Apache-2.0
octavia-lib>=3.6.0 # Apache-2.0
setproctitle>=1.1.10 # BSD
python-dateutil>=2.7.0 # BSD
+14 -5
View File
@@ -17,6 +17,7 @@
import argparse
import importlib
import os
from unittest.mock import patch
import graphviz
from taskflow import engines
@@ -94,11 +95,19 @@ def generate(flow_list, output_directory):
{constants.POOL_ID:
'08ada7a2-3eff-42c6-bdd8-b6f2ecd73358'}]
lb = dmh.generate_load_balancer()
if 'v2' in current_tuple[0]:
lb = utils.lb_dict_to_provider_dict(lb.to_dict())
delete_flow = get_flow_method(lb, listeners, pools)
else:
delete_flow, store = get_flow_method(lb)
with patch('octavia.db.repositories.AmphoraRepository.'
'get_amphorae_ids_on_lb',
return_value=[
'a9aa2b0b-0442-471e-b400-e04847e3ef1f']):
with patch('octavia.db.repositories.'
'AmphoraMemberPortRepository.get_port_ids',
return_value=[
'6e03e9ad-726a-46ee-90e0-1cad753ba1b0']):
if 'v2' in current_tuple[0]:
lb = utils.lb_dict_to_provider_dict(lb.to_dict())
delete_flow = get_flow_method(lb, listeners, pools)
else:
delete_flow, store = get_flow_method(lb)
current_engine = engines.load(delete_flow)
elif (current_tuple[1] == 'LoadBalancerFlows' and
current_tuple[2] == 'get_failover_LB_flow'):