Browse Source

Add multi-segment support

Neutron ML2 mechanisms allows multiple network segments. A net-show
call with a multi-segment setup won't return a single network-type
or segmentation id. Instead it returns a list of it with a certain
hierarchy.

Co-Authored-By: Daniel Gonzalez <daniel@gonzalez-nothnagel.de>
Change-Id: I3570752920d897c11cacb9e4a0b0d012ae2ce13a
Partially-Implements: bp manila-hpb-support
tags/3.0.0.0b3^0
Marc Koderer 3 years ago
parent
commit
8328ebde24

+ 82
- 6
manila/network/neutron/neutron_network_plugin.py View File

@@ -29,6 +29,16 @@ from manila import utils
29 29
 
30 30
 LOG = log.getLogger(__name__)
31 31
 
32
+neutron_network_plugin_opts = [
33
+    cfg.StrOpt(
34
+        'neutron_physical_net_name',
35
+        help="The name of the physical network to determine which net segment "
36
+             "is used. This opt is optional and will only be used for "
37
+             "networks configured with multiple segments.",
38
+        default=None,
39
+        deprecated_group='DEFAULT'),
40
+]
41
+
32 42
 neutron_single_network_plugin_opts = [
33 43
     cfg.StrOpt(
34 44
         'neutron_net_id',
@@ -95,6 +105,9 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
95 105
         self._neutron_api_args = args
96 106
         self._neutron_api_kwargs = kwargs
97 107
         self._label = kwargs.pop('label', 'user')
108
+        CONF.register_opts(
109
+            neutron_network_plugin_opts,
110
+            group=self.neutron_api.config_group_name)
98 111
 
99 112
     @property
100 113
     def label(self):
@@ -108,6 +121,10 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
108 121
                                                 **self._neutron_api_kwargs)
109 122
         return self._neutron_api
110 123
 
124
+    def _store_neutron_net_info(self, context, share_network):
125
+        self._save_neutron_network_data(context, share_network)
126
+        self._save_neutron_subnet_data(context, share_network)
127
+
111 128
     def allocate_network(self, context, share_server, share_network=None,
112 129
                          **kwargs):
113 130
         """Allocate network resources using given network information.
@@ -128,8 +145,7 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
128 145
             raise exception.NetworkBadConfigurationException(reason=msg)
129 146
 
130 147
         self._verify_share_network(share_server['id'], share_network)
131
-        self._save_neutron_network_data(context, share_network)
132
-        self._save_neutron_subnet_data(context, share_network)
148
+        self._store_neutron_net_info(context, share_network)
133 149
 
134 150
         allocation_count = kwargs.get('count', 1)
135 151
         device_owner = kwargs.get('device_owner', 'share')
@@ -181,8 +197,8 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
181 197
             'mac_address': port['mac_address'],
182 198
             'status': constants.STATUS_ACTIVE,
183 199
             'label': self.label,
184
-            'network_type': share_network['network_type'],
185
-            'segmentation_id': share_network['segmentation_id'],
200
+            'network_type': share_network.get('network_type'),
201
+            'segmentation_id': share_network.get('segmentation_id'),
186 202
             'ip_version': share_network['ip_version'],
187 203
             'cidr': share_network['cidr'],
188 204
             'mtu': share_network['mtu'],
@@ -203,13 +219,43 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
203 219
         extensions = self.neutron_api.list_extensions()
204 220
         return neutron_constants.PROVIDER_NW_EXT in extensions
205 221
 
222
+    def _is_neutron_multi_segment(self, share_network, net_info=None):
223
+        if net_info is None:
224
+            net_info = self.neutron_api.get_network(
225
+                share_network['neutron_net_id'])
226
+        return 'segments' in net_info
227
+
206 228
     def _save_neutron_network_data(self, context, share_network):
207 229
         net_info = self.neutron_api.get_network(
208 230
             share_network['neutron_net_id'])
231
+        segmentation_id = None
232
+        network_type = None
233
+
234
+        if self._is_neutron_multi_segment(share_network, net_info):
235
+            # we have a multi segment network and need to identify the
236
+            # lowest segment used for binding
237
+            phy_nets = []
238
+            phy = self.neutron_api.configuration.neutron_physical_net_name
239
+            if not phy:
240
+                msg = "Cannot identify segment used for binding. Please add "
241
+                "neutron_physical_net_name in configuration."
242
+                raise exception.NetworkBadConfigurationException(reason=msg)
243
+            for segment in net_info['segments']:
244
+                phy_nets.append(segment['provider:physical_network'])
245
+                if segment['provider:physical_network'] == phy:
246
+                    segmentation_id = segment['provider:segmentation_id']
247
+                    network_type = segment['provider:network_type']
248
+            if not (segmentation_id and network_type):
249
+                msg = ("No matching neutron_physical_net_name found for %s "
250
+                       "(found: %s)." % (phy, phy_nets))
251
+                raise exception.NetworkBadConfigurationException(reason=msg)
252
+        else:
253
+            network_type = net_info['provider:network_type']
254
+            segmentation_id = net_info['provider:segmentation_id']
209 255
 
210 256
         provider_nw_dict = {
211
-            'network_type': net_info['provider:network_type'],
212
-            'segmentation_id': net_info['provider:segmentation_id'],
257
+            'network_type': network_type,
258
+            'segmentation_id': segmentation_id,
213 259
             'mtu':  net_info['mtu'],
214 260
         }
215 261
         share_network.update(provider_nw_dict)
@@ -377,6 +423,23 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin):
377 423
                 "local_link_information": local_links}
378 424
         return arguments
379 425
 
426
+    def _store_neutron_net_info(self, context, share_network):
427
+        """Store the Neutron network info.
428
+
429
+        In case of dynamic multi segments the segment is determined while
430
+        binding the port. Therefore this method will return for multi segments
431
+        network without storing network information.
432
+
433
+        Instead, multi segments network will wait until ports are bound and
434
+        then store network information (see allocate_network()).
435
+        """
436
+        if self._is_neutron_multi_segment(share_network):
437
+            # In case of dynamic multi segment the segment is determined while
438
+            # binding the port
439
+            return
440
+        super(NeutronBindNetworkPlugin, self)._store_neutron_net_info(
441
+            context, share_network)
442
+
380 443
     def allocate_network(self, context, share_server, share_network=None,
381 444
                          **kwargs):
382 445
         ports = super(NeutronBindNetworkPlugin, self).allocate_network(
@@ -389,6 +452,19 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin):
389 452
         # order to update the ports with the correct binding.
390 453
         if self.config.neutron_vnic_type != 'normal':
391 454
             self._wait_for_ports_bind(ports, share_server)
455
+            if self._is_neutron_multi_segment(share_network):
456
+                # update segment information after port bind
457
+                super(NeutronBindNetworkPlugin, self)._store_neutron_net_info(
458
+                    context, share_network)
459
+                for num, port in enumerate(ports):
460
+                    port_info = {
461
+                        'network_type': share_network['network_type'],
462
+                        'segmentation_id': share_network['segmentation_id'],
463
+                        'cidr': share_network['cidr'],
464
+                        'ip_version': share_network['ip_version'],
465
+                    }
466
+                    ports[num] = self.db.network_allocation_update(
467
+                        context, port['id'], port_info)
392 468
         return ports
393 469
 
394 470
 

+ 226
- 6
manila/tests/network/neutron/test_neutron_plugin.py View File

@@ -73,8 +73,8 @@ fake_neutron_network = {
73 73
 
74 74
 fake_share_network = {
75 75
     'id': 'fake nw info id',
76
-    'neutron_subnet_id': 'fake subnet id',
77
-    'neutron_net_id': 'fake net id',
76
+    'neutron_subnet_id': fake_neutron_network['subnets'][0],
77
+    'neutron_net_id': fake_neutron_network['id'],
78 78
     'project_id': 'fake project id',
79 79
     'status': 'test_subnet_status',
80 80
     'name': 'fake name',
@@ -111,6 +111,77 @@ fake_network_allocation = {
111 111
     'mtu': 1509,
112 112
 }
113 113
 
114
+fake_nw_info = {
115
+    'segments': [
116
+        {
117
+            'provider:network_type': 'vlan',
118
+            'provider:physical_network': 'net1',
119
+            'provider:segmentation_id': 3926,
120
+        },
121
+        {
122
+            'provider:network_type': 'vxlan',
123
+            'provider:physical_network': None,
124
+            'provider:segmentation_id': 2000,
125
+        },
126
+    ],
127
+    'mtu': 1509,
128
+}
129
+
130
+fake_neutron_network_multi = {
131
+    'admin_state_up': True,
132
+    'availability_zone_hints': [],
133
+    'availability_zones': ['nova'],
134
+    'description': '',
135
+    'id': 'fake net id',
136
+    'ipv4_address_scope': None,
137
+    'ipv6_address_scope': None,
138
+    'name': 'test_neutron_network',
139
+    'port_security_enabled': True,
140
+    'router:external': False,
141
+    'shared': False,
142
+    'status': 'ACTIVE',
143
+    'subnets': ['fake subnet id',
144
+                'fake subnet id 2'],
145
+    'segments': fake_nw_info['segments'],
146
+    'mtu': fake_nw_info['mtu'],
147
+}
148
+
149
+fake_share_network_multi = {
150
+    'id': 'fake nw info id',
151
+    'neutron_subnet_id': fake_neutron_network_multi['subnets'][0],
152
+    'neutron_net_id': fake_neutron_network_multi['id'],
153
+    'project_id': 'fake project id',
154
+    'status': 'test_subnet_status',
155
+    'name': 'fake name',
156
+    'description': 'fake description',
157
+    'security_services': [],
158
+    'ip_version': 4,
159
+    'cidr': 'fake_cidr',
160
+    'gateway': 'fake_gateway',
161
+    'mtu': fake_neutron_network_multi['mtu'],
162
+}
163
+
164
+fake_network_allocation_multi = {
165
+    'id': fake_neutron_port['id'],
166
+    'share_server_id': fake_share_server['id'],
167
+    'ip_address': fake_neutron_port['fixed_ips'][0]['ip_address'],
168
+    'mac_address': fake_neutron_port['mac_address'],
169
+    'status': constants.STATUS_ACTIVE,
170
+    'label': 'user',
171
+    'network_type': None,
172
+    'segmentation_id': None,
173
+    'ip_version': fake_share_network_multi['ip_version'],
174
+    'cidr': fake_share_network_multi['cidr'],
175
+    'gateway': 'fake_gateway',
176
+    'mtu': fake_share_network_multi['mtu'],
177
+}
178
+
179
+fake_binding_profile = {
180
+    'neutron_switch_id': 'fake switch id',
181
+    'neutron_port_id': 'fake port id',
182
+    'neutron_switch_info': 'fake switch info'
183
+}
184
+
114 185
 
115 186
 class NeutronNetworkPluginTest(test.TestCase):
116 187
 
@@ -310,6 +381,58 @@ class NeutronNetworkPluginTest(test.TestCase):
310 381
                 fake_share_network['id'],
311 382
                 share_nw_update_dict)
312 383
 
384
+    @mock.patch.object(db_api, 'share_network_update', mock.Mock())
385
+    def test_save_neutron_network_data_multi_segment(self):
386
+        share_nw_update_dict = {
387
+            'network_type': 'vlan',
388
+            'segmentation_id': 3926,
389
+            'mtu': 1509
390
+        }
391
+        config_data = {
392
+            'DEFAULT': {
393
+                'neutron_physical_net_name': 'net1',
394
+            }
395
+        }
396
+
397
+        self.mock_object(self.plugin.neutron_api, 'get_network')
398
+        self.plugin.neutron_api.get_network.return_value = fake_nw_info
399
+
400
+        with test_utils.create_temp_config_with_opts(config_data):
401
+            self.plugin._save_neutron_network_data(self.fake_context,
402
+                                                   fake_share_network)
403
+
404
+        self.plugin.neutron_api.get_network.assert_called_once_with(
405
+            fake_share_network['neutron_net_id'])
406
+        self.plugin.db.share_network_update.assert_called_once_with(
407
+            self.fake_context,
408
+            fake_share_network['id'],
409
+            share_nw_update_dict)
410
+
411
+    @mock.patch.object(db_api, 'share_network_update', mock.Mock())
412
+    def test_save_neutron_network_data_multi_segment_without_ident(self):
413
+        config_data = {
414
+            'DEFAULT': {
415
+                'neutron_physical_net_name': 'net100',
416
+            }
417
+        }
418
+
419
+        self.mock_object(self.plugin.neutron_api, 'get_network')
420
+        self.plugin.neutron_api.get_network.return_value = fake_nw_info
421
+
422
+        with test_utils.create_temp_config_with_opts(config_data):
423
+            self.assertRaises(exception.NetworkBadConfigurationException,
424
+                              self.plugin._save_neutron_network_data,
425
+                              self.fake_context, fake_share_network)
426
+
427
+    @mock.patch.object(db_api, 'share_network_update', mock.Mock())
428
+    def test_save_neutron_network_data_multi_segment_without_cfg(self):
429
+        self.mock_object(self.plugin.neutron_api, 'get_network')
430
+        self.plugin.neutron_api.get_network.return_value = fake_nw_info
431
+
432
+        self.assertRaises(exception.NetworkBadConfigurationException,
433
+                          self.plugin._save_neutron_network_data,
434
+                          self.fake_context, fake_share_network)
435
+
313 436
     @mock.patch.object(db_api, 'share_network_update', mock.Mock())
314 437
     def test_save_neutron_subnet_data(self):
315 438
         neutron_subnet_info = {
@@ -543,6 +666,7 @@ class NeutronBindNetworkPluginTest(test.TestCase):
543 666
         self.bind_plugin = self._get_neutron_network_plugin_instance()
544 667
         self.bind_plugin.db = db_api
545 668
         self.sleep_mock = self.mock_object(time, 'sleep')
669
+        self.fake_share_network_multi = dict(fake_share_network_multi)
546 670
 
547 671
     def _get_neutron_network_plugin_instance(self, config_data=None):
548 672
         if config_data is None:
@@ -593,8 +717,6 @@ class NeutronBindNetworkPluginTest(test.TestCase):
593 717
                           [fake_neut_port1, fake_neut_port2],
594 718
                           fake_share_server)
595 719
 
596
-    @mock.patch.object(db_api, 'network_allocation_create',
597
-                       mock.Mock(return_values=fake_network_allocation))
598 720
     @mock.patch.object(db_api, 'share_network_get',
599 721
                        mock.Mock(return_value=fake_share_network))
600 722
     @mock.patch.object(db_api, 'share_server_get',
@@ -611,6 +733,10 @@ class NeutronBindNetworkPluginTest(test.TestCase):
611 733
         self.mock_object(neutron_host_id_opts, 'default')
612 734
         neutron_host_id_opts.default = 'foohost1'
613 735
         self.mock_object(db_api, 'network_allocation_create')
736
+        db_api.network_allocation_create.return_value = fake_network_allocation
737
+        self.mock_object(self.bind_plugin.neutron_api, 'get_network')
738
+        self.bind_plugin.neutron_api.get_network.return_value = (
739
+            fake_neutron_network)
614 740
 
615 741
         with mock.patch.object(self.bind_plugin.neutron_api, 'create_port',
616 742
                                mock.Mock(return_value=fake_neutron_port)):
@@ -641,6 +767,96 @@ class NeutronBindNetworkPluginTest(test.TestCase):
641 767
             self.bind_plugin._wait_for_ports_bind.assert_called_once_with(
642 768
                 [db_api.network_allocation_create()], fake_share_server)
643 769
 
770
+    @mock.patch.object(db_api, 'network_allocation_create',
771
+                       mock.Mock(return_values=fake_network_allocation_multi))
772
+    @mock.patch.object(db_api, 'share_network_get',
773
+                       mock.Mock(return_value=fake_share_network_multi))
774
+    @mock.patch.object(db_api, 'share_server_get',
775
+                       mock.Mock(return_value=fake_share_server))
776
+    def test_allocate_network_multi_segment(self):
777
+        network_allocation_update_data = {
778
+            'network_type':
779
+                fake_nw_info['segments'][0]['provider:network_type'],
780
+            'segmentation_id':
781
+                fake_nw_info['segments'][0]['provider:segmentation_id'],
782
+        }
783
+        network_update_data = dict(network_allocation_update_data)
784
+        network_update_data['mtu'] = fake_nw_info['mtu']
785
+        fake_network_allocation_multi_updated = dict(
786
+            fake_network_allocation_multi)
787
+        fake_network_allocation_multi_updated.update(
788
+            network_allocation_update_data)
789
+        fake_share_network_multi_updated = dict(fake_share_network_multi)
790
+        fake_share_network_multi_updated.update(network_update_data)
791
+        config_data = {
792
+            'DEFAULT': {
793
+                'neutron_net_id': 'fake net id',
794
+                'neutron_subnet_id': 'fake subnet id',
795
+                'neutron_physical_net_name': 'net1',
796
+            }
797
+        }
798
+        self.bind_plugin = self._get_neutron_network_plugin_instance(
799
+            config_data)
800
+        self.bind_plugin.db = db_api
801
+
802
+        self.mock_object(self.bind_plugin, '_has_provider_network_extension')
803
+        self.bind_plugin._has_provider_network_extension.return_value = True
804
+        save_subnet_data = self.mock_object(self.bind_plugin,
805
+                                            '_save_neutron_subnet_data')
806
+        self.mock_object(self.bind_plugin, '_wait_for_ports_bind')
807
+        neutron_host_id_opts = plugin.neutron_bind_network_plugin_opts[1]
808
+        self.mock_object(neutron_host_id_opts, 'default')
809
+        neutron_host_id_opts.default = 'foohost1'
810
+
811
+        self.mock_object(db_api, 'network_allocation_create')
812
+        db_api.network_allocation_create.return_value = (
813
+            fake_network_allocation_multi)
814
+        self.mock_object(db_api, 'network_allocation_update')
815
+        db_api.network_allocation_update.return_value = (
816
+            fake_network_allocation_multi_updated)
817
+        self.mock_object(self.bind_plugin.neutron_api, 'get_network')
818
+        self.bind_plugin.neutron_api.get_network.return_value = (
819
+            fake_neutron_network_multi)
820
+        self.mock_object(db_api, 'share_network_update')
821
+
822
+        with mock.patch.object(self.bind_plugin.neutron_api, 'create_port',
823
+                               mock.Mock(return_value=fake_neutron_port)):
824
+            self.bind_plugin.allocate_network(
825
+                self.fake_context,
826
+                fake_share_server,
827
+                self.fake_share_network_multi,
828
+                allocation_info={'count': 1})
829
+
830
+            self.bind_plugin._has_provider_network_extension.assert_any_call()
831
+            save_subnet_data.assert_called_once_with(
832
+                self.fake_context,
833
+                fake_share_network_multi_updated)
834
+            expected_kwargs = {
835
+                'binding:vnic_type': 'baremetal',
836
+                'host_id': 'foohost1',
837
+                'network_id': fake_share_network_multi['neutron_net_id'],
838
+                'subnet_id': fake_share_network_multi['neutron_subnet_id'],
839
+                'device_owner': 'manila:share',
840
+                'device_id': fake_share_network_multi['id']
841
+            }
842
+            self.bind_plugin.neutron_api.create_port.assert_called_once_with(
843
+                fake_share_network_multi['project_id'], **expected_kwargs)
844
+            db_api.network_allocation_create.assert_called_once_with(
845
+                self.fake_context,
846
+                fake_network_allocation_multi)
847
+            db_api.share_network_update.assert_called_once_with(
848
+                self.fake_context,
849
+                fake_share_network_multi['id'],
850
+                network_update_data)
851
+            network_allocation_update_data['cidr'] = (
852
+                fake_share_network_multi['cidr'])
853
+            network_allocation_update_data['ip_version'] = (
854
+                fake_share_network_multi['ip_version'])
855
+            db_api.network_allocation_update.assert_called_once_with(
856
+                self.fake_context,
857
+                fake_neutron_port['id'],
858
+                network_allocation_update_data)
859
+
644 860
     @ddt.data({
645 861
         'neutron_binding_profiles': None,
646 862
         'binding_profiles': {}
@@ -821,6 +1037,7 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase):
821 1037
                 'DEFAULT': {
822 1038
                     'neutron_net_id': fake_net_id,
823 1039
                     'neutron_subnet_id': fake_subnet_id,
1040
+                    'neutron_physical_net_name': 'net1',
824 1041
                 }
825 1042
             }
826 1043
             fake_net = {'subnets': ['fake1', 'fake2', fake_subnet_id]}
@@ -836,8 +1053,8 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase):
836 1053
             'port1', 'port2']
837 1054
         instance = self._get_neutron_network_plugin_instance()
838 1055
         share_server = 'fake_share_server'
839
-        share_network = 'fake_share_network'
840
-        share_network_upd = 'updated_fake_share_network'
1056
+        share_network = {'neutron_net_id': {}}
1057
+        share_network_upd = {'neutron_net_id': {'upd': True}}
841 1058
         count = 2
842 1059
         device_owner = 'fake_device_owner'
843 1060
         self.mock_object(
@@ -1279,6 +1496,9 @@ class NeutronBindNetworkPluginWithNormalTypeTest(test.TestCase):
1279 1496
         self.mock_object(neutron_host_id_opts, 'default')
1280 1497
         neutron_host_id_opts.default = 'foohost1'
1281 1498
         self.mock_object(db_api, 'network_allocation_create')
1499
+        multi_seg = self.mock_object(
1500
+            self.bind_plugin, '_is_neutron_multi_segment')
1501
+        multi_seg.return_value = False
1282 1502
 
1283 1503
         with mock.patch.object(self.bind_plugin.neutron_api, 'create_port',
1284 1504
                                mock.Mock(return_value=fake_neutron_port)):

+ 3
- 0
releasenotes/notes/multi-segment-support-fa171a8e3201d54e.yaml View File

@@ -0,0 +1,3 @@
1
+---
2
+features:
3
+  - Added port binding support for neutron networks with multiple segments.

Loading…
Cancel
Save