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
|
macvtap-agent.rst
|
||||||
openvswitch-agent.rst
|
openvswitch-agent.rst
|
||||||
sriov-agent.rst
|
sriov-agent.rst
|
||||||
|
ovn.rst
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
: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 = neutron.opts:list_ml2_conf_opts
|
||||||
neutron.ml2.linuxbridge.agent = neutron.opts:list_linux_bridge_opts
|
neutron.ml2.linuxbridge.agent = neutron.opts:list_linux_bridge_opts
|
||||||
neutron.ml2.macvtap.agent = neutron.opts:list_macvtap_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.ovs.agent = neutron.opts:list_ovs_opts
|
||||||
neutron.ml2.sriov.agent = neutron.opts:list_sriov_agent_opts
|
neutron.ml2.sriov.agent = neutron.opts:list_sriov_agent_opts
|
||||||
neutron.ml2.xenapi = neutron.opts:list_xenapi_opts
|
neutron.ml2.xenapi = neutron.opts:list_xenapi_opts
|
||||||
|
Loading…
Reference in New Issue
Block a user