Browse Source

[OVN] Move OVN commons to neutron tree

Move OVN related commons to neutron tree.

Previous paths in networking-ovn tree:
./networking_ovn/common/constants.py -> ./neutron/common/ovn/constants.py
./networking_ovn/common/exceptions.py -> ./neutron/common/ovn/exceptions.py
./networking_ovn/common/utils.py -> ./neutron/common/ovn/utils.py
./networking_ovn/common/hash_ring_manager.py -> neutron/common/ovn/hash_ring_manager.py
./networking_ovn/common/config.py -> ./neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py

Co-Authored-By: Gal Sagie <gal.sagie@huawei.com>
Co-Authored-By: Boden R <bodenvmw@gmail.com>
Co-Authored-By: Daniel Alvarez <dalvarez@redhat.com>
Co-Authored-By: Amitabha Biswas <abiswas@us.ibm.com>
Co-Authored-By: Chandra S Vejendla <csvejend@us.ibm.com>
Co-Authored-By: Babu Shanmugam <bschanmu@redhat.com>
Co-Authored-By: Lucas Alvares Gomes <lucasagomes@gmail.com>
Co-Authored-By: Terry Wilson <twilson@redhat.com>
Co-Authored-By: Ramu Ramamurthy <ramu.ramamurthy@us.ibm.com>
Co-Authored-By: Maciej Józefczyk <mjozefcz@redhat.com>
Co-Authored-By: Gary Kotton <gkotton@vmware.com>
Co-Authored-By: Andrew Austin <aaustin@redhat.com>
Co-Authored-By: Miguel Angel Ajo <majopela@redhat.com>
Co-Authored-By: Brian Haley <bhaley@redhat.com>
Co-Authored-By: Dong Jun <dongj@dtdream.com>
Co-Authored-By: xurong00037997 <xu.rong@zte.com.cn>
Co-Authored-By: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>

Change-Id: Ib46bfdd14a150a324dbf28c6a50c839c5c824e35
Related-Blueprint: neutron-ovn-merge
changes/18/696118/21
Maciej Józefczyk 2 years ago
committed by Slawek Kaplonski
parent
commit
65692127f6
  1. 1
      doc/source/configuration/config.rst
  2. 6
      doc/source/configuration/ovn.rst
  3. 6
      etc/oslo-config-generator/ovn.ini
  4. 0
      neutron/common/ovn/__init__.py
  5. 185
      neutron/common/ovn/constants.py
  6. 38
      neutron/common/ovn/exceptions.py
  7. 100
      neutron/common/ovn/hash_ring_manager.py
  8. 460
      neutron/common/ovn/utils.py
  9. 0
      neutron/conf/plugins/ml2/drivers/ovn/__init__.py
  10. 293
      neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py
  11. 0
      neutron/tests/unit/common/ovn/__init__.py
  12. 134
      neutron/tests/unit/common/ovn/test_hash_ring_manager.py
  13. 105
      neutron/tests/unit/common/ovn/test_utils.py
  14. 1
      setup.cfg

1
doc/source/configuration/config.rst

@ -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

@ -0,0 +1,6 @@
=======
ovn.ini
=======
.. show-options::
:config-file: etc/oslo-config-generator/ovn.ini

6
etc/oslo-config-generator/ovn.ini

@ -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

185
neutron/common/ovn/constants.py

@ -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

@ -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

@ -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

@ -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

293
neutron/conf/plugins/ml2/drivers/ovn/ovn_conf.py

@ -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

134
neutron/tests/unit/common/ovn/test_hash_ring_manager.py

@ -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

@ -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))

1
setup.cfg

@ -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…
Cancel
Save