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
|
||||
networks.
|
||||
* 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()
|
||||
|
||||
def _destroy_namespace_and_port(self):
|
||||
segmentation_id = (
|
||||
self.segment.segmentation_id if self.segment else None)
|
||||
try:
|
||||
self.device_manager.destroy(
|
||||
self.network, self.interface_name, segmentation_id)
|
||||
self.network, self.interface_name, self.segment)
|
||||
except RuntimeError:
|
||||
LOG.warning('Failed trying to delete interface: %s',
|
||||
self.interface_name)
|
||||
|
@ -16,6 +16,7 @@ from neutron_lib import constants as const
|
||||
from neutron_lib.db import model_query
|
||||
from neutron_lib.objects import common_types
|
||||
from neutron_lib.utils import net as net_utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from oslo_utils import versionutils
|
||||
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.services.segments import exceptions as segment_exc
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@base.NeutronObjectRegistry.register
|
||||
class DNSNameServer(base.NeutronDbObject):
|
||||
@ -342,16 +345,15 @@ class Subnet(base.NeutronDbObject):
|
||||
# The host is known. Consider both routed and non-routed networks
|
||||
results = cls._query_filter_by_segment_host_mapping(query, host).all()
|
||||
|
||||
# For now, we're using a simplifying assumption that a host will only
|
||||
# touch one segment in a given routed network. Raise exception
|
||||
# otherwise. This restriction may be relaxed as use cases for multiple
|
||||
# mappings are understood.
|
||||
# For now, we know that OVS agent is supporting multi-segments.
|
||||
segment_ids = {subnet.segment_id
|
||||
for subnet, mapping in results
|
||||
if mapping}
|
||||
|
||||
if len(segment_ids) > 1:
|
||||
raise segment_exc.HostConnectedToMultipleSegments(
|
||||
host=host, network_id=network_id)
|
||||
LOG.info("The network '%s' has multiple segments, "
|
||||
"this is currently supported by OVS agent only.",
|
||||
network_id)
|
||||
|
||||
return [subnet for subnet, _mapping in results]
|
||||
|
||||
|
@ -49,12 +49,6 @@ class NetworkIdsDontMatch(exceptions.BadRequest):
|
||||
"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):
|
||||
message = _("Host %(host)s is not connected to any segments on routed "
|
||||
"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,
|
||||
ipv6_address_mode='slaac', ipv6_ra_mode='slaac',
|
||||
subnetpool_id=None, ip_version=None,
|
||||
host_routes=None):
|
||||
host_routes=None, segment=None):
|
||||
resource_type = 'subnet'
|
||||
|
||||
name = name or utils.get_rand_name(prefix=resource_type)
|
||||
@ -173,6 +173,8 @@ class ClientFixture(fixtures.Fixture):
|
||||
spec['cidr'] = cidr
|
||||
if host_routes:
|
||||
spec['host_routes'] = host_routes
|
||||
if segment:
|
||||
spec['segment_id'] = segment
|
||||
|
||||
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'],
|
||||
arg_list=(portbindings.HOST_ID,),
|
||||
**{portbindings.HOST_ID: 'fakehost'})
|
||||
res = self.deserialize(self.fmt, response)
|
||||
self.deserialize(self.fmt, response)
|
||||
|
||||
self.assertEqual(webob.exc.HTTPConflict.code, response.status_int)
|
||||
self.assertEqual(segment_exc.HostConnectedToMultipleSegments.__name__,
|
||||
res['NeutronError']['type'])
|
||||
# multi segments supported since Antelope.
|
||||
self.assertEqual(webob.exc.HTTPCreated.code, response.status_int)
|
||||
|
||||
def test_port_update_with_fixed_ips_ok_if_no_binding_host(self):
|
||||
"""No binding host information is provided, subnets on segments"""
|
||||
@ -1552,12 +1551,10 @@ class TestSegmentAwareIpam(SegmentAwareIpamTestCase):
|
||||
port_id = port['port']['id']
|
||||
port_req = self.new_update_request('ports', data, port_id)
|
||||
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
|
||||
self.assertEqual(webob.exc.HTTPConflict.code, response.status_int)
|
||||
self.assertEqual(segment_exc.HostConnectedToMultipleSegments.__name__,
|
||||
res['NeutronError']['type'])
|
||||
# multi segments supported since Antelope.
|
||||
self.assertEqual(webob.exc.HTTPOk.code, response.status_int)
|
||||
|
||||
def test_port_update_allocate_no_segments(self):
|
||||
"""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