[OVN] Import ovsdb related code

This patch imports ovsdb related code from networking_ovn.

Previous paths in networking-ovn tree:
./networking_ovn/ovsdb/commands.py ->
  ./neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/commands.py
./networking_ovn/ovsdb/worker.py ->
  ./neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/worker.py
./networking_ovn/ovsdb/ovn_api.py ->
  ./neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/api.py

ACL commands will be implemented when the related code is merged and
could be tested properly.

Related-Blueprint: neutron-ovn-merge

Co-Authored-By: Reedip <rbanerje@redhat.com>
Co-Authored-By: Numan Siddique <nusiddiq@redhat.com>
Co-Authored-By: Flavio Fernandes <flaviof@redhat.com>
Co-Authored-By: Terry Wilson <twilson@redhat.com>
Co-Authored-By: Daniel Alvarez <dalvarez@redhat.com>
Co-Authored-By: Changxun Zhou <zhoucx@dtdream.com>
Co-Authored-By: Gal Sagie <gal.sagie@huawei.com>
Co-Authored-By: Amitabha Biswas <abiswas@us.ibm.com>
Co-Authored-By: Richard Theis <rtheis@us.ibm.com>
Co-Authored-By: lzklibj <lzklibj@cn.ibm.com>
Co-Authored-By: zhufl <zhu.fanglei@zte.com.cn>
Co-Authored-By: Na <nazhu@cn.ibm.com>
Co-Authored-By: Chandra S Vejendla <csvejend@us.ibm.com>
Co-Authored-By: Gary Kotton <gkotton@vmware.com>
Co-Authored-By: Aaron Rosen <aaronorosen@gmail.com>
Co-Authored-By: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>

Change-Id: I9fe64f954d227efaab5e96c6150df44f36a2530a
This commit is contained in:
Rodolfo Alonso Hernandez 2019-11-29 11:11:13 +00:00 committed by Slawek Kaplonski
parent 65692127f6
commit 4b5cf9e5fb
11 changed files with 3893 additions and 0 deletions

View File

@ -0,0 +1,702 @@
# Copyright 2019 Red Hat, Inc.
#
# 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 abc
from ovsdbapp import api
import six
from neutron.common.ovn import constants as ovn_const
@six.add_metaclass(abc.ABCMeta)
class API(api.API):
@abc.abstractmethod
def set_lswitch_ext_ids(self, name, ext_ids, if_exists=True):
"""Create a command to set OVN lswitch external ids
:param name: The name of the lswitch
:type name: string
:param ext_ids The external ids to set for the lswitch
:type ext_ids: dictionary
:param if_exists: Do not fail if lswitch does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def create_lswitch_port(self, lport_name, lswitch_name, may_exist=True,
**columns):
"""Create a command to add an OVN logical switch port
:param lport_name: The name of the lport
:type lport_name: string
:param lswitch_name: The name of the lswitch the lport is created on
:type lswitch_name: string
:param may_exist: Do not fail if lport already exists
:type may_exist: bool
:param columns: Dictionary of port columns
Supported columns: macs, external_ids,
parent_name, tag, enabled
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def set_lswitch_port(self, lport_name, if_exists=True, **columns):
"""Create a command to set OVN logical switch port fields
:param lport_name: The name of the lport
:type lport_name: string
:param columns: Dictionary of port columns
Supported columns: macs, external_ids,
parent_name, tag, enabled
:param if_exists: Do not fail if lport does not exist
:type if_exists: bool
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_lswitch_port(self, lport_name=None, lswitch_name=None,
ext_id=None, if_exists=True):
"""Create a command to delete an OVN logical switch port
:param lport_name: The name of the lport
:type lport_name: string
:param lswitch_name: The name of the lswitch
:type lswitch_name: string
:param ext_id: The external id of the lport
:type ext_id: pair of <ext_id_key ,ext_id_value>
:param if_exists: Do not fail if the lport does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def create_lrouter(self, name, may_exist=True, **columns):
"""Create a command to add an OVN lrouter
:param name: The id of the lrouter
:type name: string
:param may_exist: Do not fail if lrouter already exists
:type may_exist: bool
:param columns: Dictionary of lrouter columns
Supported columns: external_ids, default_gw, ip
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def update_lrouter(self, name, if_exists=True, **columns):
"""Update a command to add an OVN lrouter
:param name: The id of the lrouter
:type name: string
:param if_exists: Do not fail if the lrouter does not exist
:type if_exists: bool
:param columns: Dictionary of lrouter columns
Supported columns: external_ids, default_gw, ip
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_lrouter(self, name, if_exists=True):
"""Create a command to delete an OVN lrouter
:param name: The id of the lrouter
:type name: string
:param if_exists: Do not fail if the lrouter does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def add_lrouter_port(self, name, lrouter, may_exist=True,
**columns):
"""Create a command to add an OVN lrouter port
:param name: The unique name of the lrouter port
:type name: string
:param lrouter: The unique name of the lrouter
:type lrouter: string
:param lswitch: The unique name of the lswitch
:type lswitch: string
:param may_exist: If true, do not fail if lrouter port set
already exists.
:type may_exist: bool
:param columns: Dictionary of lrouter columns
Supported columns: external_ids, mac, network
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def update_lrouter_port(self, name, if_exists=True, **columns):
"""Update a command to add an OVN lrouter port
:param name: The unique name of the lrouter port
:type name: string
:param if_exists: Do not fail if the lrouter port does not exist
:type if_exists: bool
:param columns: Dictionary of lrouter columns
Supported columns: networks
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_lrouter_port(self, name, lrouter, if_exists=True):
"""Create a command to delete an OVN lrouter port
:param name: The unique name of the lport
:type name: string
:param lrouter: The unique name of the lrouter
:type lrouter: string
:param if_exists: Do not fail if the lrouter port does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def set_lrouter_port_in_lswitch_port(
self, lswitch_port, lrouter_port, is_gw_port=False, if_exists=True,
lsp_address=ovn_const.DEFAULT_ADDR_FOR_LSP_WITH_PEER):
"""Create a command to set lswitch_port as lrouter_port
:param lswitch_port: The name of logical switch port
:type lswitch_port: string
:param lrouter_port: The name of logical router port
:type lrouter_port: string
:param is_gw_port: True if logical router port is gw port
:type is_gw_port: bool
:param if_exists: Do not fail if the lswitch port does not exist
:type if_exists: bool
:param lsp_address: logical switch port's addresses to set
:type lsp_address: string or list of strings
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def add_acl(self, lswitch, lport, **columns):
"""Create an ACL for a logical port.
:param lswitch: The logical switch the port is attached to.
:type lswitch: string
:param lport: The logical port this ACL is associated with.
:type lport: string
:param columns: Dictionary of ACL columns
Supported columns: see ACL table in OVN_Northbound
:type columns: dictionary
"""
@abc.abstractmethod
def delete_acl(self, lswitch, lport, if_exists=True):
"""Delete all ACLs for a logical port.
:param lswitch: The logical switch the port is attached to.
:type lswitch: string
:param lport: The logical port this ACL is associated with.
:type lport: string
:param if_exists: Do not fail if the ACL for this lport does not
exist
:type if_exists: bool
"""
@abc.abstractmethod
def update_acls(self, lswitch_names, port_list, acl_new_values_dict,
need_compare=True, is_add_acl=True):
"""Update the list of acls on logical switches with new values.
:param lswitch_names: List of logical switch names
:type lswitch_name: []
:param port_list: Iterator of list of ports
:type port_list: []
:param acl_new_values_dict: Dictionary of acls indexed by port id
:type acl_new_values_dict: {}
:param need_compare: If acl_new_values_dict need compare
with existing acls
:type need_compare: bool
:is_add_acl: If updating is caused by adding acl
:type is_add_acl: bool
"""
@abc.abstractmethod
def get_acl_by_id(self, acl_id):
"""Get an ACL by its ID.
:param acl_id: ID of the ACL to lookup
:type acl_id: string
:returns The ACL row or None:
"""
@abc.abstractmethod
def add_static_route(self, lrouter, **columns):
"""Add static route to logical router.
:param lrouter: The unique name of the lrouter
:type lrouter: string
:param columns: Dictionary of static columns
Supported columns: prefix, nexthop, valid
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_static_route(self, lrouter, ip_prefix, nexthop, if_exists=True):
"""Delete static route from logical router.
:param lrouter: The unique name of the lrouter
:type lrouter: string
:param ip_prefix: The prefix of the static route
:type ip_prefix: string
:param nexthop: The nexthop of the static route
:type nexthop: string
:param if_exists: Do not fail if router does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def create_address_set(self, name, may_exist=True, **columns):
"""Create an address set
:param name: The name of the address set
:type name: string
:param may_exist: Do not fail if address set already exists
:type may_exist: bool
:param columns: Dictionary of address set columns
Supported columns: external_ids, addresses
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_address_set(self, name, if_exists=True):
"""Delete an address set
:param name: The name of the address set
:type name: string
:param if_exists: Do not fail if the address set does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def update_address_set(self, name, addrs_add, addrs_remove,
if_exists=True):
"""Updates addresses in an address set
:param name: The name of the address set
:type name: string
:param addrs_add: The addresses to be added
:type addrs_add: []
:param addrs_remove: The addresses to be removed
:type addrs_remove: []
:param if_exists: Do not fail if the address set does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def update_address_set_ext_ids(self, name, external_ids, if_exists=True):
"""Update external IDs for an address set
:param name: The name of the address set
:type name: string
:param external_ids: The external IDs for the address set
:type external_ids: dict
:param if_exists: Do not fail if the address set does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def get_all_chassis_gateway_bindings(self,
chassis_candidate_list=None):
"""Return a dictionary of chassis name:list of gateways
:param chassis_candidate_list: List of possible chassis candidates
:type chassis_candidate_list: []
:returns: {} of chassis to routers mapping
"""
@abc.abstractmethod
def get_gateway_chassis_binding(self, gateway_id):
"""Return the list of chassis to which the gateway is bound to
As one gateway can be hosted by multiple chassis, this method is
returning a list of those chassis ordered by priority. This means
that the first element of the list is the chassis hosting the
gateway with the highest priority (which will likely be where
the router port is going to be active).
:param gateway_id: The gateway id
:type gateway_id: string
:returns: a list of strings with the chassis names
"""
@abc.abstractmethod
def get_unhosted_gateways(self, port_physnet_dict, chassis_physnets,
gw_chassis):
"""Return a list of gateways not hosted on chassis
:param port_physnet_dict: Dictionary of gateway ports and their physnet
:param chassis_physnets: Dictionary of chassis and physnets
:param gw_chassis: List of gateway chassis provided by admin
through ovn-cms-options
:returns: List of gateways not hosted on a valid
chassis
"""
@abc.abstractmethod
def add_dhcp_options(self, subnet_id, port_id=None, may_exist=True,
**columns):
"""Adds the DHCP options specified in the @columns in DHCP_Options
If the DHCP options already exist in the DHCP_Options table for
the @subnet_id (and @lsp_name), updates the row, else creates a new
row.
:param subnet_id: The subnet id to which the DHCP options belong
to
:type subnet_id: string
:param port_id: The port id to which the DHCP options belong to
if specified
:type port_id: string
:param may_exist: If true, checks if the DHCP options for
subnet_id exists or not. If it already exists,
it updates the row with the columns specified.
Else creates a new row.
:type may_exist: bool
:type columns: Dictionary of DHCP_Options columns
Supported columns: see DHCP_Options table in
OVN_Northbound
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_dhcp_options(self, row_uuid, if_exists=True):
"""Deletes the row in DHCP_Options with the @row_uuid
:param row_uuid: The UUID of the row to be deleted.
:type row_uuid: string
:param if_exists: Do not fail if the DHCP_Options row does not
exist
:type if_exists: bool
"""
@abc.abstractmethod
def get_subnet_dhcp_options(self, subnet_id, with_ports=False):
"""Returns the Subnet DHCP options as a dictionary
:param subnet_id: The subnet id whose DHCP options are returned
:type subnet_id: string
:param with_ports: If True, also returns the ports DHCP options.
:type with_ports: bool
:returns: Returns a dictionary containing two keys:
subnet and ports.
"""
@abc.abstractmethod
def get_subnets_dhcp_options(self, subnet_ids):
"""Returns the Subnets DHCP options as list of dictionary
:param subnet_ids: The subnet ids whose DHCP options are returned
:type subnet_ids: list of string
:returns: Returns the columns of the DHCP_Options as list
of dictionary. Empty list is returned if no
DHCP_Options matched found.
"""
@abc.abstractmethod
def get_address_sets(self):
"""Gets all address sets in the OVN_Northbound DB
:returns: dictionary indexed by name, DB columns as values
"""
@abc.abstractmethod
def get_port_groups(self):
"""Gets all port groups in the OVN_Northbound DB
:returns: dictionary indexed by name, DB columns as values
"""
@abc.abstractmethod
def get_router_port_options(self, lsp_name):
"""Get options set for lsp of type router
:returns: router port options
"""
@abc.abstractmethod
def add_nat_rule_in_lrouter(self, lrouter, **columns):
"""Add NAT rule in logical router
:param lrouter: The unique name of the lrouter
:type lrouter: string
:param columns: Dictionary of nat columns
Supported columns: type, logical_ip, external_ip
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def delete_nat_rule_in_lrouter(self, lrouter, type, logical_ip,
external_ip, if_exists=True):
"""Delete NAT rule in logical router
:param lrouter: The unique name of the lrouter
:type lrouter: string
:param type: Type of nat. Supported values are 'snat', 'dnat'
and 'dnat_and_snat'
:type type: string
:param logical_ip: IP or network that needs to be natted
:type logical_ip: string
:param external_ip: External IP to be used for nat
:type external_ip: string
:param if_exists: Do not fail if the Logical_Router row does not
exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def get_lrouter_nat_rules(self, lrouter):
"""Returns the nat rules of a router
:param lrouter: The unique name of the router
:type lrouter: string
:returns: A list of nat rules of the router, with each item
as a dict with the keys - 'external_ip', 'logical_ip'
'type' and 'uuid' of the row.
"""
@abc.abstractmethod
def set_nat_rule_in_lrouter(self, lrouter, nat_rule_uuid, **columns):
"""Sets the NAT rule fields
:param lrouter: The unique name of the router to which this the
NAT rule belongs to.
:type lrouter: string
:param nat_rule_uuid: The uuid of the NAT rule row to be updated.
:type nat_rule_uuid: string
:type columns: dictionary
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def get_lswitch(self, lswitch_name):
"""Returns the logical switch
:param lswitch_name: The unique name of the logical switch
:type lswitch_name: string
:returns: Returns logical switch or None
"""
@abc.abstractmethod
def get_ls_and_dns_record(self, lswitch_name):
"""Returns the logical switch and 'dns' records
:param lswitch_name: The unique name of the logical switch
:type lswitch_name: string
:returns: Returns logical switch and dns records as a tuple
"""
@abc.abstractmethod
def get_floatingip(self, fip_id):
"""Get a Floating IP by its ID
:param fip_id: The floating IP id
:type fip_id: string
:returns: The NAT rule row or None
"""
@abc.abstractmethod
def get_floatingip_by_ips(self, router_id, logical_ip, external_ip):
"""Get a Floating IP based on it's logical and external IPs.
DEPRECATED. In the Rocky release of OpenStack this method can be
removed and get_floatingip() should be used instead. This method
is a backward compatibility layer for the Pike -> Queens release.
:param router_id: The ID of the router to which the FIP belongs to.
:type lrouter: string
:param logical_ip: The FIP's logical IP address
:type logical_ip: string
:param external_ip: The FIP's external IP address
:type external_ip: string
:returns: The NAT rule row or None
"""
def check_revision_number(self, name, resource, resource_type,
if_exists=True):
"""Compare the revision number from Neutron and OVN.
Check if the revision number in OVN is lower than the one from
the Neutron resource, otherwise raise RevisionConflict and abort
the transaction.
:param name: The unique name of the resource
:type name: string
:param resource: The neutron resource object
:type resource: dictionary
:param resource_type: The resource object type
:type resource_type: dictionary
:param if_exists: Do not fail if resource does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
:raise: RevisionConflict if the revision number in
OVN is equal or higher than the neutron object
"""
@abc.abstractmethod
def get_lswitch_port(self, lsp_name):
"""Get a Logical Switch Port by its name.
:param lsp_name: The Logical Switch Port name
:type lsp_name: string
:returns: The Logical Switch Port row or None
"""
@abc.abstractmethod
def get_lrouter(self, lrouter_name):
"""Get a Logical Router by its name
:param lrouter_name: The name of the logical router
:type lrouter_name: string
:returns: The Logical_Router row or None
"""
@abc.abstractmethod
def delete_lrouter_ext_gw(self, lrouter_name):
"""Delete Logical Router external gateway.
:param lrouter_name: The name of the logical router
:type lrouter_name: string
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def get_address_set(self, addrset_id, ip_version='ip4'):
"""Get a Address Set by its ID.
:param addrset_id: The Address Set ID
:type addrset_id: string
:param ip_version: Either "ip4" or "ip6". Defaults to "ip4"
:type addr_name: string
:returns: The Address Set row or None
"""
@abc.abstractmethod
def set_lswitch_port_to_virtual_type(self, lport_name, vip,
virtual_parent, if_exists=True):
"""Set the type of a given port to "virtual".
Set the type of a given port to "virtual" and all its related
options.
:param lport_name: The name of the lport
:type lport_name: string
:param vip: The virtual ip
:type vip: string
:param virtual_parent: The name of the parent lport
:type virtual_parent: string
:param if_exists: Do not fail if lport does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def unset_lswitch_port_to_virtual_type(self, lport_name,
virtual_parent, if_exists=True):
"""Unset the type of a given port from "virtual".
Unset the type of a given port from "virtual" and all its related
options.
:param lport_name: The name of the lport
:type lport_name: string
:param virtual_parent: The name of the parent lport
:type virtual_parent: string
:param if_exists: Do not fail if lport does not exist
:type if_exists: bool
:returns: :class:`Command` with no result
"""
@six.add_metaclass(abc.ABCMeta)
class SbAPI(api.API):
@abc.abstractmethod
def chassis_exists(self, hostname):
"""Test if chassis for given hostname exists.
@param hostname: The hostname of the chassis
@type hostname: string
:returns: True if the chassis exists, else False.
"""
@abc.abstractmethod
def get_chassis_hostname_and_physnets(self):
"""Return a dict contains hostname and physnets mapping.
Hostname will be dict key, and a list of physnets will be dict
value. And hostname and physnets are related to the same host.
"""
def get_gateway_chassis_from_cms_options(self):
"""Get chassis eligible for external connectivity from CMS options.
When admin wants to enable router gateway on few chassis,
he would set the external_ids as
ovs-vsctl set open .
external_ids:ovn-cms-options="enable-chassis-as-gw"
In this function, we parse ovn-cms-options and return these chassis
:returns: List with chassis names.
"""
@abc.abstractmethod
def get_chassis_and_physnets(self):
"""Return a dict contains chassis name and physnets mapping.
Chassis name will be dict key, and a list of physnets will be dict
value. And chassis name and physnets are related to the same chassis.
"""
@abc.abstractmethod
def get_all_chassis(self, chassis_type=None):
"""Return a list of all chassis which match the compute_type
:param chassis_type: The type of chassis
:type chassis_type: string
"""
@abc.abstractmethod
def get_chassis_data_for_ml2_bind_port(self, hostname):
"""Return chassis data for ML2 port binding.
@param hostname: The hostname of the chassis
@type hostname: string
:returns: Tuple containing the chassis datapath type,
iface types and physical networks for the
OVN bridge mappings.
:raises: RuntimeError exception if an OVN chassis
does not exist.
"""

View File

@ -0,0 +1,973 @@
# Copyright 2019 Red Hat, Inc.
#
# 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 oslo_utils import timeutils
from ovsdbapp.backend.ovs_idl import command
from ovsdbapp.backend.ovs_idl import idlutils
from neutron._i18n import _
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import exceptions as ovn_exc
from neutron.common.ovn import utils
RESOURCE_TYPE_MAP = {
ovn_const.TYPE_NETWORKS: 'Logical_Switch',
ovn_const.TYPE_PORTS: 'Logical_Switch_Port',
ovn_const.TYPE_ROUTERS: 'Logical_Router',
ovn_const.TYPE_ROUTER_PORTS: 'Logical_Router_Port',
ovn_const.TYPE_FLOATINGIPS: 'NAT',
ovn_const.TYPE_SUBNETS: 'DHCP_Options',
}
def _addvalue_to_list(row, column, new_value):
row.addvalue(column, new_value)
def _delvalue_from_list(row, column, old_value):
row.delvalue(column, old_value)
def _updatevalues_in_list(row, column, new_values=None, old_values=None):
new_values = new_values or []
old_values = old_values or []
for new_value in new_values:
row.addvalue(column, new_value)
for old_value in old_values:
row.delvalue(column, old_value)
def get_lsp_dhcp_options_uuids(lsp, lsp_name):
# Get dhcpv4_options and dhcpv6_options uuids from Logical_Switch_Port,
# which are references of port dhcp options in DHCP_Options table.
uuids = set()
for dhcp_opts in getattr(lsp, 'dhcpv4_options', []):
external_ids = getattr(dhcp_opts, 'external_ids', {})
if external_ids.get('port_id') == lsp_name:
uuids.add(dhcp_opts.uuid)
for dhcp_opts in getattr(lsp, 'dhcpv6_options', []):
external_ids = getattr(dhcp_opts, 'external_ids', {})
if external_ids.get('port_id') == lsp_name:
uuids.add(dhcp_opts.uuid)
return uuids
def _add_gateway_chassis(api, txn, lrp_name, val):
gateway_chassis = api._tables.get('Gateway_Chassis')
if gateway_chassis:
prio = len(val)
uuid_list = []
for chassis in val:
gwc_name = '%s_%s' % (lrp_name, chassis)
try:
gwc = idlutils.row_by_value(api.idl,
'Gateway_Chassis',
'name', gwc_name)
except idlutils.RowNotFound:
gwc = txn.insert(gateway_chassis)
gwc.name = gwc_name
gwc.chassis_name = chassis
gwc.priority = prio
prio = prio - 1
uuid_list.append(gwc.uuid)
return 'gateway_chassis', uuid_list
else:
chassis = {ovn_const.OVN_GATEWAY_CHASSIS_KEY: val[0]}
return 'options', chassis
class CheckLivenessCommand(command.BaseCommand):
def __init__(self, api):
super(CheckLivenessCommand, self).__init__(api)
def run_idl(self, txn):
# txn.pre_commit responsible for updating nb_global.nb_cfg, but
# python-ovs will not update nb_cfg if no other changes are made
self.api.nb_global.setkey('external_ids',
ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY,
str(timeutils.utcnow(with_timezone=True)))
self.result = self.api.nb_global.nb_cfg
class LSwitchSetExternalIdsCommand(command.BaseCommand):
def __init__(self, api, name, ext_ids, if_exists):
super(LSwitchSetExternalIdsCommand, self).__init__(api)
self.name = name
self.ext_ids = ext_ids
self.if_exists = if_exists
def run_idl(self, txn):
try:
lswitch = idlutils.row_by_value(self.api.idl, 'Logical_Switch',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Switch %s does not exist") % self.name
raise RuntimeError(msg)
lswitch.verify('external_ids')
external_ids = getattr(lswitch, 'external_ids', {})
for key, value in self.ext_ids.items():
external_ids[key] = value
lswitch.external_ids = external_ids
class AddLSwitchPortCommand(command.BaseCommand):
def __init__(self, api, lport, lswitch, may_exist, **columns):
super(AddLSwitchPortCommand, self).__init__(api)
self.lport = lport
self.lswitch = lswitch
self.may_exist = may_exist
self.columns = columns
def run_idl(self, txn):
try:
lswitch = idlutils.row_by_value(self.api.idl, 'Logical_Switch',
'name', self.lswitch)
except idlutils.RowNotFound:
msg = _("Logical Switch %s does not exist") % self.lswitch
raise RuntimeError(msg)
if self.may_exist:
port = idlutils.row_by_value(self.api.idl,
'Logical_Switch_Port', 'name',
self.lport, None)
if port:
return
port = txn.insert(self.api._tables['Logical_Switch_Port'])
port.name = self.lport
dhcpv4_options = self.columns.pop('dhcpv4_options', [])
if isinstance(dhcpv4_options, list):
port.dhcpv4_options = dhcpv4_options
else:
port.dhcpv4_options = [dhcpv4_options.result]
dhcpv6_options = self.columns.pop('dhcpv6_options', [])
if isinstance(dhcpv6_options, list):
port.dhcpv6_options = dhcpv6_options
else:
port.dhcpv6_options = [dhcpv6_options.result]
for col, val in self.columns.items():
setattr(port, col, val)
# add the newly created port to existing lswitch
_addvalue_to_list(lswitch, 'ports', port.uuid)
self.result = port.uuid
def post_commit(self, txn):
self.result = txn.get_insert_uuid(self.result)
class SetLSwitchPortCommand(command.BaseCommand):
def __init__(self, api, lport, if_exists, **columns):
super(SetLSwitchPortCommand, self).__init__(api)
self.lport = lport
self.columns = columns
self.if_exists = if_exists
def run_idl(self, txn):
try:
port = idlutils.row_by_value(self.api.idl, 'Logical_Switch_Port',
'name', self.lport)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Switch Port %s does not exist") % self.lport
raise RuntimeError(msg)
# Delete DHCP_Options records no longer referred by this port.
# The table rows should be consistent for the same transaction.
# After we get DHCP_Options rows uuids from port dhcpv4_options
# and dhcpv6_options references, the rows shouldn't disappear for
# this transaction before we delete it.
cur_port_dhcp_opts = get_lsp_dhcp_options_uuids(
port, self.lport)
new_port_dhcp_opts = set()
dhcpv4_options = self.columns.pop('dhcpv4_options', None)
if dhcpv4_options is None:
new_port_dhcp_opts.update([option.uuid for option in
getattr(port, 'dhcpv4_options', [])])
elif isinstance(dhcpv4_options, list):
new_port_dhcp_opts.update(dhcpv4_options)
port.dhcpv4_options = dhcpv4_options
else:
new_port_dhcp_opts.add(dhcpv4_options.result)
port.dhcpv4_options = [dhcpv4_options.result]
dhcpv6_options = self.columns.pop('dhcpv6_options', None)
if dhcpv6_options is None:
new_port_dhcp_opts.update([option.uuid for option in
getattr(port, 'dhcpv6_options', [])])
elif isinstance(dhcpv6_options, list):
new_port_dhcp_opts.update(dhcpv6_options)
port.dhcpv6_options = dhcpv6_options
else:
new_port_dhcp_opts.add(dhcpv6_options.result)
port.dhcpv6_options = [dhcpv6_options.result]
for uuid in cur_port_dhcp_opts - new_port_dhcp_opts:
self.api._tables['DHCP_Options'].rows[uuid].delete()
for col, val in self.columns.items():
setattr(port, col, val)
class DelLSwitchPortCommand(command.BaseCommand):
def __init__(self, api, lport, lswitch, if_exists):
super(DelLSwitchPortCommand, self).__init__(api)
self.lport = lport
self.lswitch = lswitch
self.if_exists = if_exists
def run_idl(self, txn):
try:
lport = idlutils.row_by_value(self.api.idl, 'Logical_Switch_Port',
'name', self.lport)
lswitch = idlutils.row_by_value(self.api.idl, 'Logical_Switch',
'name', self.lswitch)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Port %s does not exist") % self.lport
raise RuntimeError(msg)
# Delete DHCP_Options records no longer referred by this port.
cur_port_dhcp_opts = get_lsp_dhcp_options_uuids(
lport, self.lport)
for uuid in cur_port_dhcp_opts:
self.api._tables['DHCP_Options'].rows[uuid].delete()
_delvalue_from_list(lswitch, 'ports', lport)
self.api._tables['Logical_Switch_Port'].rows[lport.uuid].delete()
class AddLRouterCommand(command.BaseCommand):
def __init__(self, api, name, may_exist, **columns):
super(AddLRouterCommand, self).__init__(api)
self.name = name
self.columns = columns
self.may_exist = may_exist
def run_idl(self, txn):
if self.may_exist:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.name, None)
if lrouter:
return
row = txn.insert(self.api._tables['Logical_Router'])
row.name = self.name
for col, val in self.columns.items():
setattr(row, col, val)
class UpdateLRouterCommand(command.BaseCommand):
def __init__(self, api, name, if_exists, **columns):
super(UpdateLRouterCommand, self).__init__(api)
self.name = name
self.columns = columns
self.if_exists = if_exists
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.name, None)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Router %s does not exist") % self.name
raise RuntimeError(msg)
if lrouter:
for col, val in self.columns.items():
setattr(lrouter, col, val)
return
class DelLRouterCommand(command.BaseCommand):
def __init__(self, api, name, if_exists):
super(DelLRouterCommand, self).__init__(api)
self.name = name
self.if_exists = if_exists
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Router %s does not exist") % self.name
raise RuntimeError(msg)
self.api._tables['Logical_Router'].rows[lrouter.uuid].delete()
class AddLRouterPortCommand(command.BaseCommand):
def __init__(self, api, name, lrouter, may_exist, **columns):
super(AddLRouterPortCommand, self).__init__(api)
self.name = name
self.lrouter = lrouter
self.may_exist = may_exist
self.columns = columns
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
try:
idlutils.row_by_value(self.api.idl, 'Logical_Router_Port',
'name', self.name)
if self.may_exist:
return
# The LRP entry with certain name has already exist, raise an
# exception to notice caller. It's caller's responsibility to
# call UpdateLRouterPortCommand to get LRP entry processed
# correctly.
msg = _("Logical Router Port with name \"%s\" "
"already exists.") % self.name
raise RuntimeError(msg)
except idlutils.RowNotFound:
lrouter_port = txn.insert(self.api._tables['Logical_Router_Port'])
lrouter_port.name = self.name
for col, val in self.columns.items():
if col == 'gateway_chassis':
col, val = _add_gateway_chassis(self.api, txn, self.name,
val)
setattr(lrouter_port, col, val)
_addvalue_to_list(lrouter, 'ports', lrouter_port)
class UpdateLRouterPortCommand(command.BaseCommand):
def __init__(self, api, name, if_exists, **columns):
super(UpdateLRouterPortCommand, self).__init__(api)
self.name = name
self.columns = columns
self.if_exists = if_exists
def run_idl(self, txn):
try:
lrouter_port = idlutils.row_by_value(self.api.idl,
'Logical_Router_Port',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Router Port %s does not exist") % self.name
raise RuntimeError(msg)
# TODO(lucasagomes): Remove this check once we drop the support
# for OVS versions <= 2.8
ipv6_ra_configs_supported = self.api.is_col_present(
'Logical_Router_Port', 'ipv6_ra_configs')
for col, val in self.columns.items():
if col == 'ipv6_ra_configs' and not ipv6_ra_configs_supported:
continue
if col == 'gateway_chassis':
col, val = _add_gateway_chassis(self.api, txn, self.name,
val)
setattr(lrouter_port, col, val)
class DelLRouterPortCommand(command.BaseCommand):
def __init__(self, api, name, lrouter, if_exists):
super(DelLRouterPortCommand, self).__init__(api)
self.name = name
self.lrouter = lrouter
self.if_exists = if_exists
def run_idl(self, txn):
try:
lrouter_port = idlutils.row_by_value(self.api.idl,
'Logical_Router_Port',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Router Port %s does not exist") % self.name
raise RuntimeError(msg)
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
_delvalue_from_list(lrouter, 'ports', lrouter_port)
lrouter_port.delete()
class SetLRouterPortInLSwitchPortCommand(command.BaseCommand):
def __init__(self, api, lswitch_port, lrouter_port, is_gw_port,
if_exists, lsp_address):
super(SetLRouterPortInLSwitchPortCommand, self).__init__(api)
self.lswitch_port = lswitch_port
self.lrouter_port = lrouter_port
self.is_gw_port = is_gw_port
self.if_exists = if_exists
self.lsp_address = lsp_address
def run_idl(self, txn):
try:
port = idlutils.row_by_value(self.api.idl, 'Logical_Switch_Port',
'name', self.lswitch_port)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Switch Port %s does not "
"exist") % self.lswitch_port
raise RuntimeError(msg)
options = {'router-port': self.lrouter_port}
if self.is_gw_port:
options[ovn_const.OVN_GATEWAY_NAT_ADDRESSES_KEY] = 'router'
setattr(port, 'options', options)
setattr(port, 'type', 'router')
setattr(port, 'addresses', self.lsp_address)
class AddStaticRouteCommand(command.BaseCommand):
def __init__(self, api, lrouter, **columns):
super(AddStaticRouteCommand, self).__init__(api)
self.lrouter = lrouter
self.columns = columns
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
row = txn.insert(self.api._tables['Logical_Router_Static_Route'])
for col, val in self.columns.items():
setattr(row, col, val)
_addvalue_to_list(lrouter, 'static_routes', row.uuid)
class DelStaticRouteCommand(command.BaseCommand):
def __init__(self, api, lrouter, ip_prefix, nexthop, if_exists):
super(DelStaticRouteCommand, self).__init__(api)
self.lrouter = lrouter
self.ip_prefix = ip_prefix
self.nexthop = nexthop
self.if_exists = if_exists
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
static_routes = getattr(lrouter, 'static_routes', [])
for route in static_routes:
ip_prefix = getattr(route, 'ip_prefix', '')
nexthop = getattr(route, 'nexthop', '')
if self.ip_prefix == ip_prefix and self.nexthop == nexthop:
_delvalue_from_list(lrouter, 'static_routes', route)
route.delete()
break
class AddAddrSetCommand(command.BaseCommand):
def __init__(self, api, name, may_exist, **columns):
super(AddAddrSetCommand, self).__init__(api)
self.name = name
self.columns = columns
self.may_exist = may_exist
def run_idl(self, txn):
if self.may_exist:
addrset = idlutils.row_by_value(self.api.idl, 'Address_Set',
'name', self.name, None)
if addrset:
return
row = txn.insert(self.api._tables['Address_Set'])
row.name = self.name
for col, val in self.columns.items():
setattr(row, col, val)
class DelAddrSetCommand(command.BaseCommand):
def __init__(self, api, name, if_exists):
super(DelAddrSetCommand, self).__init__(api)
self.name = name
self.if_exists = if_exists
def run_idl(self, txn):
try:
addrset = idlutils.row_by_value(self.api.idl, 'Address_Set',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Address set %s does not exist. "
"Can't delete.") % self.name
raise RuntimeError(msg)
self.api._tables['Address_Set'].rows[addrset.uuid].delete()
class UpdateAddrSetCommand(command.BaseCommand):
def __init__(self, api, name, addrs_add, addrs_remove, if_exists):
super(UpdateAddrSetCommand, self).__init__(api)
self.name = name
self.addrs_add = addrs_add
self.addrs_remove = addrs_remove
self.if_exists = if_exists
def run_idl(self, txn):
try:
addrset = idlutils.row_by_value(self.api.idl, 'Address_Set',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Address set %s does not exist. "
"Can't update addresses") % self.name
raise RuntimeError(msg)
_updatevalues_in_list(
addrset, 'addresses',
new_values=self.addrs_add,
old_values=self.addrs_remove)
class UpdateAddrSetExtIdsCommand(command.BaseCommand):
def __init__(self, api, name, external_ids, if_exists):
super(UpdateAddrSetExtIdsCommand, self).__init__(api)
self.name = name
self.external_ids = external_ids
self.if_exists = if_exists
def run_idl(self, txn):
try:
addrset = idlutils.row_by_value(self.api.idl, 'Address_Set',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Address set %s does not exist. "
"Can't update external IDs") % self.name
raise RuntimeError(msg)
addrset.verify('external_ids')
addrset_external_ids = getattr(addrset, 'external_ids', {})
for ext_id_key, ext_id_value in self.external_ids.items():
addrset_external_ids[ext_id_key] = ext_id_value
addrset.external_ids = addrset_external_ids
class UpdateChassisExtIdsCommand(command.BaseCommand):
def __init__(self, api, name, external_ids, if_exists):
super(UpdateChassisExtIdsCommand, self).__init__(api)
self.name = name
self.external_ids = external_ids
self.if_exists = if_exists
def run_idl(self, txn):
try:
chassis = idlutils.row_by_value(self.api.idl, 'Chassis',
'name', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Chassis %s does not exist. "
"Can't update external IDs") % self.name
raise RuntimeError(msg)
chassis.verify('external_ids')
chassis_external_ids = getattr(chassis, 'external_ids', {})
for ext_id_key, ext_id_value in self.external_ids.items():
chassis_external_ids[ext_id_key] = ext_id_value
chassis.external_ids = chassis_external_ids
class UpdatePortBindingExtIdsCommand(command.BaseCommand):
def __init__(self, api, name, external_ids, if_exists):
super(UpdatePortBindingExtIdsCommand, self).__init__(api)
self.name = name
self.external_ids = external_ids
self.if_exists = if_exists
def run_idl(self, txn):
try:
port = idlutils.row_by_value(self.api.idl, 'Port_Binding',
'logical_port', self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Port %s does not exist. "
"Can't update external IDs") % self.name
raise RuntimeError(msg)
port.verify('external_ids')
port_external_ids = getattr(port, 'external_ids', {})
for ext_id_key, ext_id_value in self.external_ids.items():
port_external_ids[ext_id_key] = ext_id_value
port.external_ids = port_external_ids
class AddDHCPOptionsCommand(command.BaseCommand):
def __init__(self, api, subnet_id, port_id=None, may_exist=True,
**columns):
super(AddDHCPOptionsCommand, self).__init__(api)
self.columns = columns
self.may_exist = may_exist
self.subnet_id = subnet_id
self.port_id = port_id
self.new_insert = False
def _get_dhcp_options_row(self):
for row in self.api._tables['DHCP_Options'].rows.values():
external_ids = getattr(row, 'external_ids', {})
port_id = external_ids.get('port_id')
if self.subnet_id == external_ids.get('subnet_id'):
if self.port_id == port_id:
return row
def run_idl(self, txn):
row = None
if self.may_exist:
row = self._get_dhcp_options_row()
if not row:
row = txn.insert(self.api._tables['DHCP_Options'])
self.new_insert = True
for col, val in self.columns.items():
setattr(row, col, val)
self.result = row.uuid
def post_commit(self, txn):
# Update the result with inserted uuid for new inserted row, or the
# uuid get in run_idl should be real uuid already.
if self.new_insert:
self.result = txn.get_insert_uuid(self.result)
class DelDHCPOptionsCommand(command.BaseCommand):
def __init__(self, api, row_uuid, if_exists=True):
super(DelDHCPOptionsCommand, self).__init__(api)
self.if_exists = if_exists
self.row_uuid = row_uuid
def run_idl(self, txn):
if self.row_uuid not in self.api._tables['DHCP_Options'].rows:
if self.if_exists:
return
msg = _("DHCP Options row %s does not exist") % self.row_uuid
raise RuntimeError(msg)
self.api._tables['DHCP_Options'].rows[self.row_uuid].delete()
class AddNATRuleInLRouterCommand(command.BaseCommand):
# TODO(chandrav): Add unit tests, bug #1638715.
def __init__(self, api, lrouter, **columns):
super(AddNATRuleInLRouterCommand, self).__init__(api)
self.lrouter = lrouter
self.columns = columns
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
row = txn.insert(self.api._tables['NAT'])
for col, val in self.columns.items():
setattr(row, col, val)
# TODO(chandrav): convert this to ovs transaction mutate
lrouter.verify('nat')
nat = getattr(lrouter, 'nat', [])
nat.append(row.uuid)
setattr(lrouter, 'nat', nat)
class DeleteNATRuleInLRouterCommand(command.BaseCommand):
# TODO(chandrav): Add unit tests, bug #1638715.
def __init__(self, api, lrouter, type, logical_ip, external_ip,
if_exists):
super(DeleteNATRuleInLRouterCommand, self).__init__(api)
self.lrouter = lrouter
self.type = type
self.logical_ip = logical_ip
self.external_ip = external_ip
self.if_exists = if_exists
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
lrouter.verify('nat')
# TODO(chandrav): convert this to ovs transaction mutate
nats = getattr(lrouter, 'nat', [])
for nat in nats:
type = getattr(nat, 'type', '')
external_ip = getattr(nat, 'external_ip', '')
logical_ip = getattr(nat, 'logical_ip', '')
if (self.type == type and
self.external_ip == external_ip and
self.logical_ip == logical_ip):
nats.remove(nat)
nat.delete()
break
setattr(lrouter, 'nat', nats)
class SetNATRuleInLRouterCommand(command.BaseCommand):
def __init__(self, api, lrouter, nat_rule_uuid, **columns):
super(SetNATRuleInLRouterCommand, self).__init__(api)
self.lrouter = lrouter
self.nat_rule_uuid = nat_rule_uuid
self.columns = columns
def run_idl(self, txn):
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
lrouter.verify('nat')
nat_rules = getattr(lrouter, 'nat', [])
for nat_rule in nat_rules:
if nat_rule.uuid == self.nat_rule_uuid:
for col, val in self.columns.items():
setattr(nat_rule, col, val)
break
class CheckRevisionNumberCommand(command.BaseCommand):
def __init__(self, api, name, resource, resource_type, if_exists):
super(CheckRevisionNumberCommand, self).__init__(api)
self.name = name
self.resource = resource
self.resource_type = resource_type
self.if_exists = if_exists
def _get_floatingip(self):
# TODO(lucasagomes): We can't use self.api.lookup() because that
# method does not introspect map type columns. We could either:
# 1. Enhance it to look into maps or, 2. Add a new ``name`` column
# to the NAT table so that we can use lookup() just like we do
# for other resources
for nat in self.api._tables['NAT'].rows.values():
if nat.type != 'dnat_and_snat':
continue
ext_ids = getattr(nat, 'external_ids', {})
if ext_ids.get(ovn_const.OVN_FIP_EXT_ID_KEY) == self.name:
return nat
raise idlutils.RowNotFound(
table='NAT', col='external_ids', match=self.name)
def _get_subnet(self):
for dhcp in self.api._tables['DHCP_Options'].rows.values():
ext_ids = getattr(dhcp, 'external_ids', {})
# Ignore ports DHCP Options
if ext_ids.get('port_id'):
continue
if ext_ids.get('subnet_id') == self.name:
return dhcp
raise idlutils.RowNotFound(
table='DHCP_Options', col='external_ids', match=self.name)
def run_idl(self, txn):
try:
ovn_table = RESOURCE_TYPE_MAP[self.resource_type]
# TODO(lucasagomes): After OVS 2.8.2 is released all tables should
# have the external_ids column. We can remove this conditional
# here by then.
if not self.api.is_col_present(ovn_table, 'external_ids'):
return
ovn_resource = None
if self.resource_type == ovn_const.TYPE_FLOATINGIPS:
ovn_resource = self._get_floatingip()
elif self.resource_type == ovn_const.TYPE_SUBNETS:
ovn_resource = self._get_subnet()
else:
ovn_resource = self.api.lookup(ovn_table, self.name)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = (_('Failed to check the revision number for %s: Resource '
'does not exist') % self.name)
raise RuntimeError(msg)
external_ids = getattr(ovn_resource, 'external_ids', {})
ovn_revision = int(external_ids.get(
ovn_const.OVN_REV_NUM_EXT_ID_KEY, -1))
neutron_revision = utils.get_revision_number(self.resource,
self.resource_type)
if ovn_revision > neutron_revision:
raise ovn_exc.RevisionConflict(
resource_id=self.name, resource_type=self.resource_type)
ovn_resource.verify('external_ids')
ovn_resource.setkey('external_ids', ovn_const.OVN_REV_NUM_EXT_ID_KEY,
str(neutron_revision))
def post_commit(self, txn):
self.result = ovn_const.TXN_COMMITTED
class DeleteLRouterExtGwCommand(command.BaseCommand):
def __init__(self, api, lrouter, if_exists):
super(DeleteLRouterExtGwCommand, self).__init__(api)
self.lrouter = lrouter
self.if_exists = if_exists
def run_idl(self, txn):
# TODO(lucasagomes): Remove this check after OVS 2.8.2 is tagged
# (prior to that, the external_ids column didn't exist in this
# table).
if not self.api.is_col_present('Logical_Router_Static_Route',
'external_ids'):
return
try:
lrouter = idlutils.row_by_value(self.api.idl, 'Logical_Router',
'name', self.lrouter)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = _("Logical Router %s does not exist") % self.lrouter
raise RuntimeError(msg)
lrouter.verify('static_routes')
static_routes = getattr(lrouter, 'static_routes', [])
for route in static_routes:
external_ids = getattr(route, 'external_ids', {})
if ovn_const.OVN_ROUTER_IS_EXT_GW in external_ids:
_delvalue_from_list(lrouter, 'static_routes', route)
route.delete()
break
lrouter.verify('nat')
nats = getattr(lrouter, 'nat', [])
for nat in nats:
if nat.type != 'snat':
continue
_delvalue_from_list(lrouter, 'nat', nat)
nat.delete()
lrouter_ext_ids = getattr(lrouter, 'external_ids', {})
gw_port_id = lrouter_ext_ids.get(ovn_const.OVN_GW_PORT_EXT_ID_KEY)
if not gw_port_id:
return
try:
lrouter_port = idlutils.row_by_value(
self.api.idl, 'Logical_Router_Port', 'name',
utils.ovn_lrouter_port_name(gw_port_id))
except idlutils.RowNotFound:
return
_delvalue_from_list(lrouter, 'ports', lrouter_port)
class SetLSwitchPortToVirtualTypeCommand(command.BaseCommand):
def __init__(self, api, lport, vip, parent, if_exists):
super(SetLSwitchPortToVirtualTypeCommand, self).__init__(api)
self.lport = lport
self.vip = vip
self.parent = parent
self.if_exists = if_exists
def run_idl(self, txn):
try:
lsp = idlutils.row_by_value(self.api.idl, 'Logical_Switch_Port',
'name', self.lport)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = "Logical Switch Port %s does not exist" % self.lport
raise RuntimeError(msg)
options = lsp.options
options[ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY] = self.vip
virtual_parents = options.get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY, set())
if virtual_parents:
virtual_parents = set(virtual_parents.split(','))
virtual_parents.add(self.parent)
options[ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY] = ','.join(
virtual_parents)
setattr(lsp, 'options', options)
setattr(lsp, 'type', ovn_const.LSP_TYPE_VIRTUAL)
class UnsetLSwitchPortToVirtualTypeCommand(command.BaseCommand):
def __init__(self, api, lport, parent, if_exists):
super(UnsetLSwitchPortToVirtualTypeCommand, self).__init__(api)
self.lport = lport
self.parent = parent
self.if_exists = if_exists
def run_idl(self, txn):
try:
lsp = idlutils.row_by_value(self.api.idl, 'Logical_Switch_Port',
'name', self.lport)
except idlutils.RowNotFound:
if self.if_exists:
return
msg = "Logical Switch Port %s does not exist" % self.lport
raise RuntimeError(msg)
options = lsp.options
virtual_parents = options.get(
ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY, set())
if virtual_parents:
virtual_parents = set(virtual_parents.split(','))
try:
virtual_parents.remove(self.parent)
except KeyError:
pass
# If virtual-parents is now empty, change the type and remove the
# virtual-parents and virtual-ip options
if not virtual_parents:
options.pop(ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY, None)
options.pop(ovn_const.LSP_OPTIONS_VIRTUAL_IP_KEY, None)
setattr(lsp, 'type', '')
else:
options[ovn_const.LSP_OPTIONS_VIRTUAL_PARENTS_KEY] = ','.join(
virtual_parents)
setattr(lsp, 'options', options)

View File

@ -0,0 +1,38 @@
# Copyright 2019 Red Hat, Inc.
#
# 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 worker
from neutron.common import config
class MaintenanceWorker(worker.BaseWorker):
def start(self):
super(MaintenanceWorker, self).start()
# NOTE(twilson) The super class will trigger the post_fork_initialize
# in the driver, which starts the connection/IDL notify loop which
# keeps the process from exiting
def stop(self):
"""Stop service."""
super(MaintenanceWorker, self).stop()
def wait(self):
"""Wait for service to complete."""
super(MaintenanceWorker, self).wait()
@staticmethod
def reset():
config.reset_service()

View File

@ -0,0 +1,752 @@
#
# 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 copy
import mock
from neutron_lib.api.definitions import l3
from oslo_utils import uuidutils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils as ovn_utils
class FakeOvsdbNbOvnIdl(object):
def __init__(self, **kwargs):
self.lswitch_table = FakeOvsdbTable.create_one_ovsdb_table()
self.lsp_table = FakeOvsdbTable.create_one_ovsdb_table()
self.lrouter_table = FakeOvsdbTable.create_one_ovsdb_table()
self.lrouter_static_route_table = \
FakeOvsdbTable.create_one_ovsdb_table()
self.lrp_table = FakeOvsdbTable.create_one_ovsdb_table()
self.addrset_table = FakeOvsdbTable.create_one_ovsdb_table()
self.acl_table = FakeOvsdbTable.create_one_ovsdb_table()
self.dhcp_options_table = FakeOvsdbTable.create_one_ovsdb_table()
self.nat_table = FakeOvsdbTable.create_one_ovsdb_table()
self.port_group_table = FakeOvsdbTable.create_one_ovsdb_table()
self._tables = {}
self._tables['Logical_Switch'] = self.lswitch_table
self._tables['Logical_Switch_Port'] = self.lsp_table
self._tables['Logical_Router'] = self.lrouter_table
self._tables['Logical_Router_Port'] = self.lrp_table
self._tables['Logical_Router_Static_Route'] = \
self.lrouter_static_route_table
self._tables['ACL'] = self.acl_table
self._tables['Address_Set'] = self.addrset_table
self._tables['DHCP_Options'] = self.dhcp_options_table
self._tables['NAT'] = self.nat_table
self._tables['Port_Group'] = self.port_group_table
self.transaction = mock.MagicMock()
self.ls_add = mock.Mock()
self.set_lswitch_ext_ids = mock.Mock()
self.ls_del = mock.Mock()
self.create_lswitch_port = mock.Mock()
self.set_lswitch_port = mock.Mock()
self.delete_lswitch_port = mock.Mock()
self.get_acls_for_lswitches = mock.Mock()
self.create_lrouter = mock.Mock()
self.lrp_del = mock.Mock()
self.update_lrouter = mock.Mock()
self.delete_lrouter = mock.Mock()
self.add_lrouter_port = mock.Mock()
self.update_lrouter_port = mock.Mock()
self.delete_lrouter_port = mock.Mock()
self.set_lrouter_port_in_lswitch_port = mock.Mock()
self.add_acl = mock.Mock()
self.delete_acl = mock.Mock()
self.update_acls = mock.Mock()
self.idl = mock.Mock()
self.add_static_route = mock.Mock()
self.delete_static_route = mock.Mock()
self.create_address_set = mock.Mock()
self.update_address_set_ext_ids = mock.Mock()
self.delete_address_set = mock.Mock()
self.update_address_set = mock.Mock()
self.get_all_chassis_gateway_bindings = mock.Mock()
self.get_gateway_chassis_binding = mock.Mock()
self.get_unhosted_gateways = mock.Mock()
self.add_dhcp_options = mock.Mock()
self.delete_dhcp_options = mock.Mock()
self.get_subnet_dhcp_options = mock.Mock()
self.get_subnet_dhcp_options.return_value = {
'subnet': None, 'ports': []}
self.get_subnets_dhcp_options = mock.Mock()
self.get_subnets_dhcp_options.return_value = []
self.get_all_dhcp_options = mock.Mock()
self.get_router_port_options = mock.MagicMock()
self.get_router_port_options.return_value = {}
self.add_nat_rule_in_lrouter = mock.Mock()
self.delete_nat_rule_in_lrouter = mock.Mock()
self.get_lrouter_nat_rules = mock.Mock()
self.get_lrouter_nat_rules.return_value = []
self.set_nat_rule_in_lrouter = mock.Mock()
self.check_for_row_by_value_and_retry = mock.Mock()
self.get_parent_port = mock.Mock()
self.get_parent_port.return_value = []
self.dns_add = mock.Mock()
self.get_lswitch = mock.Mock()
fake_ovs_row = FakeOvsdbRow.create_one_ovsdb_row()
self.get_lswitch.return_value = fake_ovs_row
self.get_lswitch_port = mock.Mock()
self.get_lswitch_port.return_value = fake_ovs_row
self.get_ls_and_dns_record = mock.Mock()
self.get_ls_and_dns_record.return_value = (fake_ovs_row, None)
self.ls_set_dns_records = mock.Mock()
self.get_floatingip = mock.Mock()
self.get_floatingip.return_value = None
self.check_revision_number = mock.Mock()
self.lookup = mock.MagicMock()
# TODO(lucasagomes): The get_floatingip_by_ips() method is part
# of a backwards compatibility layer for the Pike -> Queens release,
# remove it in the Rocky release.
self.get_floatingip_by_ips = mock.Mock()
self.get_floatingip_by_ips.return_value = None
self.is_col_present = mock.Mock()
self.is_col_present.return_value = False
self.get_lrouter = mock.Mock()
self.get_lrouter.return_value = None
self.delete_lrouter_ext_gw = mock.Mock()
self.delete_lrouter_ext_gw.return_value = None
self.is_port_groups_supported = mock.Mock()
# TODO(lucasagomes): Flip this return value to True at some point,
# port groups should be the default method used by networking-ovn
self.is_port_groups_supported.return_value = False
self.get_address_set = mock.Mock()
self.get_address_set.return_value = None
self.pg_acl_add = mock.Mock()
self.pg_acl_del = mock.Mock()
self.pg_del = mock.Mock()
self.pg_add = mock.Mock()
self.get_port_group = mock.Mock()
self.pg_add_ports = mock.Mock()
self.pg_del_ports = mock.Mock()
self.lsp_get_up = mock.Mock()
self.nb_global = mock.Mock()
self.db_list_rows = mock.Mock()
self.lsp_list = mock.MagicMock()
self.db_find = mock.Mock()
self.db_set = mock.Mock()
self.db_clear = mock.Mock()
self.db_remove = mock.Mock()
self.set_lswitch_port_to_virtual_type = mock.Mock()
self.unset_lswitch_port_to_virtual_type = mock.Mock()
self.ls_get = mock.Mock()
class FakeOvsdbSbOvnIdl(object):
def __init__(self, **kwargs):
self.chassis_exists = mock.Mock()
self.chassis_exists.return_value = True
self.get_chassis_hostname_and_physnets = mock.Mock()
self.get_chassis_hostname_and_physnets.return_value = {}
self.get_all_chassis = mock.Mock()
self.get_chassis_data_for_ml2_bind_port = mock.Mock()
self.get_chassis_data_for_ml2_bind_port.return_value = \
('fake', '', ['fake-physnet'])
self.get_logical_port_chassis_and_datapath = mock.Mock()
self.get_logical_port_chassis_and_datapath.return_value = \
('fake', 'fake-dp')
self.get_chassis_and_physnets = mock.Mock()
self.get_gateway_chassis_from_cms_options = mock.Mock()
self.is_col_present = mock.Mock()
self.is_col_present.return_value = False
class FakeOvsdbTransaction(object):
def __init__(self, **kwargs):
self.insert = mock.Mock()
class FakePlugin(object):
def __init__(self, **kwargs):
self.get_ports = mock.Mock()
self._get_port_security_group_bindings = mock.Mock()
class FakeResource(dict):
def __init__(self, manager=None, info=None, loaded=False, methods=None):
"""Set attributes and methods for a resource.
:param manager:
The resource manager
:param Dictionary info:
A dictionary with all attributes
:param bool loaded:
True if the resource is loaded in memory
:param Dictionary methods:
A dictionary with all methods
"""
info = info or {}
super(FakeResource, self).__init__(info)
methods = methods or {}
self.__name__ = type(self).__name__
self.manager = manager
self._info = info
self._add_details(info)
self._add_methods(methods)
self._loaded = loaded
# Add a revision number by default
setattr(self, 'revision_number', 1)
@property
def db_obj(self):
return self
def _add_details(self, info):
for (k, v) in info.items():
setattr(self, k, v)
def _add_methods(self, methods):
"""Fake methods with MagicMock objects.
For each <@key, @value> pairs in methods, add an callable MagicMock
object named @key as an attribute, and set the mock's return_value to
@value. When users access the attribute with (), @value will be
returned, which looks like a function call.
"""
for (name, ret) in methods.items():
method = mock.MagicMock(return_value=ret)
setattr(self, name, method)
def __repr__(self):
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
k != 'manager')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
def keys(self):
return self._info.keys()
def info(self):
return self._info
def update(self, info):
super(FakeResource, self).update(info)
self._add_details(info)
class FakeNetwork(object):
"""Fake one or more networks."""
@staticmethod
def create_one_network(attrs=None):
"""Create a fake network.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the network
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
network_attrs = {
'id': 'network-id-' + fake_uuid,
'name': 'network-name-' + fake_uuid,
'status': 'ACTIVE',
'tenant_id': 'project-id-' + fake_uuid,
'admin_state_up': True,
'shared': False,
'subnets': [],
'provider:network_type': 'geneve',
'provider:physical_network': None,
'provider:segmentation_id': 10,
'router:external': False,
'availability_zones': [],
'availability_zone_hints': [],
'is_default': False,
}
# Overwrite default attributes.
network_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(network_attrs),
loaded=True)
class FakeNetworkContext(object):
def __init__(self, network, segments):
self.fake_network = network
self.fake_segments = segments
self._plugin_context = mock.MagicMock()
@property
def current(self):
return self.fake_network
@property
def original(self):
return None
@property
def network_segments(self):
return self.fake_segments
class FakeSubnetContext(object):
def __init__(self, subnet, original_subnet=None, network=None):
self.fake_subnet = subnet
self.fake_original_subnet = original_subnet
self.fake_network = FakeNetworkContext(network, None)
@property
def current(self):
return self.fake_subnet
@property
def original(self):
return self.fake_original_subnet
@property
def network(self):
return self.fake_network
class FakeOvsdbRow(FakeResource):
"""Fake one or more OVSDB rows."""
@staticmethod
def create_one_ovsdb_row(attrs=None, methods=None):
"""Create a fake OVSDB row.
:param Dictionary attrs:
A dictionary with all attributes
:param Dictionary methods:
A dictionary with all methods
:return:
A FakeResource object faking the OVSDB row
"""
attrs = attrs or {}
methods = methods or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
ovsdb_row_attrs = {
'uuid': fake_uuid,
'name': 'name-' + fake_uuid,
'external_ids': {},
}
# Set default methods.
ovsdb_row_methods = {
'addvalue': None,
'delete': None,
'delvalue': None,
'verify': None,
'setkey': None,
}
# Overwrite default attributes and methods.
ovsdb_row_attrs.update(attrs)
ovsdb_row_methods.update(methods)
return FakeResource(info=copy.deepcopy(ovsdb_row_attrs),
loaded=True,
methods=copy.deepcopy(ovsdb_row_methods))
class FakeOvsdbTable(FakeResource):
"""Fake one or more OVSDB tables."""
@staticmethod
def create_one_ovsdb_table(attrs=None):
"""Create a fake OVSDB table.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the OVSDB table
"""
attrs = attrs or {}
# Set default attributes.
ovsdb_table_attrs = {
'rows': {},
'columns': {},
}
# Overwrite default attributes.
ovsdb_table_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(ovsdb_table_attrs),
loaded=True)
class FakePort(object):
"""Fake one or more ports."""
@staticmethod
def create_one_port(attrs=None):
"""Create a fake port.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the port
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
port_attrs = {
'admin_state_up': True,
'allowed_address_pairs': [{}],
'binding:host_id': 'binding-host-id-' + fake_uuid,
'binding:profile': {},
'binding:vif_details': {},
'binding:vif_type': 'ovs',
'binding:vnic_type': 'normal',
'device_id': 'device-id-' + fake_uuid,
'device_owner': 'compute:nova',
'dns_assignment': [{}],
'dns_name': 'dns-name-' + fake_uuid,
'extra_dhcp_opts': [{}],
'fixed_ips': [{'subnet_id': 'subnet-id-' + fake_uuid,
'ip_address': '10.10.10.20'}],
'id': 'port-id-' + fake_uuid,
'mac_address': 'fa:16:3e:a9:4e:72',
'name': 'port-name-' + fake_uuid,
'network_id': 'network-id-' + fake_uuid,
'port_security_enabled': True,
'security_groups': [],
'status': 'ACTIVE',
'tenant_id': 'project-id-' + fake_uuid,
}
# Overwrite default attributes.
port_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(port_attrs),
loaded=True)
class FakePortContext(object):
def __init__(self, port, host, segments_to_bind):
self.fake_port = port
self.fake_host = host
self.fake_segments_to_bind = segments_to_bind
self.set_binding = mock.Mock()
@property
def current(self):
return self.fake_port
@property
def host(self):
return self.fake_host
@property
def segments_to_bind(self):
return self.fake_segments_to_bind
class FakeSecurityGroup(object):
"""Fake one or more security groups."""
@staticmethod
def create_one_security_group(attrs=None):
"""Create a fake security group.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the security group
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
security_group_attrs = {
'id': 'security-group-id-' + fake_uuid,
'name': 'security-group-name-' + fake_uuid,
'description': 'security-group-description-' + fake_uuid,
'tenant_id': 'project-id-' + fake_uuid,
'security_group_rules': [],
}
# Overwrite default attributes.
security_group_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(security_group_attrs),
loaded=True)
class FakeSecurityGroupRule(object):
"""Fake one or more security group rules."""
@staticmethod
def create_one_security_group_rule(attrs=None):
"""Create a fake security group rule.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the security group rule
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
security_group_rule_attrs = {
'direction': 'ingress',
'ethertype': 'IPv4',
'id': 'security-group-rule-id-' + fake_uuid,
'port_range_max': 22,
'port_range_min': 22,
'protocol': 'tcp',
'remote_group_id': None,
'remote_ip_prefix': '0.0.0.0/0',
'security_group_id': 'security-group-id-' + fake_uuid,
'tenant_id': 'project-id-' + fake_uuid,
}
# Overwrite default attributes.
security_group_rule_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(security_group_rule_attrs),
loaded=True)
class FakeSegment(object):
"""Fake one or more segments."""
@staticmethod
def create_one_segment(attrs=None):
"""Create a fake segment.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the segment
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
segment_attrs = {
'id': 'segment-id-' + fake_uuid,
'network_type': 'geneve',
'physical_network': None,
'segmentation_id': 10,
}
# Overwrite default attributes.
segment_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(segment_attrs),
loaded=True)
class FakeSubnet(object):
"""Fake one or more subnets."""
@staticmethod
def create_one_subnet(attrs=None):
"""Create a fake subnet.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the subnet
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
subnet_attrs = {
'id': 'subnet-id-' + fake_uuid,
'name': 'subnet-name-' + fake_uuid,
'network_id': 'network-id-' + fake_uuid,
'cidr': '10.10.10.0/24',
'tenant_id': 'project-id-' + fake_uuid,
'enable_dhcp': True,
'dns_nameservers': [],
'allocation_pools': [],
'host_routes': [],
'ip_version': 4,
'gateway_ip': '10.10.10.1',
'ipv6_address_mode': 'None',
'ipv6_ra_mode': 'None',
'subnetpool_id': None,
}
# Overwrite default attributes.
subnet_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(subnet_attrs),
loaded=True)
class FakeFloatingIp(object):
"""Fake one or more floating ips."""
@staticmethod
def create_one_fip(attrs=None):
"""Create a fake floating ip.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the floating ip
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
fip_attrs = {
'id': 'fip-id-' + fake_uuid,
'tenant_id': '',
'fixed_ip_address': '10.0.0.10',
'floating_ip_address': '172.21.0.100',
'router_id': 'router-id',
'port_id': 'port_id',
'fixed_port_id': 'port_id',
'floating_port_id': 'fip-port-id',
'status': 'Active',
'floating_network_id': 'fip-net-id',
'dns': '',
'dns_domain': '',
'dns_name': '',
'project_id': '',
}
# Overwrite default attributes.
fip_attrs.update(attrs)
return FakeResource(info=copy.deepcopy(fip_attrs),
loaded=True)
class FakeOVNPort(object):
"""Fake one or more ports."""
@staticmethod
def create_one_port(attrs=None):
"""Create a fake ovn port.
:param Dictionary attrs:
A dictionary with all attributes
:return:
A FakeResource object faking the port
"""
attrs = attrs or {}
# Set default attributes.
fake_uuid = uuidutils.generate_uuid()
port_attrs = {
'addresses': [],
'dhcpv4_options': '',
'dhcpv6_options': [],
'enabled': True,
'external_ids': {},
'name': fake_uuid,
'options': {},
'parent_name': [],
'port_security': [],
'tag': [],
'tag_request': [],
'type': '',
'up': False,
}
# Overwrite default attributes.
port_attrs.update(attrs)
return type('Logical_Switch_Port', (object, ), port_attrs)
@staticmethod
def from_neutron_port(port):
"""Create a fake ovn port based on a neutron port."""
external_ids = {
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
ovn_utils.ovn_name(port['network_id']),
ovn_const.OVN_SG_IDS_EXT_ID_KEY:
' '.join(port['security_groups']),
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
port.get('device_owner', '')}
addresses = [port['mac_address'], ]
addresses += [x['ip_address'] for x in port.get('fixed_ips', [])]
port_security = (
addresses + [x['ip_address'] for x in
port.get('allowed_address_pairs', [])])
return FakeOVNPort.create_one_port(
{'external_ids': external_ids, 'addresses': addresses,
'port_security': port_security})
FakeStaticRoute = collections.namedtuple(
'Static_Routes', ['ip_prefix', 'nexthop', 'external_ids'])
class FakeOVNRouter(object):
@staticmethod
def create_one_router(attrs=None):
router_attrs = {
'enabled': False,
'external_ids': {},
'load_balancer': [],
'name': '',
'nat': [],
'options': {},
'ports': [],
'static_routes': [],
}
# Overwrite default attributes.
router_attrs.update(attrs)
return type('Logical_Router', (object, ), router_attrs)
@staticmethod
def from_neutron_router(router):
def _get_subnet_id(gw_info):
subnet_id = ''
ext_ips = gw_info.get('external_fixed_ips', [])
if ext_ips:
subnet_id = ext_ips[0]['subnet_id']
return subnet_id
external_ids = {
ovn_const.OVN_GW_PORT_EXT_ID_KEY: router.get('gw_port_id') or '',
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
router.get('name', 'no_router_name')}
# Get the routes
routes = []
for r in router.get('routes', []):
routes.append(FakeStaticRoute(ip_prefix=r['destination'],
nexthop=r['nexthop'],
external_ids={}))
gw_info = router.get(l3.EXTERNAL_GW_INFO)
if gw_info:
external_ids = {
ovn_const.OVN_ROUTER_IS_EXT_GW: 'true',
ovn_const.OVN_SUBNET_EXT_ID_KEY: _get_subnet_id(gw_info)}
routes.append(FakeStaticRoute(
ip_prefix='0.0.0.0/0', nexthop='',
external_ids=external_ids))
return FakeOVNRouter.create_one_router(
{'external_ids': external_ids,
'enabled': router.get('admin_state_up') or False,
'name': ovn_utils.ovn_name(router['id']),
'static_routes': routes})

File diff suppressed because it is too large Load Diff