[OVN] Use new distributed device_owner for OVN distributed services

OVN distributed services like Metadata and DHCP uses now
DEVICE_OWNER_DHCP device_owner which isn't distributed by its nature.

To fully use benefits of OVN Distributed ports (localports) [1]
and to not overlap with Neutron logic created for not-distributed
ports we should use new device_owner.

In this change we need also to bump minimum required
neutron-lib to 2.4.0.

[1] https://www.ovn.org/support/dist-docs/ovn-nb.5.txt
Change-Id: I0a69f1bddaa7030c7287216e62ec1ac6dd381475
This commit is contained in:
Maciej Józefczyk 2020-06-01 10:17:02 +00:00
parent da45bbbff4
commit b2b40b6a8c
11 changed files with 116 additions and 18 deletions

View File

@ -51,7 +51,7 @@ msgpack-python==0.4.0
munch==2.1.0 munch==2.1.0
netaddr==0.7.18 netaddr==0.7.18
netifaces==0.10.4 netifaces==0.10.4
neutron-lib==2.3.0 neutron-lib==2.4.0
openstacksdk==0.31.2 openstacksdk==0.31.2
os-client-config==1.28.0 os-client-config==1.28.0
os-ken==0.3.0 os-ken==0.3.0

View File

@ -75,4 +75,5 @@ IDPOOL_SELECT_SIZE = 100
# finds out that all existing IP Allocations are associated with ports # finds out that all existing IP Allocations are associated with ports
# with these owners, it will allow subnet deletion to proceed with the # with these owners, it will allow subnet deletion to proceed with the
# IP allocations being cleaned up by cascade. # IP allocations being cleaned up by cascade.
AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP] AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP,
constants.DEVICE_OWNER_DISTRIBUTED]

View File

@ -1 +1 @@
dfe425060830 fd6107509ccd

View File

@ -0,0 +1,52 @@
# Copyright 2020 OpenStack Foundation
#
# 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 alembic import op
from neutron_lib import constants
import sqlalchemy as sa
"""ovn_distributed_device_owner
Revision ID: fd6107509ccd
Revises: 5c85685d616d
Create Date: 2020-06-01 11:16:58.312355
"""
# revision identifiers, used by Alembic.
revision = 'fd6107509ccd'
down_revision = 'dfe425060830'
PORTS_TABLE = 'ports'
OVN_METADATA_PREFIX = 'ovnmeta'
def upgrade():
update_device_owner_ovn_distributed_ports()
def update_device_owner_ovn_distributed_ports():
ports = sa.Table(PORTS_TABLE,
sa.MetaData(),
sa.Column('device_owner', sa.String(255)),
sa.Column('device_id', sa.String(255)))
session = sa.orm.Session(bind=op.get_bind())
with session.begin(subtransactions=True):
session.execute(ports.update().values(
device_owner=constants.DEVICE_OWNER_DISTRIBUTED).where(
ports.c.device_owner == constants.DEVICE_OWNER_DHCP).where(
ports.c.device_id.like('{}%'.format(OVN_METADATA_PREFIX))))
session.commit()

View File

@ -285,7 +285,8 @@ class AddressRequestFactory(object):
elif ip_dict.get('eui64_address'): elif ip_dict.get('eui64_address'):
return AutomaticAddressRequest(prefix=ip_dict['subnet_cidr'], return AutomaticAddressRequest(prefix=ip_dict['subnet_cidr'],
mac=ip_dict['mac']) mac=ip_dict['mac'])
elif port['device_owner'] == constants.DEVICE_OWNER_DHCP: elif (port['device_owner'] == constants.DEVICE_OWNER_DHCP or
port['device_owner'] == constants.DEVICE_OWNER_DISTRIBUTED):
# preserve previous behavior of DHCP ports choosing start of pool # preserve previous behavior of DHCP ports choosing start of pool
return PreferNextAddressRequest() return PreferNextAddressRequest()
else: else:

View File

@ -259,7 +259,11 @@ class OVNClient(object):
# Only adjust the OVN type if the port is not owned by Neutron # Only adjust the OVN type if the port is not owned by Neutron
# DHCP agents. # DHCP agents.
if (port['device_owner'] == const.DEVICE_OWNER_DHCP and # TODO(mjozefcz): Remove const.DEVICE_OWNER_DHCP
# from get_ports in W-release.
if (port['device_owner'] in [
const.DEVICE_OWNER_DISTRIBUTED,
const.DEVICE_OWNER_DHCP] and
not utils.is_neutron_dhcp_agent_port(port)): not utils.is_neutron_dhcp_agent_port(port)):
port_type = 'localport' port_type = 'localport'
@ -2030,9 +2034,19 @@ class OVNClient(object):
if not ovn_conf.is_ovn_metadata_enabled(): if not ovn_conf.is_ovn_metadata_enabled():
return return
# TODO(mjozefcz): Remove const.DEVICE_OWNER_DHCP
# from get_ports in W-release.
ports = self._plugin.get_ports(context, filters=dict( ports = self._plugin.get_ports(context, filters=dict(
network_id=[network_id], device_owner=[const.DEVICE_OWNER_DHCP])) network_id=[network_id],
device_owner=[
const.DEVICE_OWNER_DHCP,
const.DEVICE_OWNER_DISTRIBUTED]))
# TODO(mjozefcz): Remove this compatibility code in W release.
# First look for const.DEVICE_OWNER_DISTRIBUTED and then for
# const.DEVICE_OWNER_DHCP.
for port in ports:
if port['device_owner'] == const.DEVICE_OWNER_DISTRIBUTED:
return port
# Metadata ports are DHCP ports not belonging to the Neutron # Metadata ports are DHCP ports not belonging to the Neutron
# DHCP agents # DHCP agents
for port in ports: for port in ports:
@ -2054,7 +2068,7 @@ class OVNClient(object):
port = {'port': port = {'port':
{'network_id': network['id'], {'network_id': network['id'],
'tenant_id': network['project_id'], 'tenant_id': network['project_id'],
'device_owner': const.DEVICE_OWNER_DHCP, 'device_owner': const.DEVICE_OWNER_DISTRIBUTED,
'device_id': 'ovnmeta-%s' % network['id']}} 'device_id': 'ovnmeta-%s' % network['id']}}
# TODO(boden): rehome create_port into neutron-lib # TODO(boden): rehome create_port into neutron-lib
p_utils.create_port(self._plugin, context, port) p_utils.create_port(self._plugin, context, port)

View File

@ -742,10 +742,14 @@ class OvnNbSynchronizer(OvnDbSynchronizer):
if not ovn_conf.is_ovn_metadata_enabled(): if not ovn_conf.is_ovn_metadata_enabled():
return return
LOG.debug('OVN sync metadata ports started') LOG.debug('OVN sync metadata ports started')
# TODO(mjozefcz): Remove constants.DEVICE_OWNER_DHCP
# from get_ports in W-release.
for net in self.core_plugin.get_networks(ctx): for net in self.core_plugin.get_networks(ctx):
dhcp_ports = self.core_plugin.get_ports(ctx, filters=dict( dhcp_ports = self.core_plugin.get_ports(ctx, filters=dict(
network_id=[net['id']], network_id=[net['id']],
device_owner=[constants.DEVICE_OWNER_DHCP])) device_owner=[
constants.DEVICE_OWNER_DISTRIBUTED,
constants.DEVICE_OWNER_DHCP]))
for port in dhcp_ports: for port in dhcp_ports:
# Do not touch the Neutron DHCP agents ports # Do not touch the Neutron DHCP agents ports

View File

@ -879,7 +879,7 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
db_metadata_ports_ids = set() db_metadata_ports_ids = set()
db_metadata_ports_nets = set() db_metadata_ports_nets = set()
for port in db_ports['ports']: for port in db_ports['ports']:
if (port['device_owner'] == constants.DEVICE_OWNER_DHCP and if (port['device_owner'] == constants.DEVICE_OWNER_DISTRIBUTED and
port['device_id'].startswith('ovnmeta')): port['device_id'].startswith('ovnmeta')):
db_metadata_ports_ids.add(port['id']) db_metadata_ports_ids.add(port['id'])
db_metadata_ports_nets.add(port['network_id']) db_metadata_ports_nets.add(port['network_id'])
@ -1341,7 +1341,7 @@ class TestOvnNbSync(base.TestOVNFunctionalBase):
db_ports = self._list('ports') db_ports = self._list('ports')
db_metadata_ports = [port for port in db_ports['ports'] if db_metadata_ports = [port for port in db_ports['ports'] if
port['device_owner'] == port['device_owner'] ==
constants.DEVICE_OWNER_DHCP and constants.DEVICE_OWNER_DISTRIBUTED and
port['device_id'].startswith('ovnmeta')] port['device_id'].startswith('ovnmeta')]
lswitches = {} lswitches = {}
ports_to_delete = len(db_metadata_ports) / 2 ports_to_delete = len(db_metadata_ports) / 2

View File

@ -317,6 +317,13 @@ class TestAddressRequestFactory(base.BaseTestCase):
ipam_req.AddressRequestFactory.get_request(None, port, ip), ipam_req.AddressRequestFactory.get_request(None, port, ip),
ipam_req.PreferNextAddressRequest) ipam_req.PreferNextAddressRequest)
def test_prefernext_address_request_on_distributed_port(self):
ip = {}
port = {'device_owner': constants.DEVICE_OWNER_DISTRIBUTED}
self.assertIsInstance(
ipam_req.AddressRequestFactory.get_request(None, port, ip),
ipam_req.PreferNextAddressRequest)
class TestSubnetRequestFactory(IpamSubnetRequestTestCase): class TestSubnetRequestFactory(IpamSubnetRequestTestCase):

View File

@ -2906,16 +2906,21 @@ class TestOVNMechanismDriverMetadataPort(test_plugin.Ml2PluginV2TestCase):
p.start() p.start()
self.addCleanup(p.stop) self.addCleanup(p.stop)
def _create_fake_dhcp_port(self, device_id): def _create_fake_dhcp_port(self, device_id, neutron_port=False):
return {'network_id': 'fake', 'device_owner': const.DEVICE_OWNER_DHCP, port = {'network_id': 'fake',
'device_owner': const.DEVICE_OWNER_DISTRIBUTED,
'device_id': device_id} 'device_id': device_id}
if neutron_port:
port['device_owner'] = const.DEVICE_OWNER_DHCP
return port
@mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_ports') @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_ports')
def test__find_metadata_port(self, mock_get_ports): def test__find_metadata_port(self, mock_get_ports):
ports = [ ports = [
self._create_fake_dhcp_port('dhcp-0'), self._create_fake_dhcp_port('dhcp-0', neutron_port=True),
self._create_fake_dhcp_port('dhcp-1'), self._create_fake_dhcp_port('dhcp-1', neutron_port=True),
self._create_fake_dhcp_port(const.DEVICE_ID_RESERVED_DHCP_PORT), self._create_fake_dhcp_port(const.DEVICE_ID_RESERVED_DHCP_PORT,
neutron_port=True),
self._create_fake_dhcp_port('ovnmeta-0')] self._create_fake_dhcp_port('ovnmeta-0')]
mock_get_ports.return_value = ports mock_get_ports.return_value = ports
@ -2923,6 +2928,20 @@ class TestOVNMechanismDriverMetadataPort(test_plugin.Ml2PluginV2TestCase):
self.ctx, 'fake-net-id') self.ctx, 'fake-net-id')
self.assertEqual('ovnmeta-0', md_port['device_id']) self.assertEqual('ovnmeta-0', md_port['device_id'])
@mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, 'get_ports')
def test__find_metadata_port_compat(self, mock_get_ports):
ports = [
self._create_fake_dhcp_port('dhcp-0', neutron_port=True),
self._create_fake_dhcp_port('dhcp-1', neutron_port=True),
self._create_fake_dhcp_port(const.DEVICE_ID_RESERVED_DHCP_PORT,
neutron_port=True),
self._create_fake_dhcp_port('ovnmeta-0', neutron_port=True)]
mock_get_ports.return_value = ports
md_port = self.mech_driver._ovn_client._find_metadata_port(
self.ctx, 'fake-net-id')
self.assertEqual('ovnmeta-0', md_port['device_id'])
def test_metadata_port_on_network_create(self): def test_metadata_port_on_network_create(self):
"""Check metadata port create. """Check metadata port create.
@ -2958,7 +2977,7 @@ class TestOVNMechanismDriverMetadataPort(test_plugin.Ml2PluginV2TestCase):
# Create a network:dhcp owner port just as how Neutron DHCP # Create a network:dhcp owner port just as how Neutron DHCP
# agent would do. # agent would do.
with self.port(subnet=subnet1, with self.port(subnet=subnet1,
device_owner=const.DEVICE_OWNER_DHCP, device_owner=const.DEVICE_OWNER_DISTRIBUTED,
device_id='dhcpxxxx', device_id='dhcpxxxx',
set_context=True, tenant_id='test'): set_context=True, tenant_id='test'):
with self.subnet(network=net1, cidr='20.0.0.0/24'): with self.subnet(network=net1, cidr='20.0.0.0/24'):

View File

@ -16,7 +16,7 @@ Jinja2>=2.10 # BSD License (3 clause)
keystonemiddleware>=4.17.0 # Apache-2.0 keystonemiddleware>=4.17.0 # Apache-2.0
netaddr>=0.7.18 # BSD netaddr>=0.7.18 # BSD
netifaces>=0.10.4 # MIT netifaces>=0.10.4 # MIT
neutron-lib>=2.3.0 # Apache-2.0 neutron-lib>=2.4.0 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0 python-neutronclient>=6.7.0 # Apache-2.0
tenacity>=4.4.0 # Apache-2.0 tenacity>=4.4.0 # Apache-2.0
SQLAlchemy>=1.2.0 # MIT SQLAlchemy>=1.2.0 # MIT