segment: enable multisegments support for host
This updates the exception with a log message informing that multi-segments is supported by OVS only at that point. This also add fullstack tests that validates multisegs deployment on a physnet. Closes-Bug: #1956435 Partial-Bug: #1764738 Signed-off-by: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@industrialdiscipline.com> Change-Id: I3811a4ca28906dd29100c602de7fa4a3595393ab
This commit is contained in:
parent
7c449f1833
commit
be0996c308
@ -616,3 +616,68 @@ subnets L3 agent when:
|
|||||||
* The "segments" plugin is enabled; this plugin is needed for routed provided
|
* The "segments" plugin is enabled; this plugin is needed for routed provided
|
||||||
networks.
|
networks.
|
||||||
* The network is connected to a segment.
|
* The network is connected to a segment.
|
||||||
|
|
||||||
|
|
||||||
|
Multiple routed provider segments per host
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Starting with Antelope, the support of routed provider networks has
|
||||||
|
been enhanced to handle multiple segments per host. The main
|
||||||
|
consequence will be for an operator to extend the IP pool without
|
||||||
|
creating multiple networks and/or increasing broadcast domain..
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The present support is only available for OVS agent at this point.
|
||||||
|
|
||||||
|
#. On a given provided network, create a second segment. In this
|
||||||
|
example, the second segment uses the ``provider1`` physical network
|
||||||
|
with VLAN ID 2020.
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack network segment create --physical-network provider1 \
|
||||||
|
--network-type vlan --segment 2020 --network multisegment1 segment1-2
|
||||||
|
+------------------+--------------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+------------------+--------------------------------------+
|
||||||
|
| description | None |
|
||||||
|
| headers | |
|
||||||
|
| id | 333b7925-9a89-4489-9992-e164c8cc8764 |
|
||||||
|
| name | segment1-2 |
|
||||||
|
| network_id | 6ab19caa-dda9-4b3d-abc4-5b8f435b98d9 |
|
||||||
|
| network_type | vlan |
|
||||||
|
| physical_network | provider1 |
|
||||||
|
| revision_number | 1 |
|
||||||
|
| segmentation_id | 2020 |
|
||||||
|
| tags | [] |
|
||||||
|
+------------------+--------------------------------------+
|
||||||
|
|
||||||
|
#. Create subnets on the ``segment1-2`` segment. In this example, the IPv4
|
||||||
|
subnet uses 203.0.114.0/24.
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ openstack subnet create \
|
||||||
|
--network multisegment1 --network-segment segment1-2 \
|
||||||
|
--ip-version 4 --subnet-range 203.0.114.0/24 \
|
||||||
|
multisegment1-segment1-2
|
||||||
|
+-------------------+--------------------------------------+
|
||||||
|
| Field | Value |
|
||||||
|
+-------------------+--------------------------------------+
|
||||||
|
| allocation_pools | 203.0.114.2-203.0.114.254 |
|
||||||
|
| cidr | 203.0.114.0/24 |
|
||||||
|
| enable_dhcp | True |
|
||||||
|
| gateway_ip | 203.0.114.1 |
|
||||||
|
| id | c428797a-6f8e-4cb1-b394-c404318a2762 |
|
||||||
|
| ip_version | 4 |
|
||||||
|
| name | multisegment1-segment1-2 |
|
||||||
|
| network_id | 6ab19caa-dda9-4b3d-abc4-5b8f435b98d9 |
|
||||||
|
| revision_number | 1 |
|
||||||
|
| segment_id | 333b7925-9a89-4489-9992-e164c8cc8764 |
|
||||||
|
| tags | [] |
|
||||||
|
+-------------------+--------------------------------------+
|
||||||
|
|
||||||
|
Considering that, for a subnet of the given provider network
|
||||||
|
``provider1`` running out of available IP, Neutron will automatically
|
||||||
|
switch to the subnet ``multisegment1-segment1-2``.
|
||||||
|
@ -358,11 +358,9 @@ class DhcpLocalProcess(DhcpBase, metaclass=abc.ABCMeta):
|
|||||||
self._remove_config_files()
|
self._remove_config_files()
|
||||||
|
|
||||||
def _destroy_namespace_and_port(self):
|
def _destroy_namespace_and_port(self):
|
||||||
segmentation_id = (
|
|
||||||
self.segment.segmentation_id if self.segment else None)
|
|
||||||
try:
|
try:
|
||||||
self.device_manager.destroy(
|
self.device_manager.destroy(
|
||||||
self.network, self.interface_name, segmentation_id)
|
self.network, self.interface_name, self.segment)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
LOG.warning('Failed trying to delete interface: %s',
|
LOG.warning('Failed trying to delete interface: %s',
|
||||||
self.interface_name)
|
self.interface_name)
|
||||||
|
@ -16,6 +16,7 @@ from neutron_lib import constants as const
|
|||||||
from neutron_lib.db import model_query
|
from neutron_lib.db import model_query
|
||||||
from neutron_lib.objects import common_types
|
from neutron_lib.objects import common_types
|
||||||
from neutron_lib.utils import net as net_utils
|
from neutron_lib.utils import net as net_utils
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from oslo_utils import versionutils
|
from oslo_utils import versionutils
|
||||||
from oslo_versionedobjects import fields as obj_fields
|
from oslo_versionedobjects import fields as obj_fields
|
||||||
@ -32,6 +33,8 @@ from neutron.objects import network
|
|||||||
from neutron.objects import rbac_db
|
from neutron.objects import rbac_db
|
||||||
from neutron.services.segments import exceptions as segment_exc
|
from neutron.services.segments import exceptions as segment_exc
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@base.NeutronObjectRegistry.register
|
@base.NeutronObjectRegistry.register
|
||||||
class DNSNameServer(base.NeutronDbObject):
|
class DNSNameServer(base.NeutronDbObject):
|
||||||
@ -342,16 +345,15 @@ class Subnet(base.NeutronDbObject):
|
|||||||
# The host is known. Consider both routed and non-routed networks
|
# The host is known. Consider both routed and non-routed networks
|
||||||
results = cls._query_filter_by_segment_host_mapping(query, host).all()
|
results = cls._query_filter_by_segment_host_mapping(query, host).all()
|
||||||
|
|
||||||
# For now, we're using a simplifying assumption that a host will only
|
# For now, we know that OVS agent is supporting multi-segments.
|
||||||
# touch one segment in a given routed network. Raise exception
|
|
||||||
# otherwise. This restriction may be relaxed as use cases for multiple
|
|
||||||
# mappings are understood.
|
|
||||||
segment_ids = {subnet.segment_id
|
segment_ids = {subnet.segment_id
|
||||||
for subnet, mapping in results
|
for subnet, mapping in results
|
||||||
if mapping}
|
if mapping}
|
||||||
|
|
||||||
if len(segment_ids) > 1:
|
if len(segment_ids) > 1:
|
||||||
raise segment_exc.HostConnectedToMultipleSegments(
|
LOG.info("The network '%s' has multiple segments, "
|
||||||
host=host, network_id=network_id)
|
"this is currently supported by OVS agent only.",
|
||||||
|
network_id)
|
||||||
|
|
||||||
return [subnet for subnet, _mapping in results]
|
return [subnet for subnet, _mapping in results]
|
||||||
|
|
||||||
|
@ -49,12 +49,6 @@ class NetworkIdsDontMatch(exceptions.BadRequest):
|
|||||||
"the network_id of segment '%(segment_id)s'")
|
"the network_id of segment '%(segment_id)s'")
|
||||||
|
|
||||||
|
|
||||||
class HostConnectedToMultipleSegments(exceptions.Conflict):
|
|
||||||
message = _("Host %(host)s is connected to multiple segments on routed "
|
|
||||||
"provider network '%(network_id)s'. It should be connected "
|
|
||||||
"to one.")
|
|
||||||
|
|
||||||
|
|
||||||
class HostNotConnectedToAnySegment(exceptions.Conflict):
|
class HostNotConnectedToAnySegment(exceptions.Conflict):
|
||||||
message = _("Host %(host)s is not connected to any segments on routed "
|
message = _("Host %(host)s is not connected to any segments on routed "
|
||||||
"provider network '%(network_id)s'. It should be connected "
|
"provider network '%(network_id)s'. It should be connected "
|
||||||
|
@ -153,7 +153,7 @@ class ClientFixture(fixtures.Fixture):
|
|||||||
cidr=None, gateway_ip=None, name=None, enable_dhcp=True,
|
cidr=None, gateway_ip=None, name=None, enable_dhcp=True,
|
||||||
ipv6_address_mode='slaac', ipv6_ra_mode='slaac',
|
ipv6_address_mode='slaac', ipv6_ra_mode='slaac',
|
||||||
subnetpool_id=None, ip_version=None,
|
subnetpool_id=None, ip_version=None,
|
||||||
host_routes=None):
|
host_routes=None, segment=None):
|
||||||
resource_type = 'subnet'
|
resource_type = 'subnet'
|
||||||
|
|
||||||
name = name or utils.get_rand_name(prefix=resource_type)
|
name = name or utils.get_rand_name(prefix=resource_type)
|
||||||
@ -173,6 +173,8 @@ class ClientFixture(fixtures.Fixture):
|
|||||||
spec['cidr'] = cidr
|
spec['cidr'] = cidr
|
||||||
if host_routes:
|
if host_routes:
|
||||||
spec['host_routes'] = host_routes
|
spec['host_routes'] = host_routes
|
||||||
|
if segment:
|
||||||
|
spec['segment_id'] = segment
|
||||||
|
|
||||||
return self._create_resource(resource_type, spec)
|
return self._create_resource(resource_type, spec)
|
||||||
|
|
||||||
|
147
neutron/tests/fullstack/test_multisegs.py
Normal file
147
neutron/tests/fullstack/test_multisegs.py
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# 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 constants
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from neutron.common import utils as common_utils
|
||||||
|
from neutron.tests.fullstack import base
|
||||||
|
from neutron.tests.fullstack.resources import environment
|
||||||
|
from neutron.tests.fullstack.resources import machine
|
||||||
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
|
||||||
|
load_tests = testlib_api.module_load_tests
|
||||||
|
|
||||||
|
|
||||||
|
class TestMultiSegs(base.BaseFullStackTestCase):
|
||||||
|
scenarios = [
|
||||||
|
('Open vSwitch Agent', {'l2_agent_type': constants.AGENT_TYPE_OVS})]
|
||||||
|
num_hosts = 1
|
||||||
|
agent_down_time = 30
|
||||||
|
network_type = "vlan"
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
host_descriptions = [
|
||||||
|
environment.HostDescription(
|
||||||
|
dhcp_agent=True, l2_agent_type=constants.AGENT_TYPE_OVS),
|
||||||
|
]
|
||||||
|
|
||||||
|
env = environment.Environment(
|
||||||
|
environment.EnvironmentDescription(
|
||||||
|
network_type=self.network_type,
|
||||||
|
mech_drivers='openvswitch',
|
||||||
|
l2_pop=False,
|
||||||
|
arp_responder=False,
|
||||||
|
agent_down_time=self.agent_down_time,
|
||||||
|
service_plugins='router,segments',
|
||||||
|
api_workers=1,
|
||||||
|
),
|
||||||
|
host_descriptions)
|
||||||
|
|
||||||
|
super(TestMultiSegs, self).setUp(env)
|
||||||
|
self.project_id = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
def _spawn_vm(self, neutron_port=None):
|
||||||
|
vm = self.useFixture(
|
||||||
|
machine.FakeFullstackMachine(
|
||||||
|
self.environment.hosts[0],
|
||||||
|
self.network['id'],
|
||||||
|
self.project_id,
|
||||||
|
self.safe_client,
|
||||||
|
neutron_port=neutron_port,
|
||||||
|
use_dhcp=True))
|
||||||
|
vm.block_until_boot()
|
||||||
|
vm.block_until_dhcp_config_done()
|
||||||
|
return vm
|
||||||
|
|
||||||
|
def test_multi_segs_network(self):
|
||||||
|
ovs_physnet = None
|
||||||
|
agents = self.client.list_agents()
|
||||||
|
for agent in agents['agents']:
|
||||||
|
if agent['binary'] == 'neutron-openvswitch-agent':
|
||||||
|
ovs_physnet = list(
|
||||||
|
agent['configurations']['bridge_mappings'].keys())[0]
|
||||||
|
|
||||||
|
self.network = self.safe_client.create_network(
|
||||||
|
tenant_id=self.project_id,
|
||||||
|
network_type=self.network_type,
|
||||||
|
segmentation_id=1010,
|
||||||
|
physical_network=ovs_physnet)
|
||||||
|
|
||||||
|
self.segment1 = self.safe_client.client.list_segments()['segments'][0]
|
||||||
|
self.segment2 = self.safe_client.create_segment(
|
||||||
|
project_id=self.project_id,
|
||||||
|
network=self.network['id'],
|
||||||
|
network_type=self.network_type,
|
||||||
|
name='segment2',
|
||||||
|
segmentation_id=1011,
|
||||||
|
physical_network=ovs_physnet)
|
||||||
|
|
||||||
|
# Let's validate segments created on network
|
||||||
|
net = self.safe_client.client.show_network(
|
||||||
|
self.network['id'])['network']
|
||||||
|
self.assertEqual(
|
||||||
|
ovs_physnet, net['segments'][0]['provider:physical_network'])
|
||||||
|
self.assertEqual(
|
||||||
|
ovs_physnet, net['segments'][1]['provider:physical_network'])
|
||||||
|
self.assertEqual(
|
||||||
|
1010, net['segments'][0]['provider:segmentation_id'])
|
||||||
|
self.assertEqual(
|
||||||
|
1011, net['segments'][1]['provider:segmentation_id'])
|
||||||
|
|
||||||
|
self.subnet1 = self.safe_client.create_subnet(
|
||||||
|
self.project_id,
|
||||||
|
self.network['id'],
|
||||||
|
cidr='10.0.11.0/24',
|
||||||
|
gateway_ip='10.0.11.1',
|
||||||
|
name='subnet-test1',
|
||||||
|
enable_dhcp=True,
|
||||||
|
segment=self.segment1['id'])
|
||||||
|
|
||||||
|
self.port1 = self.safe_client.create_port(
|
||||||
|
network_id=self.network['id'],
|
||||||
|
tenant_id=self.project_id,
|
||||||
|
hostname=self.environment.hosts[0].hostname,
|
||||||
|
fixed_ips=[{'subnet_id': self.subnet1['id']}])
|
||||||
|
|
||||||
|
self.subnet2 = self.safe_client.create_subnet(
|
||||||
|
self.project_id,
|
||||||
|
self.network['id'],
|
||||||
|
cidr='10.0.12.0/24',
|
||||||
|
gateway_ip='10.0.12.1',
|
||||||
|
name='subnet-test2',
|
||||||
|
enable_dhcp=True,
|
||||||
|
segment=self.segment2['id'])
|
||||||
|
|
||||||
|
self.port2 = self.safe_client.create_port(
|
||||||
|
network_id=self.network['id'],
|
||||||
|
tenant_id=self.project_id,
|
||||||
|
hostname=self.environment.hosts[0].hostname,
|
||||||
|
fixed_ips=[{'subnet_id': self.subnet2['id']}])
|
||||||
|
|
||||||
|
def _is_dhcp_ports_ready():
|
||||||
|
dhcp_ports = self.safe_client.list_ports(**{
|
||||||
|
'device_owner': 'network:dhcp',
|
||||||
|
'network_id': self.network['id']})
|
||||||
|
if len(dhcp_ports) != 2:
|
||||||
|
return False
|
||||||
|
if dhcp_ports[0]['status'] != 'ACTIVE':
|
||||||
|
return False
|
||||||
|
if dhcp_ports[1]['status'] != 'ACTIVE':
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
common_utils.wait_until_true(_is_dhcp_ports_ready)
|
||||||
|
|
||||||
|
self.vm1 = self._spawn_vm(neutron_port=self.port1)
|
||||||
|
self.vm2 = self._spawn_vm(neutron_port=self.port2)
|
@ -1220,11 +1220,10 @@ class TestSegmentAwareIpam(SegmentAwareIpamTestCase):
|
|||||||
tenant_id=network['network']['tenant_id'],
|
tenant_id=network['network']['tenant_id'],
|
||||||
arg_list=(portbindings.HOST_ID,),
|
arg_list=(portbindings.HOST_ID,),
|
||||||
**{portbindings.HOST_ID: 'fakehost'})
|
**{portbindings.HOST_ID: 'fakehost'})
|
||||||
res = self.deserialize(self.fmt, response)
|
self.deserialize(self.fmt, response)
|
||||||
|
|
||||||
self.assertEqual(webob.exc.HTTPConflict.code, response.status_int)
|
# multi segments supported since Antelope.
|
||||||
self.assertEqual(segment_exc.HostConnectedToMultipleSegments.__name__,
|
self.assertEqual(webob.exc.HTTPCreated.code, response.status_int)
|
||||||
res['NeutronError']['type'])
|
|
||||||
|
|
||||||
def test_port_update_with_fixed_ips_ok_if_no_binding_host(self):
|
def test_port_update_with_fixed_ips_ok_if_no_binding_host(self):
|
||||||
"""No binding host information is provided, subnets on segments"""
|
"""No binding host information is provided, subnets on segments"""
|
||||||
@ -1552,12 +1551,10 @@ class TestSegmentAwareIpam(SegmentAwareIpamTestCase):
|
|||||||
port_id = port['port']['id']
|
port_id = port['port']['id']
|
||||||
port_req = self.new_update_request('ports', data, port_id)
|
port_req = self.new_update_request('ports', data, port_id)
|
||||||
response = port_req.get_response(self.api)
|
response = port_req.get_response(self.api)
|
||||||
res = self.deserialize(self.fmt, response)
|
self.deserialize(self.fmt, response)
|
||||||
|
|
||||||
# Gets conflict because it can't map the host to a segment
|
# multi segments supported since Antelope.
|
||||||
self.assertEqual(webob.exc.HTTPConflict.code, response.status_int)
|
self.assertEqual(webob.exc.HTTPOk.code, response.status_int)
|
||||||
self.assertEqual(segment_exc.HostConnectedToMultipleSegments.__name__,
|
|
||||||
res['NeutronError']['type'])
|
|
||||||
|
|
||||||
def test_port_update_allocate_no_segments(self):
|
def test_port_update_allocate_no_segments(self):
|
||||||
"""Binding information is provided, subnet created after port"""
|
"""Binding information is provided, subnet created after port"""
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Extend routed provider networks to allow provisioning more than
|
||||||
|
one segment per physical network.
|
Loading…
Reference in New Issue
Block a user