Files
ovn-octavia-provider/ovn_octavia_provider/tests/functional/base.py
Fernando Royo 84d6dfca43 [FT] Unlock the functional jobs
Functional jobs are failing due to two consecutive calls that are
calling to update the LB VIP port (finally calling to
SetLSwitchPortCommand) are not propagating the second one.

Consequently the port_name is not setted as ovn-lb-vip-<uuid> and
it is also affecting to logic when FIP is assigned to LB VIP.

Change-Id: I174a0f971426c18eaaae3133446c0750bb50dd13
2025-05-28 16:30:01 +02:00

1254 lines
56 KiB
Python

# Copyright 2018 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
import copy
import time
from unittest import mock
from neutron.common import utils as n_utils
from neutron_lib import constants as n_const
from neutron_lib.plugins import directory
from octavia_lib.api.drivers import data_models as octavia_data_model
from octavia_lib.api.drivers import driver_lib
from octavia_lib.common import constants as o_constants
from oslo_db import exception as odb_exc
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from ovsdbapp.schema.ovn_northbound import impl_idl as nb_idl_ovn
from ovsdbapp.schema.ovn_southbound import impl_idl as sb_idl_ovn
import tenacity
# NOTE(mjozefcz): We need base neutron functionals because we need
# mechanism driver and l3 plugin.
from neutron.tests.functional import base
from ovn_octavia_provider.common import clients
from ovn_octavia_provider.common import constants as ovn_const
from ovn_octavia_provider import driver as ovn_driver
class TestOvnOctaviaBase(base.TestOVNFunctionalBase,
base.BaseLoggingTestCase):
def setUp(self):
super().setUp()
nb_idl_ovn.OvnNbApiIdlImpl.ovsdb_connection = None
sb_idl_ovn.OvnSbApiIdlImpl.ovsdb_connection = None
# TODO(mjozefcz): Use octavia listeners to provide needed
# sockets and modify tests in order to verify if fake
# listener (status) has received valid value.
try:
mock.patch.object(
driver_lib.DriverLibrary, '_check_for_socket_ready').start()
except AttributeError:
# Backward compatiblity with octavia-lib < 1.3.1
pass
self.ovn_driver = ovn_driver.OvnProviderDriver()
self.ovn_driver._ovn_helper._octavia_driver_lib = mock.MagicMock()
self._o_driver_lib = self.ovn_driver._ovn_helper._octavia_driver_lib
self._o_driver_lib.update_loadbalancer_status = mock.Mock()
self.fake_neutron_client = mock.MagicMock()
clients.get_neutron_client = mock.MagicMock()
clients.get_neutron_client.return_value = self.fake_neutron_client
self.fake_neutron_client.get_network = self._mock_get_network
self.fake_neutron_client.get_subnet = self._mock_get_subnet
self.fake_neutron_client.ports = self._mock_ports
self.fake_neutron_client.get_port = self._mock_get_port
self.fake_neutron_client.delete_port.return_value = True
self._local_net_cache = {}
self._local_cidr_cache = {}
self._local_port_cache = {'ports': []}
self.core_plugin = directory.get_plugin()
def _port_dict_to_mock(self, port_dict):
port = mock.Mock(**port_dict)
return port
def _mock_get_network(self, network_id):
network = mock.Mock()
network.id = network_id
network.provider_physical_network = None
return network
def _mock_get_subnet(self, subnet_id):
subnet = mock.Mock()
subnet.network_id = self._local_net_cache[subnet_id]
subnet.cidr = self._local_cidr_cache[subnet_id]
subnet.gateway_ip = None
return subnet
def _mock_ports(self, **kwargs):
return self._local_port_cache['ports']
def _mock_get_port(self, port_id):
for port in self._local_port_cache['ports']:
if port.id == port_id:
return port
def _create_provider_network(self):
e1 = self._make_network(self.fmt, 'e1', True, True,
arg_list=('router:external',
'provider:network_type',
'provider:physical_network'),
**{'router:external': True,
'provider:network_type': 'flat',
'provider:physical_network': 'public'})
res = self._create_subnet(self.fmt, e1['network']['id'],
'100.0.0.0/24', gateway_ip='100.0.0.254',
allocation_pools=[{'start': '100.0.0.2',
'end': '100.0.0.253'}],
enable_dhcp=False)
e1_s1 = self.deserialize(self.fmt, res)
return e1, e1_s1
def _create_lb_model(self, vip=None, vip_network_id=None,
vip_subnet_id=None, vip_port_id=None,
admin_state_up=True, additional_vips=[]):
lb = octavia_data_model.LoadBalancer()
lb.loadbalancer_id = uuidutils.generate_uuid()
if vip:
lb.vip_address = vip
else:
lb.vip_address = '10.0.0.4'
if vip_network_id:
lb.vip_network_id = vip_network_id
if vip_subnet_id:
lb.vip_subnet_id = vip_subnet_id
if vip_port_id:
lb.vip_port_id = vip_port_id
if additional_vips:
lb.additional_vips = additional_vips
lb.admin_state_up = admin_state_up
return lb
def _create_pool_model(
self, loadbalancer_id, pool_name,
protocol=o_constants.PROTOCOL_TCP,
lb_algorithm=o_constants.LB_ALGORITHM_SOURCE_IP_PORT,
admin_state_up=True, listener_id=None):
m_pool = octavia_data_model.Pool()
if protocol:
m_pool.protocol = protocol
else:
m_pool.protocol = o_constants.PROTOCOL_TCP
m_pool.name = pool_name
m_pool.pool_id = uuidutils.generate_uuid()
m_pool.loadbalancer_id = loadbalancer_id
m_pool.members = []
m_pool.admin_state_up = admin_state_up
m_pool.lb_algorithm = lb_algorithm
if listener_id:
m_pool.listener_id = listener_id
return m_pool
def _create_member_model(self, pool_id, subnet_id, address,
protocol_port=None, admin_state_up=True):
m_member = octavia_data_model.Member()
if protocol_port:
m_member.protocol_port = protocol_port
else:
m_member.protocol_port = 80
m_member.member_id = uuidutils.generate_uuid()
m_member.pool_id = pool_id
if subnet_id:
m_member.subnet_id = subnet_id
m_member.address = address
m_member.admin_state_up = admin_state_up
return m_member
def _create_hm_model(self, pool_id, name, delay, timeout, max_retries,
hm_type, max_retries_down=3, admin_state_up=True):
return octavia_data_model.HealthMonitor(
admin_state_up=admin_state_up, delay=delay,
max_retries=max_retries, max_retries_down=max_retries_down,
healthmonitor_id=uuidutils.generate_uuid(),
name=name, pool_id=pool_id, type=hm_type, timeout=timeout)
def _create_listener_model(self, loadbalancer_id, pool_id=None,
protocol_port=80, protocol=None,
admin_state_up=True):
m_listener = octavia_data_model.Listener()
if protocol:
m_listener.protocol = protocol
else:
m_listener.protocol = o_constants.PROTOCOL_TCP
m_listener.listener_id = uuidutils.generate_uuid()
m_listener.loadbalancer_id = loadbalancer_id
if pool_id:
m_listener.default_pool_id = pool_id
m_listener.protocol_port = protocol_port
m_listener.admin_state_up = admin_state_up
return m_listener
def _get_loadbalancers(self):
lbs = []
for lb in self.nb_api.tables['Load_Balancer'].rows.values():
external_ids = dict(lb.external_ids)
# Skip load balancers used by port forwarding plugin
if external_ids.get(ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY) == (
ovn_const.PORT_FORWARDING_PLUGIN):
continue
ls_refs = external_ids.get(ovn_const.LB_EXT_IDS_LS_REFS_KEY)
if ls_refs:
external_ids[
ovn_const.LB_EXT_IDS_LS_REFS_KEY] = jsonutils.loads(
ls_refs)
member_status = external_ids.get(ovn_const.OVN_MEMBER_STATUS_KEY)
if member_status:
external_ids[
ovn_const.OVN_MEMBER_STATUS_KEY] = jsonutils.loads(
member_status)
lb_dict = {'name': lb.name, 'protocol': lb.protocol,
'vips': lb.vips, 'external_ids': external_ids}
try:
lb_dict['selection_fields'] = lb.selection_fields
except AttributeError:
pass
lbs.append(lb_dict)
return lbs
def _get_loadbalancer_id(self, lb_name):
for lb in self.nb_api.tables['Load_Balancer'].rows.values():
if lb.name == lb_name:
return lb.uuid
def _validate_loadbalancers(self, expected_lbs):
observed_lbs = self._get_loadbalancers()
# NOTE (mjozefcz): assertCountEqual works only on first level
# of comparison, if dicts inside dicts are in different
# order it would fail.
self.assertEqual(len(expected_lbs), len(observed_lbs))
for expected_lb in expected_lbs:
# search for LB with same name and protocol
found = False
for observed_lb in observed_lbs:
if (observed_lb.get('name') ==
expected_lb.get('name') and
observed_lb.get('protocol') ==
expected_lb.get('protocol')):
self.assertEqual(expected_lb, observed_lb)
found = True
if not found:
raise Exception("Expected LB %s for protocol %s "
"not found in observed_lbs" % (
expected_lb.get('name'),
expected_lb.get('proto')))
def _is_lb_associated_to_ls(self, lb_name, ls_name):
return self._is_lb_associated_to_tab(
'Logical_Switch', lb_name, ls_name)
def _is_lb_associated_to_lr(self, lb_name, lr_name):
return self._is_lb_associated_to_tab(
'Logical_Router', lb_name, lr_name)
def _is_lb_associated_to_tab(self, table, lb_name, ls_name):
lb_uuid = self._get_loadbalancer_id(lb_name)
for ls in self.nb_api.tables[table].rows.values():
if ls.name == ls_name:
ls_lbs = [lb.uuid for lb in ls.load_balancer]
return lb_uuid in ls_lbs
return False
@tenacity.retry(
retry=tenacity.retry_if_exception_type(odb_exc.DBError),
wait=tenacity.wait_exponential(),
stop=tenacity.stop_after_attempt(3),
reraise=True)
def _create_router(self, name, gw_info=None):
router = {'router':
{'name': name,
'admin_state_up': True,
'tenant_id': self._tenant_id}}
if gw_info:
router['router']['external_gateway_info'] = gw_info
router = self.l3_plugin.create_router(self.context, router)
return router['id']
@tenacity.retry(
retry=tenacity.retry_if_exception_type(odb_exc.DBError),
wait=tenacity.wait_exponential(),
stop=tenacity.stop_after_attempt(3),
reraise=True)
def _create_net(self, name):
n1 = self._make_network(self.fmt, name, True)
return n1
@tenacity.retry(
retry=tenacity.retry_if_exception_type(odb_exc.DBError),
wait=tenacity.wait_exponential(),
stop=tenacity.stop_after_attempt(3),
reraise=True)
def _create_subnet_from_net(self, net, cidr,
ip_version=n_const.IP_VERSION_4):
res = self._create_subnet(self.fmt, net['network']['id'],
cidr, ip_version=ip_version)
subnet = self.deserialize(self.fmt, res)['subnet']
self._local_net_cache[subnet['id']] = net['network']['id']
self._local_cidr_cache[subnet['id']] = subnet['cidr']
return net['network']['id'], subnet['id']
@tenacity.retry(
retry=tenacity.retry_if_exception_type(odb_exc.DBError),
wait=tenacity.wait_exponential(),
stop=tenacity.stop_after_attempt(3),
reraise=True)
def _attach_router_to_subnet(self, subnet_id, router_id):
self.l3_plugin.add_router_interface(
self.context, router_id, {'subnet_id': subnet_id})
@tenacity.retry(
retry=tenacity.retry_if_exception_type(odb_exc.DBError),
wait=tenacity.wait_exponential(),
stop=tenacity.stop_after_attempt(3),
reraise=True)
def _create_port_on_network(self, net):
port = self._make_port(self.fmt, net['network']['id'])
self._local_port_cache['ports'].append(
self._port_dict_to_mock(port['port']))
port_address = port['port']['fixed_ips'][0]['ip_address']
return (port_address, port['port']['id'])
def _update_ls_refs(self, lb_data, net_id, add_ref=True):
if not net_id.startswith(ovn_const.LR_REF_KEY_HEADER):
net_id = ovn_const.LR_REF_KEY_HEADER + '%s' % net_id
if add_ref:
if net_id not in lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY]:
lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] = 1
else:
lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] += 1
else:
ref_ct = lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id]
if ref_ct <= 0:
del lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id]
def _wait_for_status(self, expected_statuses, check_call=True):
call_count = len(expected_statuses)
update_loadbalancer_status = (
self._o_driver_lib.update_loadbalancer_status)
n_utils.wait_until_true(
lambda: update_loadbalancer_status.call_count == call_count,
timeout=10)
if check_call:
# NOTE(mjozefcz): The updates are send in parallel and includes
# dicts with unordered lists inside. So we can't simply use
# assert_has_calls here. Sample structure:
# {'listeners': [],
# 'loadbalancers': [{'id': 'a', 'provisioning_status': 'ACTIVE'}],
# 'members': [{'id': 'b', 'provisioning_status': 'DELETED'},
# {'id': 'c', 'provisioning_status': 'DELETED'}],
# 'pools': [{'id': 'd', 'operating_status': 'ONLINE',
# 'provisioning_status': 'ACTIVE'}]},
updated_statuses = []
for call in update_loadbalancer_status.mock_calls:
updated_statuses.append(call[1][0])
calls_found = []
for expected_status in expected_statuses:
for updated_status in updated_statuses:
# Find status update having equal keys
if (sorted(updated_status.keys()) ==
sorted(expected_status.keys())):
val_check = []
# Withing this status update check if all values of
# expected keys match.
for k, v in expected_status.items():
ex = sorted(expected_status[k],
key=lambda x: x['id'])
ox = sorted(updated_status[k],
key=lambda x: x['id'])
val_check.append(all(item in ox for item in ex))
if False in val_check:
# At least one value don't match.
continue
calls_found.append(expected_status)
break
# Validate if we found all expected calls.
self.assertCountEqual(expected_statuses, calls_found)
def _wait_for_status_and_validate(self, lb_data, expected_status,
check_call=True):
self._wait_for_status(expected_status, check_call)
expected_lbs = self._make_expected_lbs(lb_data)
self._validate_loadbalancers(expected_lbs)
def _create_load_balancer_custom_lr_ls_and_validate(
self, admin_state_up=True, create_router=True,
force_retry_ls_to_lr_assoc=True):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
net_info = []
net1 = self._create_net('n' + uuidutils.generate_uuid()[:4])
network_id1, subnet_id1 = self._create_subnet_from_net(
net1, '10.0.1.0/24')
port_address1, port_id1 = self._create_port_on_network(net1)
net_info.append((network_id1, subnet_id1, port_address1, port_id1))
net2 = self._create_net('n' + uuidutils.generate_uuid()[:4])
network_id2, subnet_id2 = self._create_subnet_from_net(
net2, '10.0.2.0/24')
port_address2, port_id2 = self._create_port_on_network(net2)
net_info.append((network_id2, subnet_id2, port_address2, port_id2))
net3 = self._create_net('n' + uuidutils.generate_uuid()[:4])
network_id3, subnet_id3 = self._create_subnet_from_net(
net3, '10.0.3.0/24')
port_address3, port_id3 = self._create_port_on_network(net3)
net_info.append((network_id3, subnet_id3, port_address3, port_id3))
r_id = self._create_router(
'r' + uuidutils.generate_uuid()[:4]) if create_router else None
self._attach_router_to_subnet(subnet_id1, r_id)
self._attach_router_to_subnet(subnet_id2, r_id)
self._attach_router_to_subnet(subnet_id3, r_id)
lb_data = {}
lb_data['model'] = self._create_lb_model(
vip=net_info[0][2],
vip_network_id=net_info[0][0],
vip_subnet_id=net_info[0][1],
vip_port_id=net_info[0][3],
admin_state_up=admin_state_up)
lb_data[ovn_const.LB_EXT_IDS_LR_REF_KEY] = \
(ovn_const.LR_REF_KEY_HEADER + r_id)
lb_data['vip_net_info'] = net_info[0]
lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY] = {}
lb_data['listeners'] = []
lb_data['pools'] = []
self._update_ls_refs(lb_data, net_info[0][0])
ls = [ovn_const.LR_REF_KEY_HEADER + net[0] for net in net_info]
if force_retry_ls_to_lr_assoc:
ls_foo = copy.deepcopy(ls)
ls_foo.append('neutron-foo')
self.ovn_driver._ovn_helper._find_ls_for_lr = mock.MagicMock()
self.ovn_driver._ovn_helper._find_ls_for_lr.side_effect = \
[ls_foo, ls]
self.ovn_driver.loadbalancer_create(lb_data['model'])
# NOTE(froyo): This sleep is configured here due to previous call
# is also modifiend LB VIP port, as same way the following update
# port call. For some reason this second was not propagated.
time.sleep(1)
name = '%s%s' % (ovn_const.LB_VIP_PORT_PREFIX,
lb_data['model'].loadbalancer_id)
self.driver.update_port(
self.context, net_info[0][3], {'port': {'name': name}})
if lb_data['model'].admin_state_up:
expected_status = {
'loadbalancers': [{"id": lb_data['model'].loadbalancer_id,
"provisioning_status": "ACTIVE",
"operating_status": o_constants.ONLINE}]
}
else:
expected_status = {
'loadbalancers': [{"id": lb_data['model'].loadbalancer_id,
"provisioning_status": "ACTIVE",
"operating_status": o_constants.OFFLINE}]
}
self._wait_for_status_and_validate(lb_data, [expected_status])
self.assertTrue(
self._is_lb_associated_to_ls(
lb_data['model'].loadbalancer_id,
ovn_const.LR_REF_KEY_HEADER + net_info[0][0]))
# NOTE(froyo): Just to check all net connected to lr have a
# reference to lb
for net_id in ls:
self.assertTrue(
self._is_lb_associated_to_ls(
lb_data['model'].loadbalancer_id,
net_id))
return lb_data
def _create_load_balancer_and_validate(self, network_id, subnet_id,
port_address, port_id,
admin_state_up=True,
only_model=False,
router_id=None,
multiple_lb=False,
additional_vips=[]):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
lb_data = {}
if router_id:
lb_data[ovn_const.LB_EXT_IDS_LR_REF_KEY] = (
ovn_const.LR_REF_KEY_HEADER + router_id)
lb_data['vip_net_info'] = (
network_id, subnet_id, port_address, port_id)
lb_data['model'] = self._create_lb_model(
vip=port_address, vip_network_id=network_id,
vip_subnet_id=subnet_id, vip_port_id=port_id,
admin_state_up=admin_state_up, additional_vips=additional_vips)
lb_data[ovn_const.LB_EXT_IDS_LS_REFS_KEY] = {}
lb_data['listeners'] = []
lb_data['pools'] = []
self._update_ls_refs(lb_data, network_id)
if only_model:
return lb_data
self.ovn_driver.loadbalancer_create(lb_data['model'])
# NOTE(froyo): This sleep is configured here due to previous call
# is also modifiend LB VIP port, as same way the following update
# port call. For some reason this second was not propagated.
time.sleep(1)
name = '%s%s' % (ovn_const.LB_VIP_PORT_PREFIX,
lb_data['model'].loadbalancer_id)
self.driver.update_port(
self.context, port_id, {'port': {'name': name}})
if additional_vips:
for index, add_vip in enumerate(additional_vips, start=1):
name = name = '%s%s-%s' % (
ovn_const.LB_VIP_ADDIT_PORT_PREFIX,
index,
lb_data['model'].loadbalancer_id)
self.driver.update_port(
self.context, add_vip['port_id'], {'port': {'name': name}})
if lb_data['model'].admin_state_up:
expected_status = {
'loadbalancers': [{"id": lb_data['model'].loadbalancer_id,
"provisioning_status": "ACTIVE",
"operating_status": o_constants.ONLINE}]
}
else:
expected_status = {
'loadbalancers': [{"id": lb_data['model'].loadbalancer_id,
"provisioning_status": "ACTIVE",
"operating_status": o_constants.OFFLINE}]
}
if not multiple_lb:
self._wait_for_status_and_validate(lb_data, [expected_status])
else:
l_id = lb_data['model'].loadbalancer_id
self._wait_for_status([expected_status])
self.assertIn(l_id,
[lb['name'] for lb in self._get_loadbalancers()])
self.assertTrue(
self._is_lb_associated_to_ls(
lb_data['model'].loadbalancer_id,
ovn_const.LR_REF_KEY_HEADER + network_id))
return lb_data
def _update_load_balancer_and_validate(self, lb_data,
admin_state_up=None):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
if admin_state_up is not None:
lb_data['model'].admin_state_up = admin_state_up
self.ovn_driver.loadbalancer_update(
lb_data['model'], lb_data['model'])
if lb_data['model'].admin_state_up:
expected_status = {
'loadbalancers': [{"id": lb_data['model'].loadbalancer_id,
"provisioning_status": "ACTIVE",
"operating_status": o_constants.ONLINE}]
}
else:
expected_status = {
'loadbalancers': [{"id": lb_data['model'].loadbalancer_id,
"provisioning_status": "ACTIVE",
"operating_status": o_constants.OFFLINE}]
}
self._wait_for_status_and_validate(lb_data, [expected_status])
def _delete_load_balancer_and_validate(self, lb_data, cascade=False,
multiple_lb=False):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
self.ovn_driver.loadbalancer_delete(lb_data['model'], cascade)
expected_status = {
'loadbalancers': [{"id": lb_data['model'].loadbalancer_id,
"provisioning_status": "DELETED",
"operating_status": "OFFLINE"}]
}
if cascade:
expected_status['pools'] = []
expected_status['members'] = []
expected_status['listeners'] = []
for pool in lb_data['pools']:
expected_status['pools'].append({
'id': pool.pool_id,
'provisioning_status': 'DELETED'})
for member in pool.members:
expected_status['members'].append({
"id": member.member_id,
"provisioning_status": "DELETED"})
for listener in lb_data['listeners']:
expected_status['listeners'].append({
"id": listener.listener_id,
"provisioning_status": "DELETED",
"operating_status": "OFFLINE"})
expected_status = {
key: value for key, value in expected_status.items() if value}
l_id = lb_data['model'].loadbalancer_id
lb = lb_data['model']
del lb_data['model']
if not multiple_lb:
self._wait_for_status_and_validate(lb_data, [expected_status])
else:
self._wait_for_status([expected_status])
self.assertNotIn(
l_id, [lbs['name'] for lbs in self._get_loadbalancers()])
vip_net_id = lb_data['vip_net_info'][0]
self.assertFalse(
self._is_lb_associated_to_ls(
lb.loadbalancer_id,
ovn_const.LR_REF_KEY_HEADER + vip_net_id))
def _make_expected_lbs(self, lb_data):
def _get_lb_field_by_protocol(protocol, field='external_ids'):
"Get needed external_ids and pass by reference"
lb = [lb for lb in expected_lbs
if lb.get('protocol') == [protocol]]
return lb[0].get(field)
if not lb_data or not lb_data.get('model'):
return []
vip_net_info = lb_data['vip_net_info']
external_ids = {ovn_const.LB_EXT_IDS_LS_REFS_KEY: {},
'neutron:vip': lb_data['model'].vip_address,
'neutron:vip_port_id': vip_net_info[3],
'enabled': str(lb_data['model'].admin_state_up)}
# if there are any additional_vip on model
if lb_data['model'].additional_vips:
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_KEY] = ''
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_PORT_ID_KEY] = ''
for addi_vip in lb_data['model'].additional_vips:
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_KEY] += \
addi_vip['ip_address'] + ','
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_PORT_ID_KEY] += \
addi_vip['port_id'] + ','
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_KEY] = \
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_KEY][:-1]
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_PORT_ID_KEY] = \
external_ids[ovn_const.LB_EXT_IDS_ADDIT_VIP_PORT_ID_KEY][:-1]
# NOTE(mjozefcz): By default we don't set protocol. We don't know if
# listener/pool would be TCP, UDP or SCTP, so do not set it.
expected_protocols = set()
# Lets fetch list of L4 protocols defined for this LB.
for p in lb_data['pools']:
expected_protocols.add(p.protocol.lower())
for listener in lb_data['listeners']:
expected_protocols.add(listener.protocol.lower())
# If there is no protocol lets add default - empty [].
expected_protocols = list(expected_protocols)
if len(expected_protocols) == 0:
expected_protocols.append(None)
expected_lbs = []
for protocol in expected_protocols:
lb = {'name': lb_data['model'].loadbalancer_id,
'protocol': [protocol] if protocol else [],
'vips': {},
'external_ids': copy.deepcopy(external_ids)}
if self.ovn_driver._ovn_helper._are_selection_fields_supported():
lb['selection_fields'] = ovn_const.LB_SELECTION_FIELDS_MAP[
o_constants.LB_ALGORITHM_SOURCE_IP_PORT]
expected_lbs.append(lb)
# For every connected subnet to the LB set the ref
# counter.
for net_id, ref_ct in lb_data[
ovn_const.LB_EXT_IDS_LS_REFS_KEY].items():
for lb in expected_lbs:
# If given LB hasn't VIP configured from
# this network we shouldn't touch it here.
if net_id == 'neutron-%s' % lb_data['model'].vip_network_id:
lb.get('external_ids')[
ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] = 1
if lb_data['model'].additional_vips:
lb.get('external_ids')[
ovn_const.LB_EXT_IDS_LS_REFS_KEY][net_id] += \
len(lb_data['model'].additional_vips)
# For every connected router set it here.
if lb_data.get(ovn_const.LB_EXT_IDS_LR_REF_KEY):
for lb in expected_lbs:
lb.get('external_ids')[
ovn_const.LB_EXT_IDS_LR_REF_KEY] = lb_data[
ovn_const.LB_EXT_IDS_LR_REF_KEY]
pool_info = {}
for p in lb_data.get('pools', []):
member_status = {}
external_ids = _get_lb_field_by_protocol(
p.protocol.lower(),
field='external_ids')
p_members = ""
for m in p.members:
m_info = 'member_' + m.member_id + '_' + m.address
m_info += ":" + str(m.protocol_port)
m_info += "_" + str(m.subnet_id)
if p_members:
p_members += "," + m_info
else:
p_members = m_info
# Bump up LS refs counter if needed.
if m.subnet_id:
found = False
# Need to get the network_id.
for port in self._local_port_cache['ports']:
if not found:
for fixed_ip in port.fixed_ips:
if fixed_ip['subnet_id'] == m.subnet_id:
ex = external_ids[
ovn_const.LB_EXT_IDS_LS_REFS_KEY]
act = ex.get(
'neutron-%s' % port.network_id, 0)
ex['neutron-%s' % port.network_id] = \
act + 1
found = True
if p.healthmonitor:
member_status[m.member_id] = o_constants.ONLINE
external_ids[ovn_const.LB_EXT_IDS_HMS_KEY] = \
jsonutils.dumps([p.healthmonitor.healthmonitor_id])
else:
if m.admin_state_up:
member_status[m.member_id] = o_constants.NO_MONITOR
else:
member_status[m.member_id] = o_constants.OFFLINE
pool_key = 'pool_' + p.pool_id
if not p.admin_state_up:
pool_key += ':D'
external_ids[pool_key] = p_members
pool_info[p.pool_id] = p_members
if member_status:
external_ids[ovn_const.OVN_MEMBER_STATUS_KEY] = member_status
for listener in lb_data['listeners']:
expected_vips = _get_lb_field_by_protocol(
listener.protocol.lower(),
field='vips')
external_ids = _get_lb_field_by_protocol(
listener.protocol.lower(),
field='external_ids')
listener_k = 'listener_' + str(listener.listener_id)
if lb_data['model'].admin_state_up and listener.admin_state_up:
vips_k = [lb_data['model'].vip_address + ":" + str(
listener.protocol_port)]
# idem for additional vips if exists
if lb_data['model'].additional_vips:
for addi_vip in lb_data['model'].additional_vips:
vips_k.append(addi_vip['ip_address'] + ":" + str(
listener.protocol_port))
if not isinstance(listener.default_pool_id,
octavia_data_model.UnsetType) and pool_info[
listener.default_pool_id]:
for vip_k in vips_k:
expected_vips[vip_k] = self._extract_member_info(
pool_info[listener.default_pool_id])
else:
listener_k += ':D'
external_ids[listener_k] = str(listener.protocol_port) + ":"
if not isinstance(listener.default_pool_id,
octavia_data_model.UnsetType):
external_ids[listener_k] += 'pool_' + listener.default_pool_id
elif lb_data.get('pools', []):
external_ids[listener_k] += 'pool_' + lb_data[
'pools'][0].pool_id
return expected_lbs
def _extract_member_info(self, member):
mem_info = ''
if member:
for item in member.split(','):
mem_info += item.split('_')[2] + ","
return mem_info[:-1]
def _create_pool_and_validate(self, lb_data, pool_name,
protocol=None,
listener_id=None):
lb_pools = lb_data['pools']
m_pool = self._create_pool_model(lb_data['model'].loadbalancer_id,
pool_name,
protocol=protocol,
listener_id=listener_id)
lb_pools.append(m_pool)
self._o_driver_lib.update_loadbalancer_status.reset_mock()
self.ovn_driver.pool_create(m_pool)
operating_status = (
o_constants.ONLINE
if listener_id else o_constants.OFFLINE)
expected_status = {
'pools': [{'id': m_pool.pool_id,
'provisioning_status': 'ACTIVE',
'operating_status': operating_status}],
'loadbalancers': [{'id': m_pool.loadbalancer_id,
'provisioning_status': 'ACTIVE'}]
}
if listener_id:
expected_status['listeners'] = [
{'id': listener_id,
'provisioning_status': 'ACTIVE'}]
self._wait_for_status_and_validate(lb_data, [expected_status])
expected_lbs = self._make_expected_lbs(lb_data)
self._validate_loadbalancers(expected_lbs)
def _update_pool_and_validate(self, lb_data, pool_name,
admin_state_up=None):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
m_pool = self._get_pool_from_lb_data(lb_data, pool_name=pool_name)
old_admin_state_up = m_pool.admin_state_up
operating_status = 'ONLINE'
if admin_state_up is not None:
m_pool.admin_state_up = admin_state_up
if not admin_state_up:
operating_status = 'OFFLINE'
pool_listeners = self._get_pool_listeners(lb_data, m_pool.pool_id)
expected_listener_status = [
{'id': listener.listener_id, 'provisioning_status': 'ACTIVE'}
for listener in pool_listeners]
self.ovn_driver.pool_update(m_pool, m_pool)
expected_status = {
'pools': [{'id': m_pool.pool_id,
'provisioning_status': 'ACTIVE',
'operating_status': operating_status}],
'loadbalancers': [{'id': m_pool.loadbalancer_id,
'provisioning_status': 'ACTIVE'}],
'listeners': expected_listener_status
}
if old_admin_state_up != m_pool.admin_state_up:
if m_pool.admin_state_up:
oper_status = o_constants.ONLINE
else:
oper_status = o_constants.OFFLINE
expected_status['pools'][0]['operating_status'] = oper_status
self._wait_for_status_and_validate(lb_data, [expected_status])
def _delete_pool_and_validate(self, lb_data, pool_name,
listener_id=None):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
p = self._get_pool_from_lb_data(lb_data, pool_name=pool_name)
self.ovn_driver.pool_delete(p)
lb_data['pools'].remove(p)
expected_status = []
# When a pool is deleted and if it has a health_monitor associated or
# any members, there are expected to be deleted.
if p.healthmonitor:
member_statuses = [{"id": m.member_id,
"provisioning_status": o_constants.ACTIVE,
"operating_status": o_constants.NO_MONITOR}
for m in p.members]
expected_status.append(
{'healthmonitors': [{
"id": p.healthmonitor.healthmonitor_id,
"provisioning_status": o_constants.DELETED,
"operating_status": o_constants.NO_MONITOR}],
'members': member_statuses,
'loadbalancers': [{
"id": p.loadbalancer_id,
"provisioning_status": o_constants.ACTIVE}],
'pools': [{"id": p.pool_id,
"provisioning_status": o_constants.ACTIVE}],
'listeners': []})
for m in p.members:
expected_status.append(
{'pools': [{"id": p.pool_id,
"provisioning_status": o_constants.ACTIVE,
"operating_status": o_constants.ONLINE}],
'members': [{"id": m.member_id,
"provisioning_status": o_constants.DELETED}],
'loadbalancers': [{
"id": p.loadbalancer_id,
"provisioning_status": o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}],
'listeners': []})
self._update_ls_refs(
lb_data, self._local_net_cache[m.subnet_id], add_ref=False)
if p.members:
# If Pool has members, delete all members of the pool. When the
# last member is processed set Operating status of Pool as Offline
expected_status[-1]['pools'][0][
'operating_status'] = o_constants.OFFLINE
pool_dict = {
'pools': [{'id': p.pool_id,
'provisioning_status': 'DELETED'}],
'loadbalancers': [{'id': p.loadbalancer_id,
'provisioning_status': 'ACTIVE'}],
'listeners': []
}
if listener_id:
pool_dict['listeners'] = [{'id': listener_id,
'provisioning_status': 'ACTIVE'}]
expected_status.append(pool_dict)
self._wait_for_status_and_validate(lb_data, expected_status)
def _get_pool_from_lb_data(self, lb_data, pool_id=None,
pool_name=None):
for p in lb_data['pools']:
if pool_id and p.pool_id == pool_id:
return p
if pool_name and p.name == pool_name:
return p
def _get_listener_from_lb_data(self, lb_data, protocol, protocol_port):
for listener in lb_data['listeners']:
if (listener.protocol_port == protocol_port and
listener.protocol == protocol):
return listener
def _get_pool_listeners(self, lb_data, pool_id):
listeners = []
for listener in lb_data['listeners']:
if listener.default_pool_id == pool_id:
listeners.append(listener)
return listeners
def _create_member_and_validate(self, lb_data, pool_id, subnet_id,
network_id, address, expected_subnet=None):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
pool_status = {'id': pool.pool_id,
'provisioning_status': o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}
m_member = self._create_member_model(pool.pool_id, subnet_id, address)
# The "expected" member value, which might be different from what
# we pass to member_create(), for example, if an expected_subnet
# was given.
if expected_subnet:
e_member = copy.deepcopy(m_member)
e_member.subnet_id = expected_subnet
else:
e_member = m_member
pool.members.append(e_member)
self.ovn_driver.member_create(m_member)
self._update_ls_refs(lb_data, network_id)
pool_listeners = self._get_pool_listeners(lb_data, pool_id)
expected_listener_status = [
{'id': listener.listener_id, 'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}
for listener in pool_listeners]
expected_status = {
'pools': [pool_status],
'members': [{"id": m_member.member_id,
"provisioning_status": "ACTIVE",
"operating_status": o_constants.NO_MONITOR}],
'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': expected_listener_status
}
self._wait_for_status_and_validate(lb_data, [expected_status])
def _get_pool_member(self, pool, member_address):
for m in pool.members:
if m.address == member_address:
return m
def _update_member_and_validate(self, lb_data, pool_id, member_address,
remove_subnet_id=False,
admin_state_up=True):
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
member = self._get_pool_member(pool, member_address)
self._o_driver_lib.update_loadbalancer_status.reset_mock()
old_member = copy.deepcopy(member)
member.admin_state_up = admin_state_up
# NOTE(froyo): In order to test update of member without passing the
# subnet_id parameter of the member, just to cover the case when a new
# member has been created without passing that argument
if remove_subnet_id:
old_member.subnet_id = None
self.ovn_driver.member_update(old_member, member)
expected_status = {
'pools': [{'id': pool.pool_id,
'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'members': [{"id": member.member_id,
'provisioning_status': 'ACTIVE'}],
'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': []
}
if getattr(member, 'admin_state_up', None):
expected_status['members'][0][
'operating_status'] = o_constants.NO_MONITOR
else:
expected_status['members'][0]['operating_status'] = "OFFLINE"
self._wait_for_status_and_validate(lb_data, [expected_status])
def _update_members_in_batch_and_validate(self, lb_data, pool_id,
members):
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
expected_status = []
self._o_driver_lib.update_loadbalancer_status.reset_mock()
self.ovn_driver.member_batch_update(pool_id, members)
for member in members:
expected_status.append(
{'pools': [{'id': pool.pool_id,
'provisioning_status': 'ACTIVE'}],
'members': [{'id': member.member_id,
'provisioning_status': 'ACTIVE',
'operating_status': 'ONLINE'}],
'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': []})
for m in pool.members:
found = False
for member in members:
if member.member_id == m.member_id:
found = True
break
if not found:
expected_status.append(
{'pools': [{'id': pool.pool_id,
'provisioning_status': 'ACTIVE'}],
'members': [{'id': m.member_id,
'provisioning_status': 'DELETED'}],
'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': 'ACTIVE'}],
'listeners': []})
# Delete member from lb_data
pool.members.remove(m)
self._wait_for_status_and_validate(lb_data, expected_status,
check_call=False)
def _delete_member_and_validate(self, lb_data, pool_id, network_id,
member_address, remove_subnet_id=False):
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
member = self._get_pool_member(pool, member_address)
pool.members.remove(member)
pool_status = {"id": pool.pool_id,
"provisioning_status": o_constants.ACTIVE,
"operating_status": o_constants.ONLINE}
if not pool.members:
pool_status['operating_status'] = o_constants.OFFLINE
self._o_driver_lib.update_loadbalancer_status.reset_mock()
# NOTE(froyo): In order to test deletion of member without passing
# the subnet_id parameter of the member, just to cover the case when
# a new member has been created without passing that argument
m_member = copy.deepcopy(member)
if remove_subnet_id:
m_member.subnet_id = None
self.ovn_driver.member_delete(m_member)
expected_status = {
'pools': [pool_status],
'members': [{"id": member.member_id,
"provisioning_status": "DELETED"}],
'loadbalancers': [{"id": pool.loadbalancer_id,
'provisioning_status': 'ACTIVE',
'operating_status': o_constants.ONLINE}],
'listeners': []}
self._update_ls_refs(lb_data, network_id, add_ref=False)
self._wait_for_status_and_validate(lb_data, [expected_status])
def _create_hm_and_validate(self, lb_data, pool_id, name, delay, timeout,
max_retries, hm_type):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
pool_status = {'id': pool.pool_id,
'provisioning_status': o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}
m_hm = self._create_hm_model(pool.pool_id, name, delay, timeout,
max_retries, hm_type)
pool.healthmonitor = m_hm
self.ovn_driver._ovn_helper._update_hm_member = mock.MagicMock()
self.ovn_driver._ovn_helper._update_hm_member.side_effect = [
o_constants.ONLINE, o_constants.ONLINE]
self.ovn_driver.health_monitor_create(m_hm)
pool_listeners = self._get_pool_listeners(lb_data, pool_id)
expected_listener_status = [
{'id': listener.listener_id,
'provisioning_status': o_constants.ACTIVE}
for listener in pool_listeners]
expected_member_status = [
{'id': m.member_id, 'provisioning_status': o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}
for m in pool.members]
expected_hm_status = {'id': m_hm.healthmonitor_id,
'provisioning_status': o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}
expected_status = {
'pools': [pool_status],
'members': expected_member_status,
'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': o_constants.ACTIVE,
'operating_status': o_constants.ONLINE}],
'listeners': expected_listener_status,
'healthmonitors': [expected_hm_status]
}
self._wait_for_status_and_validate(lb_data, [expected_status])
def _update_hm_and_validate(self, lb_data, pool_id, admin_state_up=None):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
hm = pool.healthmonitor
old_hm = copy.deepcopy(hm)
operating_status = o_constants.ONLINE
if admin_state_up is not None:
hm.admin_state_up = admin_state_up
if not admin_state_up:
operating_status = o_constants.OFFLINE
pool_status = {"id": pool.pool_id,
"provisioning_status": o_constants.ACTIVE}
self.ovn_driver.health_monitor_update(old_hm, hm)
expected_hm_status = {'id': hm.healthmonitor_id,
'provisioning_status': o_constants.ACTIVE,
'operating_status': operating_status}
expected_status = {
'pools': [pool_status],
'loadbalancers': [{'id': pool.loadbalancer_id,
'provisioning_status': o_constants.ACTIVE}],
'healthmonitors': [expected_hm_status]}
self._wait_for_status_and_validate(lb_data, [expected_status])
def _delete_hm_and_validate(self, lb_data, pool_id):
self._o_driver_lib.update_loadbalancer_status.reset_mock()
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
hm = pool.healthmonitor
pool.healthmonitor = None
pool_status = {'id': pool.pool_id,
'provisioning_status': o_constants.ACTIVE}
pool_listeners = self._get_pool_listeners(lb_data, pool_id)
expected_listener_status = [
{'id': listener.listener_id,
'provisioning_status': o_constants.ACTIVE}
for listener in pool_listeners]
self.ovn_driver.health_monitor_delete(hm)
expected_hm_status = {'id': hm.healthmonitor_id,
'provisioning_status': o_constants.DELETED,
'operating_status': o_constants.NO_MONITOR}
expected_member_status = [
{'id': m.member_id, 'provisioning_status': o_constants.ACTIVE,
'operating_status': o_constants.NO_MONITOR}
for m in pool.members]
expected_status = {
'pools': [pool_status],
'loadbalancers': [{"id": pool.loadbalancer_id,
"provisioning_status": o_constants.ACTIVE}],
'members': expected_member_status,
'listeners': expected_listener_status,
'healthmonitors': [expected_hm_status]}
self._wait_for_status_and_validate(lb_data, [expected_status])
def _create_listener_and_validate(self, lb_data, pool_id=None,
protocol_port=80,
admin_state_up=True, protocol='TCP'):
if pool_id:
pool = self._get_pool_from_lb_data(lb_data, pool_id=pool_id)
loadbalancer_id = pool.loadbalancer_id
pool_id = pool.pool_id
else:
loadbalancer_id = lb_data['model'].loadbalancer_id
pool_id = None
m_listener = self._create_listener_model(loadbalancer_id,
pool_id, protocol_port,
protocol=protocol,
admin_state_up=admin_state_up)
lb_data['listeners'].append(m_listener)
self._o_driver_lib.update_loadbalancer_status.reset_mock()
self.ovn_driver.listener_create(m_listener)
expected_status = {
'listeners': [{'id': m_listener.listener_id,
'provisioning_status': 'ACTIVE',
'operating_status': 'ONLINE'}],
'loadbalancers': [{'id': m_listener.loadbalancer_id,
'provisioning_status': "ACTIVE"}]}
self._wait_for_status_and_validate(lb_data, [expected_status])
def _update_listener_and_validate(self, lb_data, protocol_port=80,
admin_state_up=None, protocol='TCP'):
m_listener = self._get_listener_from_lb_data(
lb_data, protocol, protocol_port)
self._o_driver_lib.update_loadbalancer_status.reset_mock()
old_admin_state_up = m_listener.admin_state_up
operating_status = 'ONLINE'
if admin_state_up is not None:
m_listener.admin_state_up = admin_state_up
if not admin_state_up:
operating_status = 'OFFLINE'
m_listener.protocol = protocol
self.ovn_driver.listener_update(m_listener, m_listener)
pool_status = [{'id': m_listener.default_pool_id,
'provisioning_status': 'ACTIVE'}]
expected_status = {
'listeners': [{'id': m_listener.listener_id,
'provisioning_status': 'ACTIVE',
'operating_status': operating_status}],
'loadbalancers': [{"id": m_listener.loadbalancer_id,
"provisioning_status": "ACTIVE"}],
'pools': pool_status}
if old_admin_state_up != m_listener.admin_state_up:
if m_listener.admin_state_up:
oper_status = o_constants.ONLINE
else:
oper_status = o_constants.OFFLINE
expected_status['listeners'][0]['operating_status'] = oper_status
self._wait_for_status_and_validate(lb_data, [expected_status])
def _delete_listener_and_validate(self, lb_data, protocol='TCP',
protocol_port=80):
m_listener = self._get_listener_from_lb_data(
lb_data, protocol, protocol_port)
lb_data['listeners'].remove(m_listener)
self._o_driver_lib.update_loadbalancer_status.reset_mock()
self.ovn_driver.listener_delete(m_listener)
expected_status = {
'listeners': [{"id": m_listener.listener_id,
"provisioning_status": "DELETED",
"operating_status": "OFFLINE"}],
'loadbalancers': [{"id": m_listener.loadbalancer_id,
"provisioning_status": "ACTIVE"}]}
self._wait_for_status_and_validate(lb_data, [expected_status])