Merge "[OVN] Move OVN commons to neutron tree"
This commit is contained in:
commit
3e6ba67407
@ -32,6 +32,7 @@ arbitrary file names.
|
||||
macvtap-agent.rst
|
||||
openvswitch-agent.rst
|
||||
sriov-agent.rst
|
||||
ovn.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
6
doc/source/configuration/ovn.rst
Normal file
6
doc/source/configuration/ovn.rst
Normal file
@ -0,0 +1,6 @@
|
||||
=======
|
||||
ovn.ini
|
||||
=======
|
||||
|
||||
.. show-options::
|
||||
:config-file: etc/oslo-config-generator/ovn.ini
|
6
etc/oslo-config-generator/ovn.ini
Normal file
6
etc/oslo-config-generator/ovn.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/neutron/ovn.ini.sample
|
||||
wrap_width = 79
|
||||
|
||||
namespace = neutron.ml2.ovn
|
||||
namespace = oslo.log
|
0
neutron/common/ovn/__init__.py
Normal file
0
neutron/common/ovn/__init__.py
Normal file
185
neutron/common/ovn/constants.py
Normal file
185
neutron/common/ovn/constants.py
Normal file
@ -0,0 +1,185 @@
|
||||
# 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.
|
||||
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib import constants as const
|
||||
import six
|
||||
|
||||
# TODO(lucasagomes): Remove OVN_SG_NAME_EXT_ID_KEY in the Rocky release
|
||||
OVN_SG_NAME_EXT_ID_KEY = 'neutron:security_group_name'
|
||||
OVN_SG_EXT_ID_KEY = 'neutron:security_group_id'
|
||||
OVN_SG_RULE_EXT_ID_KEY = 'neutron:security_group_rule_id'
|
||||
OVN_ML2_MECH_DRIVER_NAME = 'ovn'
|
||||
OVN_NETWORK_NAME_EXT_ID_KEY = 'neutron:network_name'
|
||||
OVN_NETWORK_MTU_EXT_ID_KEY = 'neutron:mtu'
|
||||
OVN_PORT_NAME_EXT_ID_KEY = 'neutron:port_name'
|
||||
OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip'
|
||||
OVN_ROUTER_NAME_EXT_ID_KEY = 'neutron:router_name'
|
||||
OVN_ROUTER_IS_EXT_GW = 'neutron:is_ext_gw'
|
||||
OVN_GW_PORT_EXT_ID_KEY = 'neutron:gw_port_id'
|
||||
OVN_SUBNET_EXT_ID_KEY = 'neutron:subnet_id'
|
||||
OVN_SUBNET_EXT_IDS_KEY = 'neutron:subnet_ids'
|
||||
OVN_PHYSNET_EXT_ID_KEY = 'neutron:provnet-physical-network'
|
||||
OVN_NETTYPE_EXT_ID_KEY = 'neutron:provnet-network-type'
|
||||
OVN_SEGID_EXT_ID_KEY = 'neutron:provnet-segmentation-id'
|
||||
OVN_PROJID_EXT_ID_KEY = 'neutron:project_id'
|
||||
OVN_DEVID_EXT_ID_KEY = 'neutron:device_id'
|
||||
OVN_CIDRS_EXT_ID_KEY = 'neutron:cidrs'
|
||||
OVN_FIP_EXT_ID_KEY = 'neutron:fip_id'
|
||||
OVN_FIP_PORT_EXT_ID_KEY = 'neutron:fip_port_id'
|
||||
OVN_FIP_EXT_MAC_KEY = 'neutron:fip_external_mac'
|
||||
OVN_REV_NUM_EXT_ID_KEY = 'neutron:revision_number'
|
||||
OVN_QOS_POLICY_EXT_ID_KEY = 'neutron:qos_policy_id'
|
||||
OVN_SG_IDS_EXT_ID_KEY = 'neutron:security_group_ids'
|
||||
OVN_DEVICE_OWNER_EXT_ID_KEY = 'neutron:device_owner'
|
||||
OVN_LIVENESS_CHECK_EXT_ID_KEY = 'neutron:liveness_check_at'
|
||||
METADATA_LIVENESS_CHECK_EXT_ID_KEY = 'neutron:metadata_liveness_check_at'
|
||||
OVN_PORT_BINDING_PROFILE = portbindings.PROFILE
|
||||
OVN_PORT_BINDING_PROFILE_PARAMS = [{'parent_name': six.string_types,
|
||||
'tag': six.integer_types},
|
||||
{'vtep-physical-switch': six.string_types,
|
||||
'vtep-logical-switch': six.string_types}]
|
||||
MIGRATING_ATTR = 'migrating_to'
|
||||
OVN_ROUTER_PORT_OPTION_KEYS = ['router-port', 'nat-addresses']
|
||||
OVN_GATEWAY_CHASSIS_KEY = 'redirect-chassis'
|
||||
OVN_CHASSIS_REDIRECT = 'chassisredirect'
|
||||
OVN_GATEWAY_NAT_ADDRESSES_KEY = 'nat-addresses'
|
||||
OVN_DROP_PORT_GROUP_NAME = 'neutron_pg_drop'
|
||||
OVN_ROUTER_PORT_GW_MTU_OPTION = 'gateway_mtu'
|
||||
|
||||
OVN_PROVNET_PORT_NAME_PREFIX = 'provnet-'
|
||||
|
||||
# Agent extension constants
|
||||
OVN_AGENT_DESC_KEY = 'neutron:description'
|
||||
OVN_AGENT_METADATA_SB_CFG_KEY = 'neutron:ovn-metadata-sb-cfg'
|
||||
OVN_AGENT_METADATA_DESC_KEY = 'neutron:description-metadata'
|
||||
OVN_AGENT_METADATA_ID_KEY = 'neutron:ovn-metadata-id'
|
||||
OVN_CONTROLLER_AGENT = 'OVN Controller agent'
|
||||
OVN_CONTROLLER_GW_AGENT = 'OVN Controller Gateway agent'
|
||||
OVN_METADATA_AGENT = 'OVN Metadata agent'
|
||||
|
||||
# OVN ACLs have priorities. The highest priority ACL that matches is the one
|
||||
# that takes effect. Our choice of priority numbers is arbitrary, but it
|
||||
# leaves room above and below the ACLs we create. We only need two priorities.
|
||||
# The first is for all the things we allow. The second is for dropping traffic
|
||||
# by default.
|
||||
ACL_PRIORITY_ALLOW = 1002
|
||||
ACL_PRIORITY_DROP = 1001
|
||||
|
||||
ACL_ACTION_DROP = 'drop'
|
||||
ACL_ACTION_ALLOW_RELATED = 'allow-related'
|
||||
ACL_ACTION_ALLOW = 'allow'
|
||||
|
||||
# When a OVN L3 gateway is created, it needs to be bound to a chassis. In
|
||||
# case a chassis is not found OVN_GATEWAY_INVALID_CHASSIS will be set in
|
||||
# the options column of the Logical Router. This value is used to detect
|
||||
# unhosted router gateways to schedule.
|
||||
OVN_GATEWAY_INVALID_CHASSIS = 'neutron-ovn-invalid-chassis'
|
||||
|
||||
SUPPORTED_DHCP_OPTS = {
|
||||
4: ['netmask', 'router', 'dns-server', 'log-server',
|
||||
'lpr-server', 'swap-server', 'ip-forward-enable',
|
||||
'policy-filter', 'default-ttl', 'mtu', 'router-discovery',
|
||||
'router-solicitation', 'arp-timeout', 'ethernet-encap',
|
||||
'tcp-ttl', 'tcp-keepalive', 'nis-server', 'ntp-server',
|
||||
'tftp-server'],
|
||||
6: ['server-id', 'dns-server', 'domain-search']}
|
||||
DHCPV6_STATELESS_OPT = 'dhcpv6_stateless'
|
||||
|
||||
# When setting global DHCP options, these options will be ignored
|
||||
# as they are required for basic network functions and will be
|
||||
# set by Neutron.
|
||||
GLOBAL_DHCP_OPTS_BLACKLIST = {
|
||||
4: ['server_id', 'lease_time', 'mtu', 'router', 'server_mac',
|
||||
'dns_server', 'classless_static_route'],
|
||||
6: ['dhcpv6_stateless', 'dns_server', 'server_id']}
|
||||
|
||||
CHASSIS_DATAPATH_NETDEV = 'netdev'
|
||||
CHASSIS_IFACE_DPDKVHOSTUSER = 'dpdkvhostuser'
|
||||
|
||||
OVN_IPV6_ADDRESS_MODES = {
|
||||
const.IPV6_SLAAC: const.IPV6_SLAAC,
|
||||
const.DHCPV6_STATEFUL: const.DHCPV6_STATEFUL.replace('-', '_'),
|
||||
const.DHCPV6_STATELESS: const.DHCPV6_STATELESS.replace('-', '_')
|
||||
}
|
||||
|
||||
DB_MAX_RETRIES = 60
|
||||
DB_INITIAL_RETRY_INTERVAL = 0.5
|
||||
DB_MAX_RETRY_INTERVAL = 1
|
||||
|
||||
TXN_COMMITTED = 'committed'
|
||||
INITIAL_REV_NUM = -1
|
||||
|
||||
ACL_EXPECTED_COLUMNS_NBDB = (
|
||||
'external_ids', 'direction', 'log', 'priority',
|
||||
'name', 'action', 'severity', 'match')
|
||||
|
||||
# Resource types
|
||||
TYPE_NETWORKS = 'networks'
|
||||
TYPE_PORTS = 'ports'
|
||||
TYPE_SECURITY_GROUP_RULES = 'security_group_rules'
|
||||
TYPE_ROUTERS = 'routers'
|
||||
TYPE_ROUTER_PORTS = 'router_ports'
|
||||
TYPE_SECURITY_GROUPS = 'security_groups'
|
||||
TYPE_FLOATINGIPS = 'floatingips'
|
||||
TYPE_SUBNETS = 'subnets'
|
||||
|
||||
_TYPES_PRIORITY_ORDER = (
|
||||
TYPE_NETWORKS,
|
||||
TYPE_SECURITY_GROUPS,
|
||||
TYPE_SUBNETS,
|
||||
TYPE_ROUTERS,
|
||||
TYPE_PORTS,
|
||||
TYPE_ROUTER_PORTS,
|
||||
TYPE_FLOATINGIPS,
|
||||
TYPE_SECURITY_GROUP_RULES)
|
||||
|
||||
# The order in which the resources should be created or updated by the
|
||||
# maintenance task: Root ones first and leafs at the end.
|
||||
MAINTENANCE_CREATE_UPDATE_TYPE_ORDER = {
|
||||
t: n for n, t in enumerate(_TYPES_PRIORITY_ORDER, 1)}
|
||||
|
||||
# The order in which the resources should be deleted by the maintenance
|
||||
# task: Leaf ones first and roots at the end.
|
||||
MAINTENANCE_DELETE_TYPE_ORDER = {
|
||||
t: n for n, t in enumerate(reversed(_TYPES_PRIORITY_ORDER), 1)}
|
||||
|
||||
# The addresses field to set in the logical switch port which has a
|
||||
# peer router port (connecting to the logical router).
|
||||
DEFAULT_ADDR_FOR_LSP_WITH_PEER = 'router'
|
||||
|
||||
# Loadbalancer constants
|
||||
LRP_PREFIX = "lrp-"
|
||||
LB_VIP_PORT_PREFIX = "ovn-lb-vip-"
|
||||
|
||||
# Hash Ring constants
|
||||
HASH_RING_NODES_TIMEOUT = 60
|
||||
HASH_RING_TOUCH_INTERVAL = 30
|
||||
HASH_RING_CACHE_TIMEOUT = 30
|
||||
HASH_RING_ML2_GROUP = 'mechanism_driver'
|
||||
|
||||
# Maximum chassis count where a gateway port can be hosted
|
||||
MAX_GW_CHASSIS = 5
|
||||
|
||||
UNKNOWN_ADDR = 'unknown'
|
||||
|
||||
PORT_CAP_SWITCHDEV = 'switchdev'
|
||||
|
||||
# TODO(lucasagomes): Create constants for other LSP types
|
||||
LSP_TYPE_LOCALNET = 'localnet'
|
||||
LSP_TYPE_VIRTUAL = 'virtual'
|
||||
LSP_TYPE_EXTERNAL = 'external'
|
||||
LSP_OPTIONS_VIRTUAL_PARENTS_KEY = 'virtual-parents'
|
||||
LSP_OPTIONS_VIRTUAL_IP_KEY = 'virtual-ip'
|
||||
|
||||
HA_CHASSIS_GROUP_DEFAULT_NAME = 'default_ha_chassis_group'
|
||||
HA_CHASSIS_GROUP_HIGHEST_PRIORITY = 32767
|
38
neutron/common/ovn/exceptions.py
Normal file
38
neutron/common/ovn/exceptions.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright 2019 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.
|
||||
|
||||
from neutron_lib import exceptions as n_exc
|
||||
|
||||
from neutron._i18n import _
|
||||
|
||||
|
||||
class RevisionConflict(n_exc.NeutronException):
|
||||
message = _('OVN revision number for %(resource_id)s (type: '
|
||||
'%(resource_type)s) is equal or higher than the given '
|
||||
'resource. Skipping update')
|
||||
|
||||
|
||||
class UnknownResourceType(n_exc.NeutronException):
|
||||
message = _('Uknown resource type: %(resource_type)s')
|
||||
|
||||
|
||||
class StandardAttributeIDNotFound(n_exc.NeutronException):
|
||||
message = _('Standard attribute ID not found for %(resource_uuid)s')
|
||||
|
||||
|
||||
class HashRingIsEmpty(n_exc.NeutronException):
|
||||
message = _('Hash Ring returned empty when hashing "%(key)s". '
|
||||
'This should never happen in a normal situation, please '
|
||||
'check the status of your cluster')
|
100
neutron/common/ovn/hash_ring_manager.py
Normal file
100
neutron/common/ovn/hash_ring_manager.py
Normal file
@ -0,0 +1,100 @@
|
||||
# Copyright 2019 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 datetime
|
||||
|
||||
from oslo_log import log
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
from tooz import hashring
|
||||
|
||||
from neutron.common.ovn import constants
|
||||
from neutron.common.ovn import exceptions
|
||||
from neutron.db import ovn_hash_ring_db as db_hash_ring
|
||||
from neutron_lib import context
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class HashRingManager(object):
|
||||
|
||||
def __init__(self, group_name):
|
||||
self._hash_ring = None
|
||||
self._last_time_loaded = None
|
||||
self._cache_startup_timeout = True
|
||||
self._group = group_name
|
||||
self.admin_ctx = context.get_admin_context()
|
||||
|
||||
@property
|
||||
def _wait_startup_before_caching(self):
|
||||
# NOTE(lucasagomes): Some events are processed at the service's
|
||||
# startup time and since many services may be started concurrently
|
||||
# we do not want to use a cached hash ring at that point. This
|
||||
# method checks if the created_at and updated_at columns from the
|
||||
# nodes in the ring from this host is equal, and if so it means
|
||||
# that the service just started.
|
||||
|
||||
# If the startup timeout already expired, there's no reason to
|
||||
# keep reading from the DB. At this point this will always
|
||||
# return False
|
||||
if not self._cache_startup_timeout:
|
||||
return False
|
||||
|
||||
nodes = db_hash_ring.get_active_nodes(
|
||||
self.admin_ctx,
|
||||
constants.HASH_RING_CACHE_TIMEOUT, self._group, from_host=True)
|
||||
dont_cache = nodes and nodes[0].created_at == nodes[0].updated_at
|
||||
if not dont_cache:
|
||||
self._cache_startup_timeout = False
|
||||
|
||||
return dont_cache
|
||||
|
||||
def _load_hash_ring(self, refresh=False):
|
||||
cache_timeout = timeutils.utcnow() - datetime.timedelta(
|
||||
seconds=constants.HASH_RING_CACHE_TIMEOUT)
|
||||
|
||||
# Refresh the cache if:
|
||||
# - Refreshed is forced (refresh=True)
|
||||
# - Service just started (_wait_startup_before_caching)
|
||||
# - Hash Ring is not yet instantiated
|
||||
# - Cache has timed out
|
||||
if (refresh or
|
||||
self._wait_startup_before_caching or
|
||||
self._hash_ring is None or
|
||||
not self._hash_ring.nodes or
|
||||
cache_timeout >= self._last_time_loaded):
|
||||
nodes = db_hash_ring.get_active_nodes(
|
||||
self.admin_ctx,
|
||||
constants.HASH_RING_NODES_TIMEOUT, self._group)
|
||||
self._hash_ring = hashring.HashRing({node.node_uuid
|
||||
for node in nodes})
|
||||
self._last_time_loaded = timeutils.utcnow()
|
||||
|
||||
def refresh(self):
|
||||
self._load_hash_ring(refresh=True)
|
||||
|
||||
def get_node(self, key):
|
||||
self._load_hash_ring()
|
||||
|
||||
# tooz expects a byte string for the hash
|
||||
if isinstance(key, six.string_types):
|
||||
key = key.encode('utf-8')
|
||||
|
||||
try:
|
||||
# We need to pop the value from the set. If empty,
|
||||
# KeyError is raised
|
||||
return self._hash_ring[key].pop()
|
||||
except KeyError:
|
||||
raise exceptions.HashRingIsEmpty(key=key)
|
460
neutron/common/ovn/utils.py
Normal file
460
neutron/common/ovn/utils.py
Normal file
@ -0,0 +1,460 @@
|
||||
# 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 collections
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
|
||||
import netaddr
|
||||
from neutron_lib.api.definitions import external_net
|
||||
from neutron_lib.api.definitions import extra_dhcp_opt as edo_ext
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.api.definitions import port_security as psec
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib.api import validators
|
||||
from neutron_lib import constants as const
|
||||
from neutron_lib import context as n_context
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.utils import net as n_utils
|
||||
from oslo_utils import netutils
|
||||
from oslo_utils import strutils
|
||||
from ovs.db import idl
|
||||
from ovsdbapp.backend.ovs_idl import connection
|
||||
from ovsdbapp.backend.ovs_idl import idlutils
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.common.ovn import constants
|
||||
from neutron.common.ovn import exceptions as ovn_exc
|
||||
|
||||
|
||||
DNS_RESOLVER_FILE = "/etc/resolv.conf"
|
||||
|
||||
AddrPairsDiff = collections.namedtuple(
|
||||
'AddrPairsDiff', ['added', 'removed', 'changed'])
|
||||
|
||||
|
||||
def ovn_name(id):
|
||||
# The name of the OVN entry will be neutron-<UUID>
|
||||
# This is due to the fact that the OVN application checks if the name
|
||||
# is a UUID. If so then there will be no matches.
|
||||
# We prefix the UUID to enable us to use the Neutron UUID when
|
||||
# updating, deleting etc.
|
||||
return 'neutron-%s' % id
|
||||
|
||||
|
||||
def ovn_lrouter_port_name(id):
|
||||
# The name of the OVN lrouter port entry will be lrp-<UUID>
|
||||
# This is to distinguish with the name of the connected lswitch patch port,
|
||||
# which is named with neutron port uuid, so that OVS patch ports are
|
||||
# generated properly. The pairing patch port names will be:
|
||||
# - patch-lrp-<UUID>-to-<UUID>
|
||||
# - patch-<UUID>-to-lrp-<UUID>
|
||||
# lrp stands for Logical Router Port
|
||||
return constants.LRP_PREFIX + '%s' % id
|
||||
|
||||
|
||||
def ovn_provnet_port_name(network_id):
|
||||
# The name of OVN lswitch provider network port entry will be
|
||||
# provnet-<Network-UUID>. The port is created for network having
|
||||
# provider:physical_network attribute.
|
||||
return constants.OVN_PROVNET_PORT_NAME_PREFIX + '%s' % network_id
|
||||
|
||||
|
||||
def ovn_vhu_sockpath(sock_dir, port_id):
|
||||
# Frame the socket path of a virtio socket
|
||||
return os.path.join(
|
||||
sock_dir,
|
||||
# this parameter will become the virtio port name,
|
||||
# so it should not exceed IFNAMSIZ(16).
|
||||
(const.VHOST_USER_DEVICE_PREFIX + port_id)[:14])
|
||||
|
||||
|
||||
def ovn_addrset_name(sg_id, ip_version):
|
||||
# The name of the address set for the given security group id and ip
|
||||
# version. The format is:
|
||||
# as-<ip version>-<security group uuid>
|
||||
# with all '-' replaced with '_'. This replacement is necessary
|
||||
# because OVN doesn't support '-' in an address set name.
|
||||
return ('as-%s-%s' % (ip_version, sg_id)).replace('-', '_')
|
||||
|
||||
|
||||
def ovn_pg_addrset_name(sg_id, ip_version):
|
||||
# The name of the address set for the given security group id modelled as a
|
||||
# Port Group and ip version. The format is:
|
||||
# pg-<security group uuid>-<ip version>
|
||||
# with all '-' replaced with '_'. This replacement is necessary
|
||||
# because OVN doesn't support '-' in an address set name.
|
||||
return ('pg-%s-%s' % (sg_id, ip_version)).replace('-', '_')
|
||||
|
||||
|
||||
def ovn_port_group_name(sg_id):
|
||||
# The name of the port group for the given security group id.
|
||||
# The format is: pg-<security group uuid>.
|
||||
return ('pg-%s' % sg_id).replace('-', '_')
|
||||
|
||||
|
||||
def is_network_device_port(port):
|
||||
return port.get('device_owner', '').startswith(
|
||||
const.DEVICE_OWNER_PREFIXES)
|
||||
|
||||
|
||||
def get_lsp_dhcp_opts(port, ip_version):
|
||||
# Get dhcp options from Neutron port, for setting DHCP_Options row
|
||||
# in OVN.
|
||||
lsp_dhcp_disabled = False
|
||||
lsp_dhcp_opts = {}
|
||||
if is_network_device_port(port):
|
||||
lsp_dhcp_disabled = True
|
||||
else:
|
||||
for edo in port.get(edo_ext.EXTRADHCPOPTS, []):
|
||||
if edo['ip_version'] != ip_version:
|
||||
continue
|
||||
|
||||
if edo['opt_name'] == 'dhcp_disabled' and (
|
||||
edo['opt_value'] in ['True', 'true']):
|
||||
# OVN native DHCP is disabled on this port
|
||||
lsp_dhcp_disabled = True
|
||||
# Make sure return value behavior not depends on the order and
|
||||
# content of the extra DHCP options for the port
|
||||
lsp_dhcp_opts.clear()
|
||||
break
|
||||
|
||||
if edo['opt_name'] not in (
|
||||
constants.SUPPORTED_DHCP_OPTS[ip_version]):
|
||||
continue
|
||||
|
||||
opt = edo['opt_name'].replace('-', '_')
|
||||
lsp_dhcp_opts[opt] = edo['opt_value']
|
||||
|
||||
return (lsp_dhcp_disabled, lsp_dhcp_opts)
|
||||
|
||||
|
||||
def is_lsp_trusted(port):
|
||||
return n_utils.is_port_trusted(port) if port.get('device_owner') else False
|
||||
|
||||
|
||||
def is_lsp_ignored(port):
|
||||
# Since the floating IP port is not bound to any chassis, packets from vm
|
||||
# destined to floating IP will be dropped. To overcome this, we do not
|
||||
# create/update floating IP port in OVN.
|
||||
return port.get('device_owner') in [const.DEVICE_OWNER_FLOATINGIP]
|
||||
|
||||
|
||||
def get_lsp_security_groups(port, skip_trusted_port=True):
|
||||
# In other agent link OVS, skipping trusted port is processed in security
|
||||
# groups RPC. We haven't that step, so we do it here.
|
||||
return [] if (skip_trusted_port and is_lsp_trusted(port)
|
||||
) else port.get('security_groups', [])
|
||||
|
||||
|
||||
def is_snat_enabled(router):
|
||||
return router.get(l3.EXTERNAL_GW_INFO, {}).get('enable_snat', True)
|
||||
|
||||
|
||||
def is_port_security_enabled(port):
|
||||
return port.get(psec.PORTSECURITY)
|
||||
|
||||
|
||||
def validate_and_get_data_from_binding_profile(port):
|
||||
if (constants.OVN_PORT_BINDING_PROFILE not in port or
|
||||
not validators.is_attr_set(
|
||||
port[constants.OVN_PORT_BINDING_PROFILE])):
|
||||
return {}
|
||||
param_set = {}
|
||||
param_dict = {}
|
||||
for param_set in constants.OVN_PORT_BINDING_PROFILE_PARAMS:
|
||||
param_keys = param_set.keys()
|
||||
for param_key in param_keys:
|
||||
try:
|
||||
param_dict[param_key] = (port[
|
||||
constants.OVN_PORT_BINDING_PROFILE][param_key])
|
||||
except KeyError:
|
||||
pass
|
||||
if len(param_dict) == 0:
|
||||
continue
|
||||
if len(param_dict) != len(param_keys):
|
||||
msg = _('Invalid binding:profile. %s are all '
|
||||
'required.') % param_keys
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
if (len(port[constants.OVN_PORT_BINDING_PROFILE]) != len(
|
||||
param_keys)):
|
||||
msg = _('Invalid binding:profile. too many parameters')
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
break
|
||||
|
||||
if not param_dict:
|
||||
return {}
|
||||
|
||||
for param_key, param_type in param_set.items():
|
||||
if param_type is None:
|
||||
continue
|
||||
param_value = param_dict[param_key]
|
||||
if not isinstance(param_value, param_type):
|
||||
msg = _('Invalid binding:profile. %(key)s %(value)s '
|
||||
'value invalid type') % {'key': param_key,
|
||||
'value': param_value}
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
|
||||
# Make sure we can successfully look up the port indicated by
|
||||
# parent_name. Just let it raise the right exception if there is a
|
||||
# problem.
|
||||
if 'parent_name' in param_set:
|
||||
plugin = directory.get_plugin()
|
||||
plugin.get_port(n_context.get_admin_context(),
|
||||
param_dict['parent_name'])
|
||||
|
||||
if 'tag' in param_set:
|
||||
tag = int(param_dict['tag'])
|
||||
if tag < 0 or tag > 4095:
|
||||
msg = _('Invalid binding:profile. tag "%s" must be '
|
||||
'an integer between 0 and 4095, inclusive') % tag
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
|
||||
return param_dict
|
||||
|
||||
|
||||
def is_dhcp_options_ignored(subnet):
|
||||
# Don't insert DHCP_Options entry for v6 subnet with 'SLAAC' as
|
||||
# 'ipv6_address_mode', since DHCPv6 shouldn't work for this mode.
|
||||
return (subnet['ip_version'] == const.IP_VERSION_6 and
|
||||
subnet.get('ipv6_address_mode') == const.IPV6_SLAAC)
|
||||
|
||||
|
||||
def get_ovn_ipv6_address_mode(address_mode):
|
||||
return constants.OVN_IPV6_ADDRESS_MODES[address_mode]
|
||||
|
||||
|
||||
def get_revision_number(resource, resource_type):
|
||||
"""Get the resource's revision number based on its type."""
|
||||
if resource_type in (constants.TYPE_NETWORKS,
|
||||
constants.TYPE_PORTS,
|
||||
constants.TYPE_SECURITY_GROUP_RULES,
|
||||
constants.TYPE_ROUTERS,
|
||||
constants.TYPE_ROUTER_PORTS,
|
||||
constants.TYPE_SECURITY_GROUPS,
|
||||
constants.TYPE_FLOATINGIPS, constants.TYPE_SUBNETS):
|
||||
return resource['revision_number']
|
||||
else:
|
||||
raise ovn_exc.UnknownResourceType(resource_type=resource_type)
|
||||
|
||||
|
||||
def remove_macs_from_lsp_addresses(addresses):
|
||||
"""Remove the mac addreses from the Logical_Switch_Port addresses column.
|
||||
|
||||
:param addresses: The list of addresses from the Logical_Switch_Port.
|
||||
Example: ["80:fa:5b:06:72:b7 158.36.44.22",
|
||||
"ff:ff:ff:ff:ff:ff 10.0.0.2"]
|
||||
:returns: A list of IP addesses (v4 and v6)
|
||||
"""
|
||||
ip_list = []
|
||||
for addr in addresses:
|
||||
ip_list.extend([x for x in addr.split() if
|
||||
(netutils.is_valid_ipv4(x) or
|
||||
netutils.is_valid_ipv6(x))])
|
||||
return ip_list
|
||||
|
||||
|
||||
def get_allowed_address_pairs_ip_addresses(port):
|
||||
"""Return a list of IP addresses from port's allowed_address_pairs.
|
||||
|
||||
:param port: A neutron port
|
||||
:returns: A list of IP addesses (v4 and v6)
|
||||
"""
|
||||
return [x['ip_address'] for x in port.get('allowed_address_pairs', [])
|
||||
if 'ip_address' in x]
|
||||
|
||||
|
||||
def get_allowed_address_pairs_ip_addresses_from_ovn_port(ovn_port):
|
||||
"""Return a list of IP addresses from ovn port.
|
||||
|
||||
Return a list of IP addresses equivalent of Neutron's port
|
||||
allowed_address_pairs column using the data in the OVN port.
|
||||
|
||||
:param ovn_port: A OVN port
|
||||
:returns: A list of IP addesses (v4 and v6)
|
||||
"""
|
||||
addresses = remove_macs_from_lsp_addresses(ovn_port.addresses)
|
||||
port_security = remove_macs_from_lsp_addresses(ovn_port.port_security)
|
||||
return [x for x in port_security if x not in addresses]
|
||||
|
||||
|
||||
def get_ovn_port_security_groups(ovn_port, skip_trusted_port=True):
|
||||
info = {'security_groups': ovn_port.external_ids.get(
|
||||
constants.OVN_SG_IDS_EXT_ID_KEY, '').split(),
|
||||
'device_owner': ovn_port.external_ids.get(
|
||||
constants.OVN_DEVICE_OWNER_EXT_ID_KEY, '')}
|
||||
return get_lsp_security_groups(info, skip_trusted_port=skip_trusted_port)
|
||||
|
||||
|
||||
def get_ovn_port_addresses(ovn_port):
|
||||
addresses = remove_macs_from_lsp_addresses(ovn_port.addresses)
|
||||
port_security = remove_macs_from_lsp_addresses(ovn_port.port_security)
|
||||
return list(set(addresses + port_security))
|
||||
|
||||
|
||||
def sort_ips_by_version(addresses):
|
||||
ip_map = {'ip4': [], 'ip6': []}
|
||||
for addr in addresses:
|
||||
ip_version = netaddr.IPNetwork(addr).version
|
||||
ip_map['ip%d' % ip_version].append(addr)
|
||||
return ip_map
|
||||
|
||||
|
||||
def is_lsp_router_port(port):
|
||||
return port.get('device_owner') in [const.DEVICE_OWNER_ROUTER_INTF,
|
||||
const.DEVICE_OWNER_ROUTER_GW]
|
||||
|
||||
|
||||
def get_lrouter_ext_gw_static_route(ovn_router):
|
||||
# TODO(lucasagomes): Remove the try...except block after OVS 2.8.2
|
||||
# is tagged.
|
||||
try:
|
||||
return [route for route in getattr(ovn_router, 'static_routes', []) if
|
||||
strutils.bool_from_string(getattr(
|
||||
route, 'external_ids', {}).get(
|
||||
constants.OVN_ROUTER_IS_EXT_GW, 'false'))]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def get_lrouter_snats(ovn_router):
|
||||
return [n for n in getattr(ovn_router, 'nat', []) if n.type == 'snat']
|
||||
|
||||
|
||||
def get_lrouter_non_gw_routes(ovn_router):
|
||||
routes = []
|
||||
# TODO(lucasagomes): Remove the try...except block after OVS 2.8.2
|
||||
# is tagged.
|
||||
try:
|
||||
for route in getattr(ovn_router, 'static_routes', []):
|
||||
external_ids = getattr(route, 'external_ids', {})
|
||||
if strutils.bool_from_string(
|
||||
external_ids.get(constants.OVN_ROUTER_IS_EXT_GW, 'false')):
|
||||
continue
|
||||
|
||||
routes.append({'destination': route.ip_prefix,
|
||||
'nexthop': route.nexthop})
|
||||
except KeyError:
|
||||
pass
|
||||
return routes
|
||||
|
||||
|
||||
def is_ovn_l3(l3_plugin):
|
||||
return hasattr(l3_plugin, '_ovn_client_inst')
|
||||
|
||||
|
||||
def get_system_dns_resolvers(resolver_file=DNS_RESOLVER_FILE):
|
||||
resolvers = []
|
||||
if not os.path.exists(resolver_file):
|
||||
return resolvers
|
||||
|
||||
with open(resolver_file, 'r') as rconf:
|
||||
for line in rconf.readlines():
|
||||
if not line.startswith('nameserver'):
|
||||
continue
|
||||
|
||||
line = line.split('nameserver')[1].strip()
|
||||
ipv4 = re.search(r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}', line)
|
||||
if ipv4:
|
||||
resolvers.append(ipv4.group(0))
|
||||
return resolvers
|
||||
|
||||
|
||||
def get_port_subnet_ids(port):
|
||||
fixed_ips = [ip for ip in port['fixed_ips']]
|
||||
return [f['subnet_id'] for f in fixed_ips]
|
||||
|
||||
|
||||
def get_ovsdb_connection(connection_string, schema, timeout, tables=None):
|
||||
helper = idlutils.get_schema_helper(connection_string, schema)
|
||||
if tables:
|
||||
for table in tables:
|
||||
helper.register_table(table)
|
||||
else:
|
||||
helper.register_all()
|
||||
return connection.Connection(idl.Idl(connection_string, helper), timeout)
|
||||
|
||||
|
||||
def get_method_class(method):
|
||||
if not inspect.ismethod(method):
|
||||
return
|
||||
return method.__self__.__class__
|
||||
|
||||
|
||||
def ovn_metadata_name(id_):
|
||||
"""Return the OVN metadata name based on an id."""
|
||||
return 'metadata-%s' % id_
|
||||
|
||||
|
||||
def is_gateway_chassis_invalid(chassis_name, gw_chassis,
|
||||
physnet, chassis_physnets):
|
||||
"""Check if gateway chassis is invalid
|
||||
|
||||
@param chassis_name: gateway chassis name
|
||||
@type chassis_name: string
|
||||
@param gw_chassis: List of gateway chassis in the system
|
||||
@type gw_chassis: []
|
||||
@param physnet: physical network associated to chassis_name
|
||||
@type physnet: string
|
||||
@param chassis_physnets: Dictionary linking chassis with their physnets
|
||||
@type chassis_physnets: {}
|
||||
@return Boolean
|
||||
"""
|
||||
|
||||
if chassis_name == constants.OVN_GATEWAY_INVALID_CHASSIS:
|
||||
return True
|
||||
elif chassis_name not in chassis_physnets:
|
||||
return True
|
||||
elif physnet and physnet not in chassis_physnets.get(chassis_name):
|
||||
return True
|
||||
elif gw_chassis and chassis_name not in gw_chassis:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_provider_network(network):
|
||||
return external_net.EXTERNAL in network
|
||||
|
||||
|
||||
def is_neutron_dhcp_agent_port(port):
|
||||
"""Check if the given DHCP port belongs to Neutron DHCP agents
|
||||
|
||||
The DHCP ports with the device_id equals to 'reserved_dhcp_port'
|
||||
or starting with the word 'dhcp' belongs to the Neutron DHCP agents.
|
||||
"""
|
||||
return (port['device_owner'] == const.DEVICE_OWNER_DHCP and
|
||||
(port['device_id'] == const.DEVICE_ID_RESERVED_DHCP_PORT or
|
||||
port['device_id'].startswith('dhcp')))
|
||||
|
||||
|
||||
def compute_address_pairs_diff(ovn_port, neutron_port):
|
||||
"""Compute the differences in the allowed_address_pairs field."""
|
||||
ovn_ap = get_allowed_address_pairs_ip_addresses_from_ovn_port(
|
||||
ovn_port)
|
||||
neutron_ap = get_allowed_address_pairs_ip_addresses(neutron_port)
|
||||
added = set(neutron_ap) - set(ovn_ap)
|
||||
removed = set(ovn_ap) - set(neutron_ap)
|
||||
return AddrPairsDiff(added, removed, changed=any(added or removed))
|
||||
|
||||
|
||||
def is_gateway_chassis(chassis):
|
||||
"""Check if the given chassis is a gateway chassis"""
|
||||
external_ids = getattr(chassis, 'external_ids', {})
|
||||
return ('enable-chassis-as-gw' in external_ids.get(
|
||||
'ovn-cms-options', '').split(','))
|
||||
|
||||
|
||||
def get_port_capabilities(port):
|
||||
"""Return a list of port's capabilities"""
|
||||
return port.get(portbindings.PROFILE, {}).get('capabilities', [])
|
0
neutron/conf/plugins/ml2/drivers/ovn/__init__.py
Normal file
0
neutron/conf/plugins/ml2/drivers/ovn/__init__.py
Normal file
293
neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py
Normal file
293
neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py
Normal file
@ -0,0 +1,293 @@
|
||||
# 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.
|
||||
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from ovsdbapp.backend.ovs_idl import vlog
|
||||
|
||||
from neutron._i18n import _
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
EXTRA_LOG_LEVEL_DEFAULTS = [
|
||||
]
|
||||
|
||||
VLOG_LEVELS = {'CRITICAL': vlog.CRITICAL, 'ERROR': vlog.ERROR, 'WARNING':
|
||||
vlog.WARN, 'INFO': vlog.INFO, 'DEBUG': vlog.DEBUG}
|
||||
|
||||
ovn_opts = [
|
||||
cfg.StrOpt('ovn_nb_connection',
|
||||
default='tcp:127.0.0.1:6641',
|
||||
help=_('The connection string for the OVN_Northbound OVSDB.\n'
|
||||
'Use tcp:IP:PORT for TCP connection.\n'
|
||||
'Use ssl:IP:PORT for SSL connection. The '
|
||||
'ovn_nb_private_key, ovn_nb_certificate and '
|
||||
'ovn_nb_ca_cert are mandatory.\n'
|
||||
'Use unix:FILE for unix domain socket connection.')),
|
||||
cfg.StrOpt('ovn_nb_private_key',
|
||||
default='',
|
||||
help=_('The PEM file with private key for SSL connection to '
|
||||
'OVN-NB-DB')),
|
||||
cfg.StrOpt('ovn_nb_certificate',
|
||||
default='',
|
||||
help=_('The PEM file with certificate that certifies the '
|
||||
'private key specified in ovn_nb_private_key')),
|
||||
cfg.StrOpt('ovn_nb_ca_cert',
|
||||
default='',
|
||||
help=_('The PEM file with CA certificate that OVN should use to'
|
||||
' verify certificates presented to it by SSL peers')),
|
||||
cfg.StrOpt('ovn_sb_connection',
|
||||
default='tcp:127.0.0.1:6642',
|
||||
help=_('The connection string for the OVN_Southbound OVSDB.\n'
|
||||
'Use tcp:IP:PORT for TCP connection.\n'
|
||||
'Use ssl:IP:PORT for SSL connection. The '
|
||||
'ovn_sb_private_key, ovn_sb_certificate and '
|
||||
'ovn_sb_ca_cert are mandatory.\n'
|
||||
'Use unix:FILE for unix domain socket connection.')),
|
||||
cfg.StrOpt('ovn_sb_private_key',
|
||||
default='',
|
||||
help=_('The PEM file with private key for SSL connection to '
|
||||
'OVN-SB-DB')),
|
||||
cfg.StrOpt('ovn_sb_certificate',
|
||||
default='',
|
||||
help=_('The PEM file with certificate that certifies the '
|
||||
'private key specified in ovn_sb_private_key')),
|
||||
cfg.StrOpt('ovn_sb_ca_cert',
|
||||
default='',
|
||||
help=_('The PEM file with CA certificate that OVN should use to'
|
||||
' verify certificates presented to it by SSL peers')),
|
||||
cfg.IntOpt('ovsdb_connection_timeout',
|
||||
default=180,
|
||||
help=_('Timeout in seconds for the OVSDB '
|
||||
'connection transaction')),
|
||||
cfg.IntOpt('ovsdb_retry_max_interval',
|
||||
default=180,
|
||||
help=_('Max interval in seconds between '
|
||||
'each retry to get the OVN NB and SB IDLs')),
|
||||
cfg.IntOpt('ovsdb_probe_interval',
|
||||
min=0,
|
||||
default=60000,
|
||||
help=_('The probe interval in for the OVSDB session in '
|
||||
'milliseconds. If this is zero, it disables the '
|
||||
'connection keepalive feature. If non-zero the value '
|
||||
'will be forced to at least 1000 milliseconds. Defaults '
|
||||
'to 60 seconds.')),
|
||||
cfg.StrOpt('neutron_sync_mode',
|
||||
default='log',
|
||||
choices=('off', 'log', 'repair'),
|
||||
help=_('The synchronization mode of OVN_Northbound OVSDB '
|
||||
'with Neutron DB.\n'
|
||||
'off - synchronization is off \n'
|
||||
'log - during neutron-server startup, '
|
||||
'check to see if OVN is in sync with '
|
||||
'the Neutron database. '
|
||||
' Log warnings for any inconsistencies found so'
|
||||
' that an admin can investigate \n'
|
||||
'repair - during neutron-server startup, automatically'
|
||||
' create resources found in Neutron but not in OVN.'
|
||||
' Also remove resources from OVN'
|
||||
' that are no longer in Neutron.')),
|
||||
cfg.BoolOpt('ovn_l3_mode',
|
||||
default=True,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="This option is no longer used. Native L3 "
|
||||
"support in OVN is always used.",
|
||||
help=_('Whether to use OVN native L3 support. Do not change '
|
||||
'the value for existing deployments that contain '
|
||||
'routers.')),
|
||||
cfg.StrOpt("ovn_l3_scheduler",
|
||||
default='leastloaded',
|
||||
choices=('leastloaded', 'chance'),
|
||||
help=_('The OVN L3 Scheduler type used to schedule router '
|
||||
'gateway ports on hypervisors/chassis. \n'
|
||||
'leastloaded - chassis with fewest gateway ports '
|
||||
'selected \n'
|
||||
'chance - chassis randomly selected')),
|
||||
cfg.BoolOpt('enable_distributed_floating_ip',
|
||||
default=False,
|
||||
help=_('Enable distributed floating IP support.\n'
|
||||
'If True, the NAT action for floating IPs will be done '
|
||||
'locally and not in the centralized gateway. This '
|
||||
'saves the path to the external network. This requires '
|
||||
'the user to configure the physical network map '
|
||||
'(i.e. ovn-bridge-mappings) on each compute node.')),
|
||||
cfg.StrOpt("vif_type",
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason="The port VIF type is now determined based "
|
||||
"on the OVN chassis information when the "
|
||||
"port is bound to a host.",
|
||||
default=portbindings.VIF_TYPE_OVS,
|
||||
help=_("Type of VIF to be used for ports valid values are "
|
||||
"(%(ovs)s, %(dpdk)s) default %(ovs)s") % {
|
||||
"ovs": portbindings.VIF_TYPE_OVS,
|
||||
"dpdk": portbindings.VIF_TYPE_VHOST_USER},
|
||||
choices=[portbindings.VIF_TYPE_OVS,
|
||||
portbindings.VIF_TYPE_VHOST_USER]),
|
||||
cfg.StrOpt("vhost_sock_dir",
|
||||
default="/var/run/openvswitch",
|
||||
help=_("The directory in which vhost virtio socket "
|
||||
"is created by all the vswitch daemons")),
|
||||
cfg.IntOpt('dhcp_default_lease_time',
|
||||
default=(12 * 60 * 60),
|
||||
help=_('Default least time (in seconds) to use with '
|
||||
'OVN\'s native DHCP service.')),
|
||||
cfg.StrOpt("ovsdb_log_level",
|
||||
default="INFO",
|
||||
choices=list(VLOG_LEVELS.keys()),
|
||||
help=_("The log level used for OVSDB")),
|
||||
cfg.BoolOpt('ovn_metadata_enabled',
|
||||
default=False,
|
||||
help=_('Whether to use metadata service.')),
|
||||
cfg.ListOpt('dns_servers',
|
||||
default=[],
|
||||
help=_("Comma-separated list of the DNS servers which will be "
|
||||
"used as forwarders if a subnet's dns_nameservers "
|
||||
"field is empty. If both subnet's dns_nameservers and "
|
||||
"this option is empty, then the DNS resolvers on the "
|
||||
"host running the neutron server will be used.")),
|
||||
cfg.DictOpt('ovn_dhcp4_global_options',
|
||||
default={},
|
||||
help=_("Dictionary of global DHCPv4 options which will be "
|
||||
"automatically set on each subnet upon creation and "
|
||||
"on all existing subnets when Neutron starts.\n"
|
||||
"An empty value for a DHCP option will cause that "
|
||||
"option to be unset globally.\n"
|
||||
"EXAMPLES:\n"
|
||||
"- ntp_server:1.2.3.4,wpad:1.2.3.5 - Set ntp_server "
|
||||
"and wpad\n"
|
||||
"- ntp_server:,wpad:1.2.3.5 - Unset ntp_server and "
|
||||
"set wpad\n"
|
||||
"See the ovn-nb(5) man page for available options.")),
|
||||
cfg.DictOpt('ovn_dhcp6_global_options',
|
||||
default={},
|
||||
help=_("Dictionary of global DHCPv6 options which will be "
|
||||
"automatically set on each subnet upon creation and "
|
||||
"on all existing subnets when Neutron starts.\n"
|
||||
"An empty value for a DHCP option will cause that "
|
||||
"option to be unset globally.\n"
|
||||
"EXAMPLES:\n"
|
||||
"- ntp_server:1.2.3.4,wpad:1.2.3.5 - Set ntp_server "
|
||||
"and wpad\n"
|
||||
"- ntp_server:,wpad:1.2.3.5 - Unset ntp_server and "
|
||||
"set wpad\n"
|
||||
"See the ovn-nb(5) man page for available options.")),
|
||||
cfg.BoolOpt('ovn_emit_need_to_frag',
|
||||
default=False,
|
||||
help=_('Configure OVN to emit "need to frag" packets in '
|
||||
'case of MTU mismatch.\n'
|
||||
'Before enabling this configuration make sure that '
|
||||
'its supported by the host kernel (version >= 5.2) '
|
||||
'or by checking the output of the following command: \n'
|
||||
'ovs-appctl -t ovs-vswitchd dpif/show-dp-features '
|
||||
'br-int | grep "Check pkt length action".')),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(ovn_opts, group='ovn')
|
||||
|
||||
|
||||
def list_opts():
|
||||
return [
|
||||
('ovn', ovn_opts),
|
||||
]
|
||||
|
||||
|
||||
def get_ovn_nb_connection():
|
||||
return cfg.CONF.ovn.ovn_nb_connection
|
||||
|
||||
|
||||
def get_ovn_nb_private_key():
|
||||
return cfg.CONF.ovn.ovn_nb_private_key
|
||||
|
||||
|
||||
def get_ovn_nb_certificate():
|
||||
return cfg.CONF.ovn.ovn_nb_certificate
|
||||
|
||||
|
||||
def get_ovn_nb_ca_cert():
|
||||
return cfg.CONF.ovn.ovn_nb_ca_cert
|
||||
|
||||
|
||||
def get_ovn_sb_connection():
|
||||
return cfg.CONF.ovn.ovn_sb_connection
|
||||
|
||||
|
||||
def get_ovn_sb_private_key():
|
||||
return cfg.CONF.ovn.ovn_sb_private_key
|
||||
|
||||
|
||||
def get_ovn_sb_certificate():
|
||||
return cfg.CONF.ovn.ovn_sb_certificate
|
||||
|
||||
|
||||
def get_ovn_sb_ca_cert():
|
||||
return cfg.CONF.ovn.ovn_sb_ca_cert
|
||||
|
||||
|
||||
def get_ovn_ovsdb_timeout():
|
||||
return cfg.CONF.ovn.ovsdb_connection_timeout
|
||||
|
||||
|
||||
def get_ovn_ovsdb_retry_max_interval():
|
||||
return cfg.CONF.ovn.ovsdb_retry_max_interval
|
||||
|
||||
|
||||
def get_ovn_ovsdb_probe_interval():
|
||||
return cfg.CONF.ovn.ovsdb_probe_interval
|
||||
|
||||
|
||||
def get_ovn_neutron_sync_mode():
|
||||
return cfg.CONF.ovn.neutron_sync_mode
|
||||
|
||||
|
||||
def is_ovn_l3():
|
||||
return cfg.CONF.ovn.ovn_l3_mode
|
||||
|
||||
|
||||
def get_ovn_l3_scheduler():
|
||||
return cfg.CONF.ovn.ovn_l3_scheduler
|
||||
|
||||
|
||||
def is_ovn_distributed_floating_ip():
|
||||
return cfg.CONF.ovn.enable_distributed_floating_ip
|
||||
|
||||
|
||||
def get_ovn_vhost_sock_dir():
|
||||
return cfg.CONF.ovn.vhost_sock_dir
|
||||
|
||||
|
||||
def get_ovn_dhcp_default_lease_time():
|
||||
return cfg.CONF.ovn.dhcp_default_lease_time
|
||||
|
||||
|
||||
def get_ovn_ovsdb_log_level():
|
||||
return VLOG_LEVELS[cfg.CONF.ovn.ovsdb_log_level]
|
||||
|
||||
|
||||
def is_ovn_metadata_enabled():
|
||||
return cfg.CONF.ovn.ovn_metadata_enabled
|
||||
|
||||
|
||||
def get_dns_servers():
|
||||
return cfg.CONF.ovn.dns_servers
|
||||
|
||||
|
||||
def get_global_dhcpv4_opts():
|
||||
return cfg.CONF.ovn.ovn_dhcp4_global_options
|
||||
|
||||
|
||||
def get_global_dhcpv6_opts():
|
||||
return cfg.CONF.ovn.ovn_dhcp6_global_options
|
||||
|
||||
|
||||
def is_ovn_emit_need_to_frag_enabled():
|
||||
return cfg.CONF.ovn.ovn_emit_need_to_frag
|
0
neutron/tests/unit/common/ovn/__init__.py
Normal file
0
neutron/tests/unit/common/ovn/__init__.py
Normal file
134
neutron/tests/unit/common/ovn/test_hash_ring_manager.py
Normal file
134
neutron/tests/unit/common/ovn/test_hash_ring_manager.py
Normal file
@ -0,0 +1,134 @@
|
||||
# Copyright 2019 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 datetime
|
||||
|
||||
import mock
|
||||
from neutron_lib import context
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from neutron.common.ovn import constants
|
||||
from neutron.common.ovn import exceptions
|
||||
from neutron.common.ovn import hash_ring_manager
|
||||
from neutron.db import ovn_hash_ring_db as db_hash_ring
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
HASH_RING_TEST_GROUP = 'test_group'
|
||||
|
||||
|
||||
class TestHashRingManager(testlib_api.SqlTestCaseLight):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHashRingManager, self).setUp()
|
||||
self.hash_ring_manager = hash_ring_manager.HashRingManager(
|
||||
HASH_RING_TEST_GROUP)
|
||||
self.admin_ctx = context.get_admin_context()
|
||||
|
||||
def _verify_hashes(self, hash_dict):
|
||||
for target_node, uuid_ in hash_dict.items():
|
||||
self.assertEqual(target_node,
|
||||
self.hash_ring_manager.get_node(uuid_))
|
||||
|
||||
def test_get_node(self):
|
||||
# Use pre-defined UUIDs to make the hashes predictable
|
||||
node_1_uuid = db_hash_ring.add_node(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP, 'node-1')
|
||||
node_2_uuid = db_hash_ring.add_node(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP, 'node-2')
|
||||
|
||||
hash_dict_before = {node_1_uuid: 'fake-uuid',
|
||||
node_2_uuid: 'fake-uuid-0'}
|
||||
self._verify_hashes(hash_dict_before)
|
||||
|
||||
def test_get_node_no_active_nodes(self):
|
||||
self.assertRaises(
|
||||
exceptions.HashRingIsEmpty, self.hash_ring_manager.get_node,
|
||||
'fake-uuid')
|
||||
|
||||
def test_ring_rebalance(self):
|
||||
# Use pre-defined UUIDs to make the hashes predictable
|
||||
node_1_uuid = db_hash_ring.add_node(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP, 'node-1')
|
||||
node_2_uuid = db_hash_ring.add_node(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP, 'node-2')
|
||||
|
||||
# Add another node from a different host
|
||||
with mock.patch.object(db_hash_ring, 'CONF') as mock_conf:
|
||||
mock_conf.host = 'another-host-52359446-c366'
|
||||
another_host_node = db_hash_ring.add_node(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP, 'another-host')
|
||||
|
||||
# Assert all nodes are alive in the ring
|
||||
self.hash_ring_manager.refresh()
|
||||
self.assertEqual(3, len(self.hash_ring_manager._hash_ring.nodes))
|
||||
|
||||
# Hash certain values against the nodes
|
||||
hash_dict_before = {node_1_uuid: 'fake-uuid',
|
||||
node_2_uuid: 'fake-uuid-0',
|
||||
another_host_node: 'fake-uuid-ABCDE'}
|
||||
self._verify_hashes(hash_dict_before)
|
||||
|
||||
# Mock utcnow() as the HASH_RING_NODES_TIMEOUT have expired
|
||||
# already and touch the nodes from our host
|
||||
fake_utcnow = timeutils.utcnow() - datetime.timedelta(
|
||||
seconds=constants.HASH_RING_NODES_TIMEOUT)
|
||||
with mock.patch.object(timeutils, 'utcnow') as mock_utcnow:
|
||||
mock_utcnow.return_value = fake_utcnow
|
||||
db_hash_ring.touch_nodes_from_host(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP)
|
||||
|
||||
# Now assert that the ring was re-balanced and only the node from
|
||||
# another host is marked as alive
|
||||
self.hash_ring_manager.refresh()
|
||||
self.assertEqual([another_host_node],
|
||||
list(self.hash_ring_manager._hash_ring.nodes.keys()))
|
||||
|
||||
# Now only "another_host_node" is alive, all values should hash to it
|
||||
hash_dict_after_rebalance = {another_host_node: 'fake-uuid',
|
||||
another_host_node: 'fake-uuid-0',
|
||||
another_host_node: 'fake-uuid-ABCDE'}
|
||||
self._verify_hashes(hash_dict_after_rebalance)
|
||||
|
||||
# Now touch the nodes so they appear active again
|
||||
db_hash_ring.touch_nodes_from_host(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP)
|
||||
self.hash_ring_manager.refresh()
|
||||
|
||||
# The ring should re-balance and as it was before
|
||||
self._verify_hashes(hash_dict_before)
|
||||
|
||||
def test__wait_startup_before_caching(self):
|
||||
db_hash_ring.add_node(self.admin_ctx, HASH_RING_TEST_GROUP, 'node-1')
|
||||
db_hash_ring.add_node(self.admin_ctx, HASH_RING_TEST_GROUP, 'node-2')
|
||||
|
||||
# Assert it will return True until created_at != updated_at
|
||||
self.assertTrue(self.hash_ring_manager._wait_startup_before_caching)
|
||||
self.assertTrue(self.hash_ring_manager._cache_startup_timeout)
|
||||
|
||||
# Touch the nodes (== update the updated_at column)
|
||||
db_hash_ring.touch_nodes_from_host(
|
||||
self.admin_ctx, HASH_RING_TEST_GROUP)
|
||||
|
||||
# Assert it's now False. Waiting is not needed anymore
|
||||
self.assertFalse(self.hash_ring_manager._wait_startup_before_caching)
|
||||
self.assertFalse(self.hash_ring_manager._cache_startup_timeout)
|
||||
|
||||
# Now assert that since the _cache_startup_timeout has been
|
||||
# flipped, we no longer will read from the database
|
||||
with mock.patch.object(hash_ring_manager.db_hash_ring,
|
||||
'get_active_nodes') as get_nodes_mock:
|
||||
self.assertFalse(
|
||||
self.hash_ring_manager._wait_startup_before_caching)
|
||||
self.assertFalse(get_nodes_mock.called)
|
105
neutron/tests/unit/common/ovn/test_utils.py
Normal file
105
neutron/tests/unit/common/ovn/test_utils.py
Normal file
@ -0,0 +1,105 @@
|
||||
# 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 fixtures
|
||||
|
||||
from neutron.common.ovn import constants
|
||||
from neutron.common.ovn import utils
|
||||
from neutron.tests import base
|
||||
|
||||
RESOLV_CONF_TEMPLATE = """# TEST TEST TEST
|
||||
# Geneated by OVN test
|
||||
nameserver 10.0.0.1
|
||||
#nameserver 10.0.0.2
|
||||
nameserver 10.0.0.3
|
||||
nameserver foo 10.0.0.4
|
||||
nameserver aef0::4
|
||||
foo 10.0.0.5
|
||||
"""
|
||||
|
||||
|
||||
class TestUtils(base.BaseTestCase):
|
||||
|
||||
def test_get_system_dns_resolvers(self):
|
||||
tempdir = self.useFixture(fixtures.TempDir()).path
|
||||
resolver_file_name = tempdir + '/resolv.conf'
|
||||
tmp_resolv_file = open(resolver_file_name, 'w')
|
||||
tmp_resolv_file.writelines(RESOLV_CONF_TEMPLATE)
|
||||
tmp_resolv_file.close()
|
||||
expected_dns_resolvers = ['10.0.0.1', '10.0.0.3']
|
||||
observed_dns_resolvers = utils.get_system_dns_resolvers(
|
||||
resolver_file=resolver_file_name)
|
||||
self.assertEqual(expected_dns_resolvers, observed_dns_resolvers)
|
||||
|
||||
|
||||
class TestGateWayChassisValidity(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestGateWayChassisValidity, self).setUp()
|
||||
self.gw_chassis = ['host1', 'host2']
|
||||
self.chassis_name = self.gw_chassis[0]
|
||||
self.physnet = 'physical-nw-1'
|
||||
self.chassis_physnets = {self.chassis_name: [self.physnet]}
|
||||
|
||||
def test_gateway_chassis_valid(self):
|
||||
# Return False, since everything is valid
|
||||
self.assertFalse(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
|
||||
def test_gateway_chassis_due_to_invalid_chassis_name(self):
|
||||
# Return True since chassis is invalid
|
||||
self.chassis_name = constants.OVN_GATEWAY_INVALID_CHASSIS
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
|
||||
def test_gateway_chassis_for_chassis_not_in_chassis_physnets(self):
|
||||
# Return True since chassis is not in chassis_physnets
|
||||
self.chassis_name = 'host-2'
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
|
||||
def test_gateway_chassis_for_undefined_physnet(self):
|
||||
# Return True since physnet is not defined
|
||||
self.chassis_name = 'host-1'
|
||||
self.physnet = None
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
|
||||
def test_gateway_chassis_for_physnet_not_in_chassis_physnets(self):
|
||||
# Return True since physnet is not in chassis_physnets
|
||||
self.physnet = 'physical-nw-2'
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
|
||||
def test_gateway_chassis_for_gw_chassis_empty(self):
|
||||
# Return False if gw_chassis is []
|
||||
# This condition states that the chassis is valid, has valid
|
||||
# physnets and there are no gw_chassis present in the system.
|
||||
self.gw_chassis = []
|
||||
self.assertFalse(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
||||
|
||||
def test_gateway_chassis_for_chassis_not_in_gw_chassis_list(self):
|
||||
# Return True since chassis_name not in gw_chassis
|
||||
self.gw_chassis = ['host-2']
|
||||
self.assertTrue(utils.is_gateway_chassis_invalid(
|
||||
self.chassis_name, self.gw_chassis, self.physnet,
|
||||
self.chassis_physnets))
|
@ -143,6 +143,7 @@ oslo.config.opts =
|
||||
neutron.ml2 = neutron.opts:list_ml2_conf_opts
|
||||
neutron.ml2.linuxbridge.agent = neutron.opts:list_linux_bridge_opts
|
||||
neutron.ml2.macvtap.agent = neutron.opts:list_macvtap_opts
|
||||
neutron.ml2.ovn = neutron.conf.plugins.ml2.drivers.ovn.ovn_conf:list_opts
|
||||
neutron.ml2.ovs.agent = neutron.opts:list_ovs_opts
|
||||
neutron.ml2.sriov.agent = neutron.opts:list_sriov_agent_opts
|
||||
neutron.ml2.xenapi = neutron.opts:list_xenapi_opts
|
||||
|
Loading…
Reference in New Issue
Block a user