380 lines
17 KiB
Python
380 lines
17 KiB
Python
# Copyright 2020 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 re
|
|
|
|
import netaddr
|
|
from octavia_lib.api.drivers import data_models as o_datamodels
|
|
from octavia_lib.api.drivers import exceptions as driver_exceptions
|
|
from octavia_lib.api.drivers import provider_base as driver_base
|
|
from octavia_lib.common import constants
|
|
from oslo_log import log as logging
|
|
|
|
from ovn_octavia_provider.common import config as ovn_conf
|
|
# TODO(mjozefcz): Start consuming const and utils
|
|
# from neutron-lib once released.
|
|
from ovn_octavia_provider.common import constants as ovn_const
|
|
from ovn_octavia_provider.common import exceptions as ovn_exc
|
|
from ovn_octavia_provider import helper as ovn_helper
|
|
from ovn_octavia_provider.i18n import _
|
|
|
|
ovn_conf.register_opts()
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class OvnProviderDriver(driver_base.ProviderDriver):
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._ovn_helper = ovn_helper.OvnProviderHelper()
|
|
|
|
def __del__(self):
|
|
self._ovn_helper.shutdown()
|
|
|
|
def _check_for_supported_protocols(self, protocol):
|
|
if protocol not in ovn_const.OVN_NATIVE_LB_PROTOCOLS:
|
|
msg = _('OVN provider does not support %s protocol') % protocol
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
|
|
def _check_for_supported_algorithms(self, algorithm):
|
|
if algorithm not in ovn_const.OVN_NATIVE_LB_ALGORITHMS:
|
|
msg = _('OVN provider does not support %s algorithm') % algorithm
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
|
|
def _check_for_allowed_cidrs(self, allowed_cidrs):
|
|
# TODO(haleyb): add support for this
|
|
if isinstance(allowed_cidrs, o_datamodels.UnsetType):
|
|
allowed_cidrs = []
|
|
if allowed_cidrs:
|
|
msg = _('OVN provider does not support allowed_cidrs option')
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
|
|
def loadbalancer_create(self, loadbalancer):
|
|
admin_state_up = loadbalancer.admin_state_up
|
|
if isinstance(admin_state_up, o_datamodels.UnsetType):
|
|
admin_state_up = True
|
|
request_info = {'id': loadbalancer.loadbalancer_id,
|
|
'vip_address': loadbalancer.vip_address,
|
|
'vip_network_id': loadbalancer.vip_network_id,
|
|
'admin_state_up': admin_state_up}
|
|
|
|
request = {'type': ovn_const.REQ_TYPE_LB_CREATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def loadbalancer_delete(self, loadbalancer, cascade=False):
|
|
request_info = {'id': loadbalancer.loadbalancer_id,
|
|
'cascade': cascade}
|
|
request = {'type': ovn_const.REQ_TYPE_LB_DELETE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def loadbalancer_failover(self, loadbalancer_id):
|
|
msg = _('OVN provider does not support loadbalancer failover')
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
|
|
def loadbalancer_update(self, old_loadbalancer, new_loadbalancer):
|
|
request_info = {'id': new_loadbalancer.loadbalancer_id}
|
|
if not isinstance(
|
|
new_loadbalancer.admin_state_up, o_datamodels.UnsetType):
|
|
request_info['admin_state_up'] = new_loadbalancer.admin_state_up
|
|
request = {'type': ovn_const.REQ_TYPE_LB_UPDATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
# Pool
|
|
def pool_create(self, pool):
|
|
self._check_for_supported_protocols(pool.protocol)
|
|
self._check_for_supported_algorithms(pool.lb_algorithm)
|
|
admin_state_up = pool.admin_state_up
|
|
if isinstance(admin_state_up, o_datamodels.UnsetType):
|
|
admin_state_up = True
|
|
request_info = {'id': pool.pool_id,
|
|
'loadbalancer_id': pool.loadbalancer_id,
|
|
'protocol': pool.protocol,
|
|
'lb_algorithm': pool.lb_algorithm,
|
|
'listener_id': pool.listener_id,
|
|
'admin_state_up': admin_state_up}
|
|
request = {'type': ovn_const.REQ_TYPE_POOL_CREATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def pool_delete(self, pool):
|
|
for member in pool.members:
|
|
self.member_delete(member)
|
|
|
|
request_info = {'id': pool.pool_id,
|
|
'protocol': pool.protocol,
|
|
'loadbalancer_id': pool.loadbalancer_id}
|
|
request = {'type': ovn_const.REQ_TYPE_POOL_DELETE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def pool_update(self, old_pool, new_pool):
|
|
if not isinstance(new_pool.protocol, o_datamodels.UnsetType):
|
|
self._check_for_supported_protocols(new_pool.protocol)
|
|
if not isinstance(new_pool.lb_algorithm, o_datamodels.UnsetType):
|
|
self._check_for_supported_algorithms(new_pool.lb_algorithm)
|
|
request_info = {'id': old_pool.pool_id,
|
|
'protocol': old_pool.protocol,
|
|
'loadbalancer_id': old_pool.loadbalancer_id}
|
|
|
|
if not isinstance(new_pool.admin_state_up, o_datamodels.UnsetType):
|
|
request_info['admin_state_up'] = new_pool.admin_state_up
|
|
request = {'type': ovn_const.REQ_TYPE_POOL_UPDATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def listener_create(self, listener):
|
|
self._check_for_supported_protocols(listener.protocol)
|
|
self._check_for_allowed_cidrs(listener.allowed_cidrs)
|
|
|
|
admin_state_up = listener.admin_state_up
|
|
if isinstance(admin_state_up, o_datamodels.UnsetType):
|
|
admin_state_up = True
|
|
request_info = {'id': listener.listener_id,
|
|
'protocol': listener.protocol,
|
|
'loadbalancer_id': listener.loadbalancer_id,
|
|
'protocol_port': listener.protocol_port,
|
|
'default_pool_id': listener.default_pool_id,
|
|
'admin_state_up': admin_state_up}
|
|
request = {'type': ovn_const.REQ_TYPE_LISTENER_CREATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def listener_delete(self, listener):
|
|
request_info = {'id': listener.listener_id,
|
|
'loadbalancer_id': listener.loadbalancer_id,
|
|
'protocol_port': listener.protocol_port,
|
|
'protocol': listener.protocol}
|
|
request = {'type': ovn_const.REQ_TYPE_LISTENER_DELETE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def listener_update(self, old_listener, new_listener):
|
|
self._check_for_allowed_cidrs(new_listener.allowed_cidrs)
|
|
|
|
request_info = {'id': new_listener.listener_id,
|
|
'loadbalancer_id': old_listener.loadbalancer_id,
|
|
'protocol': old_listener.protocol,
|
|
'protocol_port': old_listener.protocol_port}
|
|
|
|
if not isinstance(new_listener.admin_state_up, o_datamodels.UnsetType):
|
|
request_info['admin_state_up'] = new_listener.admin_state_up
|
|
|
|
if not isinstance(new_listener.default_pool_id,
|
|
o_datamodels.UnsetType):
|
|
request_info['default_pool_id'] = new_listener.default_pool_id
|
|
|
|
request = {'type': ovn_const.REQ_TYPE_LISTENER_UPDATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
# Member
|
|
def _check_monitor_options(self, member):
|
|
if (isinstance(member.monitor_address, o_datamodels.UnsetType) and
|
|
isinstance(member.monitor_port, o_datamodels.UnsetType)):
|
|
return False
|
|
if member.monitor_address or member.monitor_port:
|
|
return True
|
|
return False
|
|
|
|
def _ip_version_differs(self, member):
|
|
_, ovn_lb = self._ovn_helper._find_ovn_lb_by_pool_id(member.pool_id)
|
|
lb_vip = ovn_lb.external_ids[ovn_const.LB_EXT_IDS_VIP_KEY]
|
|
return netaddr.IPNetwork(lb_vip).version != (
|
|
netaddr.IPNetwork(member.address).version)
|
|
|
|
def member_create(self, member):
|
|
if self._check_monitor_options(member):
|
|
msg = _('OVN provider does not support monitor options')
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
if self._ip_version_differs(member):
|
|
raise ovn_exc.IPVersionsMixingNotSupportedError()
|
|
admin_state_up = member.admin_state_up
|
|
if (isinstance(member.subnet_id, o_datamodels.UnsetType) or
|
|
not member.subnet_id):
|
|
msg = _('Subnet is required for Member creation '
|
|
'with OVN Provider Driver')
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
|
|
if isinstance(admin_state_up, o_datamodels.UnsetType):
|
|
admin_state_up = True
|
|
request_info = {'id': member.member_id,
|
|
'address': member.address,
|
|
'protocol_port': member.protocol_port,
|
|
'pool_id': member.pool_id,
|
|
'subnet_id': member.subnet_id,
|
|
'admin_state_up': admin_state_up}
|
|
request = {'type': ovn_const.REQ_TYPE_MEMBER_CREATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
# NOTE(mjozefcz): If LB has FIP on VIP
|
|
# and member has FIP we need to centralize
|
|
# traffic for member.
|
|
request_info = {'id': member.member_id,
|
|
'address': member.address,
|
|
'pool_id': member.pool_id,
|
|
'subnet_id': member.subnet_id,
|
|
'action': ovn_const.REQ_INFO_MEMBER_ADDED}
|
|
request = {'type': ovn_const.REQ_TYPE_HANDLE_MEMBER_DVR,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def member_delete(self, member):
|
|
request_info = {'id': member.member_id,
|
|
'address': member.address,
|
|
'protocol_port': member.protocol_port,
|
|
'pool_id': member.pool_id,
|
|
'subnet_id': member.subnet_id}
|
|
request = {'type': ovn_const.REQ_TYPE_MEMBER_DELETE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
# NOTE(mjozefcz): If LB has FIP on VIP
|
|
# and member had FIP we can decentralize
|
|
# the traffic now.
|
|
request_info = {'id': member.member_id,
|
|
'address': member.address,
|
|
'pool_id': member.pool_id,
|
|
'subnet_id': member.subnet_id,
|
|
'action': ovn_const.REQ_INFO_MEMBER_DELETED}
|
|
request = {'type': ovn_const.REQ_TYPE_HANDLE_MEMBER_DVR,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def member_update(self, old_member, new_member):
|
|
if self._check_monitor_options(new_member):
|
|
msg = _('OVN provider does not support monitor options')
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
if new_member.address and self._ip_version_differs(new_member):
|
|
raise ovn_exc.IPVersionsMixingNotSupportedError()
|
|
request_info = {'id': new_member.member_id,
|
|
'address': old_member.address,
|
|
'protocol_port': old_member.protocol_port,
|
|
'pool_id': old_member.pool_id,
|
|
'subnet_id': old_member.subnet_id,
|
|
'old_admin_state_up': old_member.admin_state_up}
|
|
if not isinstance(new_member.admin_state_up, o_datamodels.UnsetType):
|
|
request_info['admin_state_up'] = new_member.admin_state_up
|
|
request = {'type': ovn_const.REQ_TYPE_MEMBER_UPDATE,
|
|
'info': request_info}
|
|
self._ovn_helper.add_request(request)
|
|
|
|
def member_batch_update(self, pool_id, members):
|
|
request_list = []
|
|
skipped_members = []
|
|
pool_key, ovn_lb = self._ovn_helper._find_ovn_lb_by_pool_id(pool_id)
|
|
external_ids = copy.deepcopy(ovn_lb.external_ids)
|
|
pool = external_ids[pool_key]
|
|
existing_members = pool.split(',') if pool else []
|
|
members_to_delete = copy.copy(existing_members)
|
|
for member in members:
|
|
if (self._check_monitor_options(member) or
|
|
member.address and self._ip_version_differs(member)):
|
|
skipped_members.append(member.member_id)
|
|
continue
|
|
# NOTE(mjozefcz): We need to have subnet_id information.
|
|
if (isinstance(member.subnet_id, o_datamodels.UnsetType) or
|
|
not member.subnet_id):
|
|
msg = _('Subnet is required for Member creation '
|
|
'with OVN Provider Driver')
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
admin_state_up = member.admin_state_up
|
|
if isinstance(admin_state_up, o_datamodels.UnsetType):
|
|
admin_state_up = True
|
|
|
|
member_info = self._ovn_helper._get_member_key(member)
|
|
# TODO(mjozefcz): Remove this workaround in W release.
|
|
member_info_old = self._ovn_helper._get_member_key(
|
|
member, old_convention=True)
|
|
member_found = [x for x in existing_members
|
|
if re.match(member_info_old, x)]
|
|
if not member_found:
|
|
req_type = ovn_const.REQ_TYPE_MEMBER_CREATE
|
|
else:
|
|
# If member exists in pool, then Update
|
|
req_type = ovn_const.REQ_TYPE_MEMBER_UPDATE
|
|
# Remove all updating members so only deleted ones are left
|
|
# TODO(mjozefcz): Remove this workaround in W release.
|
|
try:
|
|
members_to_delete.remove(member_info_old)
|
|
except ValueError:
|
|
members_to_delete.remove(member_info)
|
|
|
|
request_info = {'id': member.member_id,
|
|
'address': member.address,
|
|
'protocol_port': member.protocol_port,
|
|
'pool_id': member.pool_id,
|
|
'subnet_id': member.subnet_id,
|
|
'admin_state_up': admin_state_up}
|
|
request = {'type': req_type,
|
|
'info': request_info}
|
|
request_list.append(request)
|
|
|
|
for member in members_to_delete:
|
|
member_info = member.split('_')
|
|
request_info = {'id': member_info[1],
|
|
'address': member_info[2].split(':')[0],
|
|
'protocol_port': member_info[2].split(':')[1],
|
|
'pool_id': pool_id}
|
|
if len(member_info) == 4:
|
|
request_info['subnet_id'] = member_info[3]
|
|
request = {'type': ovn_const.REQ_TYPE_MEMBER_DELETE,
|
|
'info': request_info}
|
|
request_list.append(request)
|
|
|
|
for request in request_list:
|
|
self._ovn_helper.add_request(request)
|
|
if skipped_members:
|
|
msg = (_('OVN provider does not support monitor options, '
|
|
'so following members skipped: %s') % skipped_members)
|
|
raise driver_exceptions.UnsupportedOptionError(
|
|
user_fault_string=msg,
|
|
operator_fault_string=msg)
|
|
|
|
def create_vip_port(self, lb_id, project_id, vip_dict):
|
|
try:
|
|
port = self._ovn_helper.create_vip_port(
|
|
project_id, lb_id, vip_dict)['port']
|
|
vip_dict[constants.VIP_PORT_ID] = port['id']
|
|
vip_dict[constants.VIP_ADDRESS] = (
|
|
port['fixed_ips'][0]['ip_address'])
|
|
except Exception as e:
|
|
kwargs = {}
|
|
if hasattr(e, 'message'):
|
|
kwargs = {'user_fault_string': e.message,
|
|
'operator_fault_string': e.message}
|
|
raise driver_exceptions.DriverError(
|
|
**kwargs)
|
|
return vip_dict
|