Coverage for manila/network/neutron/neutron_network_plugin.py: 92%
327 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2013 OpenStack Foundation
2# Copyright 2015 Mirantis, Inc.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
17import ipaddress
18import socket
20from oslo_config import cfg
21from oslo_log import log
23from manila.common import constants
24from manila import exception
25from manila.i18n import _
26from manila import network
27from manila.network.neutron import api as neutron_api
28from manila.network.neutron import constants as neutron_constants
29from manila.share import utils as share_utils
30from manila import utils
32LOG = log.getLogger(__name__)
34neutron_network_plugin_opts = [
35 cfg.StrOpt(
36 'neutron_physical_net_name',
37 help="The name of the physical network to determine which net segment "
38 "is used. This opt is optional and will only be used for "
39 "networks configured with multiple segments."),
40]
42neutron_single_network_plugin_opts = [
43 cfg.StrOpt(
44 'neutron_net_id',
45 help="Default Neutron network that will be used for share server "
46 "creation. This opt is used only with "
47 "class 'NeutronSingleNetworkPlugin'."),
48 cfg.StrOpt(
49 'neutron_subnet_id',
50 help="Default Neutron subnet that will be used for share server "
51 "creation. Should be assigned to network defined in opt "
52 "'neutron_net_id'. This opt is used only with "
53 "class 'NeutronSingleNetworkPlugin'."),
54]
56neutron_bind_network_plugin_opts = [
57 cfg.StrOpt(
58 'neutron_vnic_type',
59 help="vNIC type used for binding.",
60 choices=['baremetal', 'normal', 'direct',
61 'direct-physical', 'macvtap'],
62 default='baremetal'),
63 cfg.StrOpt(
64 "neutron_host_id",
65 help="Host ID to be used when creating neutron port. If not set "
66 "host is set to manila-share host by default.",
67 default=socket.gethostname()),
68]
70neutron_binding_profile = [
71 cfg.ListOpt(
72 "neutron_binding_profiles",
73 help="A list of binding profiles to be used during port binding. This "
74 "option can be used with the NeutronBindNetworkPlugin. The value for "
75 "this option has to be a comma separated list of names that "
76 "correspond to each binding profile. Each binding profile needs to be "
77 "specified as an individual configuration section using the binding "
78 "profile name as the section name."),
79]
81neutron_binding_profile_opts = [
82 cfg.StrOpt(
83 'neutron_switch_id',
84 help="Switch ID for binding profile."),
85 cfg.StrOpt(
86 'neutron_port_id',
87 help="Port ID on the given switch.",),
88 cfg.DictOpt(
89 'neutron_switch_info',
90 help="Switch label. For example: 'switch_ip: 10.4.30.5'. Multiple "
91 "key-value pairs separated by commas are accepted.",),
92]
94CONF = cfg.CONF
97class NeutronNetworkPlugin(network.NetworkBaseAPI):
99 def __init__(self, *args, **kwargs):
100 db_driver = kwargs.pop('db_driver', None)
101 config_group_name = kwargs.get('config_group_name', 'DEFAULT')
102 super(NeutronNetworkPlugin,
103 self).__init__(config_group_name=config_group_name,
104 db_driver=db_driver)
105 self._neutron_api = None
106 self._neutron_api_args = args
107 self._neutron_api_kwargs = kwargs
108 self._label = kwargs.pop('label', 'user')
109 CONF.register_opts(
110 neutron_network_plugin_opts,
111 group=self.neutron_api.config_group_name)
113 @property
114 def label(self):
115 return self._label
117 @property
118 @utils.synchronized("instantiate_neutron_api")
119 def neutron_api(self):
120 if not self._neutron_api:
121 self._neutron_api = neutron_api.API(*self._neutron_api_args,
122 **self._neutron_api_kwargs)
123 return self._neutron_api
125 def include_network_info(self, share_network_subnet):
126 """Includes share-network-subnet with plugin specific data."""
127 self._store_and_get_neutron_net_info(None,
128 share_network_subnet,
129 save_db=False)
131 def _store_and_get_neutron_net_info(self, context, share_network_subnet,
132 save_db=True):
133 is_external_network = self._save_neutron_network_data(
134 context, share_network_subnet, save_db=save_db)
135 self._save_neutron_subnet_data(context, share_network_subnet,
136 save_db=save_db)
137 return is_external_network
139 def allocate_network(self, context, share_server, share_network=None,
140 share_network_subnet=None, **kwargs):
141 """Allocate network resources using given network information.
143 Create neutron ports for a given neutron network and subnet,
144 create manila db records for allocated neutron ports.
146 :param context: RequestContext object
147 :param share_server: share server data
148 :param share_network: share network data
149 :param share_network_subnet: share network subnet data
150 :param kwargs: allocations parameters given by the back-end
151 driver. Supported params:
152 'count' - how many allocations should be created
153 'device_owner' - set owner for network allocations
154 :rtype: list of :class: 'dict'
155 """
156 if not self._has_provider_network_extension(): 156 ↛ 157line 156 didn't jump to line 157 because the condition on line 156 was never true
157 msg = "%s extension required" % neutron_constants.PROVIDER_NW_EXT
158 raise exception.NetworkBadConfigurationException(reason=msg)
160 self._verify_share_network(share_server['id'], share_network)
161 self._verify_share_network_subnet(share_server['id'],
162 share_network_subnet)
163 is_external_network = self._store_and_get_neutron_net_info(
164 context, share_network_subnet)
166 allocation_count = kwargs.get('count', 1)
167 device_owner = kwargs.get('device_owner', 'share')
169 ports = []
170 for current_count in range(0, allocation_count):
171 ports.append(
172 self._create_port(context,
173 share_server,
174 share_network,
175 share_network_subnet,
176 device_owner,
177 current_count,
178 is_external_network=is_external_network),
179 )
181 return ports
183 def manage_network_allocations(
184 self, context, allocations, share_server, share_network=None,
185 share_network_subnet=None):
187 self._verify_share_network_subnet(share_server['id'],
188 share_network_subnet)
189 self._store_and_get_neutron_net_info(context, share_network_subnet)
191 # We begin matching the allocations to known neutron ports and
192 # finally return the non-consumed allocations
193 remaining_allocations = list(allocations)
194 fixed_ip_filter = ('subnet_id=' +
195 share_network_subnet['neutron_subnet_id'])
197 port_list = self.neutron_api.list_ports(
198 network_id=share_network_subnet['neutron_net_id'],
199 device_owner='manila:share',
200 fixed_ips=fixed_ip_filter)
202 selected_ports = self._get_ports_respective_to_ips(
203 remaining_allocations, port_list)
205 LOG.debug("Found matching allocations in Neutron:"
206 " %s", selected_ports)
208 for selected_port in selected_ports:
209 port_dict = {
210 'id': selected_port['port']['id'],
211 'share_server_id': share_server['id'],
212 'ip_address': selected_port['allocation'],
213 'gateway': share_network_subnet['gateway'],
214 'mac_address': selected_port['port']['mac_address'],
215 'status': constants.STATUS_ACTIVE,
216 'label': self.label,
217 'network_type': share_network_subnet.get('network_type'),
218 'segmentation_id': share_network_subnet.get('segmentation_id'),
219 'ip_version': share_network_subnet['ip_version'],
220 'cidr': share_network_subnet['cidr'],
221 'mtu': share_network_subnet['mtu'],
222 }
223 # NOTE(felipe_rodrigues): admin plugin does not have any Manila
224 # share net subnet, its data is from manila configuration file.
225 if self.label != 'admin': 225 ↛ 230line 225 didn't jump to line 230 because the condition on line 225 was always true
226 port_dict['share_network_subnet_id'] = (
227 share_network_subnet['id'])
229 # There should not be existing allocations with the same port_id.
230 try:
231 existing_port = self.db.network_allocation_get(
232 context, selected_port['port']['id'], read_deleted=False)
233 except exception.NotFound:
234 pass
235 else:
236 msg = _("There were existing conflicting manila network "
237 "allocations found while trying to manage share "
238 "server %(new_ss)s. The conflicting port belongs to "
239 "share server %(old_ss)s.") % {
240 'new_ss': share_server['id'],
241 'old_ss': existing_port['share_server_id'],
242 }
243 raise exception.ManageShareServerError(reason=msg)
245 # If there are previously deleted allocations, we undelete them
246 try:
247 self.db.network_allocation_get(
248 context, selected_port['port']['id'], read_deleted=True)
249 except exception.NotFound:
250 self.db.network_allocation_create(context, port_dict)
251 else:
252 port_dict.pop('id')
253 port_dict.update({
254 'deleted_at': None,
255 'deleted': 'False',
256 })
257 self.db.network_allocation_update(
258 context, selected_port['port']['id'], port_dict,
259 read_deleted=True)
261 remaining_allocations.remove(selected_port['allocation'])
263 return remaining_allocations
265 def unmanage_network_allocations(self, context, share_server_id):
267 ports = self.db.network_allocations_get_for_share_server(
268 context, share_server_id)
270 for port in ports:
271 self.db.network_allocation_delete(context, port['id'])
273 def _get_ports_respective_to_ips(self, allocations, port_list):
275 selected_ports = []
277 for port in port_list:
278 for ip in port['fixed_ips']:
279 if ip['ip_address'] in allocations:
280 if not any(port['id'] == p['port']['id']
281 for p in selected_ports):
282 selected_ports.append(
283 {'port': port, 'allocation': ip['ip_address']})
284 else:
285 LOG.warning("Port %s has more than one IP that "
286 "matches allocations, please use ports "
287 "respective to only one allocation IP.",
288 port['id'])
290 return selected_ports
292 def _get_matched_ip_address(self, fixed_ips, ip_version):
293 """Get first ip address which matches the specified ip_version."""
295 for ip in fixed_ips:
296 try:
297 address = ipaddress.ip_address(str(ip['ip_address']))
298 if address.version == ip_version:
299 return ip['ip_address']
300 except ValueError:
301 LOG.error("%(address)s isn't a valid ip "
302 "address, omitted.", {'address':
303 ip['ip_address']})
304 msg = _("Can not find any IP address with configured IP "
305 "version %(version)s in share-network.") % {'version':
306 ip_version}
307 raise exception.NetworkBadConfigurationException(reason=msg)
309 def deallocate_network(self, context, share_server_id,
310 share_network=None, share_network_subnet=None):
311 """Deallocate neutron network resources for the given share server.
313 Delete previously allocated neutron ports, delete manila db
314 records for deleted ports.
316 :param context: RequestContext object
317 :param share_server_id: id of share server
318 :param share_network: share network data
319 :param share_network_subnet: share network subnet data
320 :rtype: None
321 """
322 ports = self.db.network_allocations_get_for_share_server(
323 context, share_server_id)
325 for port in ports:
326 self._delete_port(context, port)
328 # It may be possible that there are ports existing without a
329 # corresponding manila network allocation entry in the manila db,
330 # because port create request may have been successfully sent to
331 # neutron, but the response, the created port could not be stored
332 # in manila due to unreachable db
333 if share_network_subnet: 333 ↛ 334line 333 didn't jump to line 334 because the condition on line 333 was never true
334 ports = []
335 try:
336 ports = self.neutron_api.list_ports(
337 network_id=share_network_subnet['neutron_net_id'],
338 device_owner='manila:share',
339 device_id=share_server_id)
340 except exception.NetworkException:
341 LOG.warning("Failed to list ports using neutron API during "
342 "deallocate_network.")
343 for port in ports:
344 LOG.debug(f"Deleting orphaned port {port['id']} belonging to "
345 f"share server {share_server_id} in neutron "
346 f"network {share_network_subnet['neutron_net_id']}")
347 self._delete_port(context, port, ignore_db=True)
349 def _get_port_create_args(self, share_server, share_network_subnet,
350 device_owner, count=0,
351 is_external_network=False):
352 return {
353 "network_id": share_network_subnet['neutron_net_id'],
354 "subnet_id": share_network_subnet['neutron_subnet_id'],
355 "device_owner": 'manila:' + device_owner,
356 "device_id": share_server.get('id'),
357 "name": share_server.get('id') + '_' + str(count),
358 # NOTE (gouthamr): we create disabled ports with external networks
359 # since the actual ports are not managed by neutron. The ports
360 # neutron creates merely assist in IPAM.
361 "admin_state_up": not is_external_network,
362 }
364 def _create_port(self, context, share_server, share_network,
365 share_network_subnet, device_owner, count=0,
366 is_external_network=False):
367 create_args = self._get_port_create_args(
368 share_server, share_network_subnet, device_owner, count,
369 is_external_network=is_external_network)
371 port = self.neutron_api.create_port(
372 share_network['project_id'], **create_args)
374 if is_external_network:
375 msg = (
376 f"Port '{port['id']}' is disabled to prevent improper "
377 f"routing on an external network."
378 )
379 LOG.info(msg)
381 ip_address = self._get_matched_ip_address(
382 port['fixed_ips'], share_network_subnet['ip_version'])
383 port_dict = {
384 'id': port['id'],
385 'share_server_id': share_server['id'],
386 'ip_address': ip_address,
387 'gateway': share_network_subnet['gateway'],
388 'mac_address': port['mac_address'],
389 'status': constants.STATUS_ACTIVE,
390 'label': self.label,
391 'network_type': share_network_subnet.get('network_type'),
392 'segmentation_id': share_network_subnet.get('segmentation_id'),
393 'ip_version': share_network_subnet['ip_version'],
394 'cidr': share_network_subnet['cidr'],
395 'mtu': share_network_subnet['mtu'],
396 }
397 # NOTE(felipe_rodrigues): admin plugin does not have any Manila
398 # share net subnet, its data is from manila configuration file.
399 if self.label != 'admin': 399 ↛ 403line 399 didn't jump to line 403 because the condition on line 399 was always true
400 port_dict['share_network_subnet_id'] = (
401 share_network_subnet['id'])
403 return self.db.network_allocation_create(context, port_dict)
405 def _delete_port(self, context, port, ignore_db=False):
406 try:
407 self.neutron_api.delete_port(port['id'])
408 except exception.NetworkException:
409 if not ignore_db: 409 ↛ 412line 409 didn't jump to line 412 because the condition on line 409 was always true
410 self.db.network_allocation_update(
411 context, port['id'], {'status': constants.STATUS_ERROR})
412 raise
413 else:
414 if not ignore_db: 414 ↛ exitline 414 didn't return from function '_delete_port' because the condition on line 414 was always true
415 self.db.network_allocation_delete(context, port['id'])
417 def _has_provider_network_extension(self):
418 extensions = self.neutron_api.list_extensions()
419 return neutron_constants.PROVIDER_NW_EXT in extensions
421 def _is_neutron_multi_segment(self, share_network_subnet, net_info=None):
422 if net_info is None:
423 net_info = self.neutron_api.get_network(
424 share_network_subnet['neutron_net_id'])
425 return 'segments' in net_info
427 def _save_neutron_network_data(self, context, share_network_subnet,
428 save_db=True):
429 net_info = self.neutron_api.get_network(
430 share_network_subnet['neutron_net_id'])
431 segmentation_id = None
432 network_type = None
433 is_external_network = net_info.get('router:external', False)
435 if self._is_neutron_multi_segment(share_network_subnet, net_info):
436 # we have a multi segment network and need to identify the
437 # lowest segment used for binding
438 phy_nets = []
439 phy = self.neutron_api.configuration.neutron_physical_net_name
440 if not phy:
441 msg = "Cannot identify segment used for binding. Please add "
442 "neutron_physical_net_name in configuration."
443 raise exception.NetworkBadConfigurationException(reason=msg)
444 for segment in net_info['segments']:
445 phy_nets.append(segment['provider:physical_network'])
446 if segment['provider:physical_network'] == phy:
447 segmentation_id = segment['provider:segmentation_id']
448 network_type = segment['provider:network_type']
449 if not (segmentation_id and network_type):
450 msg = ("No matching neutron_physical_net_name found for %s "
451 "(found: %s)." % (phy, phy_nets))
452 raise exception.NetworkBadConfigurationException(reason=msg)
453 else:
454 network_type = net_info.get('provider:network_type')
455 segmentation_id = net_info.get('provider:segmentation_id')
457 provider_nw_dict = {
458 'network_type': network_type,
459 'segmentation_id': segmentation_id,
460 'mtu': net_info.get('mtu'),
461 }
462 share_network_subnet.update(provider_nw_dict)
464 if self.label != 'admin' and save_db: 464 ↛ 468line 464 didn't jump to line 468 because the condition on line 464 was always true
465 self.db.share_network_subnet_update(
466 context, share_network_subnet['id'], provider_nw_dict)
468 return is_external_network
470 def _save_neutron_subnet_data(self, context, share_network_subnet,
471 save_db=True):
472 subnet_info = self.neutron_api.get_subnet(
473 share_network_subnet['neutron_subnet_id'])
475 subnet_values = {
476 'cidr': subnet_info['cidr'],
477 'gateway': subnet_info['gateway_ip'],
478 'ip_version': subnet_info['ip_version']
479 }
480 share_network_subnet.update(subnet_values)
482 if self.label != 'admin' and save_db: 482 ↛ exitline 482 didn't return from function '_save_neutron_subnet_data' because the condition on line 482 was always true
483 self.db.share_network_subnet_update(
484 context, share_network_subnet['id'], subnet_values)
487class NeutronSingleNetworkPlugin(NeutronNetworkPlugin):
489 def __init__(self, *args, **kwargs):
490 super(NeutronSingleNetworkPlugin, self).__init__(*args, **kwargs)
491 CONF.register_opts(
492 neutron_single_network_plugin_opts,
493 group=self.neutron_api.config_group_name)
494 self.net = self.neutron_api.configuration.neutron_net_id
495 self.subnet = self.neutron_api.configuration.neutron_subnet_id
496 self._verify_net_and_subnet()
498 def _select_proper_share_network_subnet(self, context,
499 share_network_subnet):
500 if self.label != 'admin':
501 share_network_subnet = self._update_share_network_net_data(
502 context, share_network_subnet)
503 else:
504 share_network_subnet = {
505 'project_id': self.neutron_api.admin_project_id,
506 'neutron_net_id': self.net,
507 'neutron_subnet_id': self.subnet,
508 }
509 return share_network_subnet
511 def allocate_network(self, context, share_server, share_network=None,
512 share_network_subnet=None, **kwargs):
514 share_network_subnet = self._select_proper_share_network_subnet(
515 context, share_network_subnet)
516 # Update share network project_id info if needed
517 if share_network_subnet.get('project_id', None) is not None: 517 ↛ 518line 517 didn't jump to line 518 because the condition on line 517 was never true
518 share_network['project_id'] = share_network_subnet.pop(
519 'project_id')
521 return super(NeutronSingleNetworkPlugin, self).allocate_network(
522 context, share_server, share_network, share_network_subnet,
523 **kwargs)
525 def manage_network_allocations(
526 self, context, allocations, share_server, share_network=None,
527 share_network_subnet=None):
528 share_network_subnet = self._select_proper_share_network_subnet(
529 context, share_network_subnet)
530 # Update share network project_id info if needed
531 if share_network and share_network_subnet.get('project_id', None): 531 ↛ 532line 531 didn't jump to line 532 because the condition on line 531 was never true
532 share_network['project_id'] = (
533 share_network_subnet.pop('project_id'))
535 return super(NeutronSingleNetworkPlugin,
536 self).manage_network_allocations(
537 context, allocations, share_server, share_network,
538 share_network_subnet)
540 def _verify_net_and_subnet(self):
541 data = dict(net=self.net, subnet=self.subnet)
542 if self.net and self.subnet:
543 net = self.neutron_api.get_network(self.net)
544 if not (net.get('subnets') and data['subnet'] in net['subnets']):
545 raise exception.NetworkBadConfigurationException(
546 "Subnet '%(subnet)s' does not belong to "
547 "network '%(net)s'." % data)
548 else:
549 raise exception.NetworkBadConfigurationException(
550 "Neutron net and subnet are expected to be both set. "
551 "Got: net=%(net)s and subnet=%(subnet)s." % data)
553 def _update_share_network_net_data(self, context, share_network_subnet):
554 upd = dict()
556 if not share_network_subnet.get('neutron_net_id') == self.net:
557 if share_network_subnet.get('neutron_net_id') is not None:
558 raise exception.NetworkBadConfigurationException(
559 "Using neutron net id different from None or value "
560 "specified in the config is forbidden for "
561 "NeutronSingleNetworkPlugin. Allowed values: (%(net)s, "
562 "None), received value: %(err)s" % {
563 "net": self.net,
564 "err": share_network_subnet.get('neutron_net_id')})
565 upd['neutron_net_id'] = self.net
566 if not share_network_subnet.get('neutron_subnet_id') == self.subnet:
567 if share_network_subnet.get('neutron_subnet_id') is not None:
568 raise exception.NetworkBadConfigurationException(
569 "Using neutron subnet id different from None or value "
570 "specified in the config is forbidden for "
571 "NeutronSingleNetworkPlugin. Allowed values: (%(snet)s, "
572 "None), received value: %(err)s" % {
573 "snet": self.subnet,
574 "err": share_network_subnet.get('neutron_subnet_id')})
575 upd['neutron_subnet_id'] = self.subnet
576 if upd:
577 share_network_subnet = self.db.share_network_subnet_update(
578 context, share_network_subnet['id'], upd)
579 return share_network_subnet
582class NeutronBindNetworkPlugin(NeutronNetworkPlugin):
583 def __init__(self, *args, **kwargs):
584 super(NeutronBindNetworkPlugin, self).__init__(*args, **kwargs)
586 self.binding_profiles = []
587 CONF.register_opts(
588 neutron_binding_profile,
589 group=self.neutron_api.config_group_name)
590 conf = CONF[self.neutron_api.config_group_name]
591 if conf.neutron_binding_profiles:
592 for profile in conf.neutron_binding_profiles:
593 CONF.register_opts(neutron_binding_profile_opts, group=profile)
594 self.binding_profiles.append(profile)
596 CONF.register_opts(
597 neutron_bind_network_plugin_opts,
598 group=self.neutron_api.config_group_name)
599 self.config = self.neutron_api.configuration
601 def update_network_allocation(self, context, share_server):
602 if self.config.neutron_vnic_type == 'normal': 602 ↛ exitline 602 didn't return from function 'update_network_allocation' because the condition on line 602 was always true
603 ports = self.db.network_allocations_get_for_share_server(
604 context,
605 share_server['id'])
606 self._wait_for_ports_bind(ports, share_server)
607 return ports
609 @utils.retry(retry_param=exception.NetworkBindException, retries=20)
610 def _wait_for_ports_bind(self, ports, share_server):
611 inactive_ports = []
612 for port in ports:
613 port = self._neutron_api.show_port(port['id'])
614 if (port['status'] == neutron_constants.PORT_STATUS_ERROR or
615 ('binding:vif_type' in port and
616 port['binding:vif_type'] ==
617 neutron_constants.VIF_TYPE_BINDING_FAILED)):
618 msg = _("Port binding %s failed.") % port['id']
619 raise exception.NetworkException(msg)
620 elif port['status'] != neutron_constants.PORT_STATUS_ACTIVE:
621 LOG.debug("The port %(id)s is in state %(state)s. "
622 "Wait for active state.", {
623 "id": port['id'],
624 "state": port['status']})
625 inactive_ports.append(port['id'])
626 if len(inactive_ports) == 0:
627 return
628 msg = _("Ports are not fully bound for share server "
629 "'%(s_id)s' (inactive ports: %(ports)s)") % {
630 "s_id": share_server['id'],
631 "ports": inactive_ports}
632 raise exception.NetworkBindException(msg)
634 def _get_port_create_args(self, share_server, share_network_subnet,
635 device_owner, count=0,
636 is_external_network=False):
637 arguments = super(
638 NeutronBindNetworkPlugin, self)._get_port_create_args(
639 share_server, share_network_subnet, device_owner, count,
640 is_external_network=is_external_network
641 )
642 arguments['host_id'] = self.config.neutron_host_id
643 arguments['binding:vnic_type'] = self.config.neutron_vnic_type
644 if self.binding_profiles:
645 local_links = []
646 for profile in self.binding_profiles:
647 local_links.append({
648 'switch_id': CONF[profile]['neutron_switch_id'],
649 'port_id': CONF[profile]['neutron_port_id'],
650 'switch_info': CONF[profile]['neutron_switch_info'],
651 })
653 arguments['binding:profile'] = {
654 "local_link_information": local_links}
655 return arguments
657 def _save_neutron_network_data(self, context, share_network_subnet,
658 save_db=True):
659 """Store the Neutron network info.
661 In case of dynamic multi segments the segment is determined while
662 binding the port. Therefore this method will return for multi segments
663 network without storing network information (apart from mtu).
665 Instead, multi segments network will wait until ports are bound and
666 then store network information (see allocate_network()).
667 """
668 if self._is_neutron_multi_segment(share_network_subnet): 668 ↛ 674line 668 didn't jump to line 674 because the condition on line 668 was always true
669 # In case of dynamic multi segment the segment is determined while
670 # binding the port, only mtu is known and already needed
671 self._save_neutron_network_mtu(context, share_network_subnet,
672 save_db=save_db)
673 return
674 super(NeutronBindNetworkPlugin, self)._save_neutron_network_data(
675 context, share_network_subnet, save_db=save_db)
677 def _save_neutron_network_mtu(self, context, share_network_subnet,
678 save_db=True):
679 """Store the Neutron network mtu.
681 In case of dynamic multi segments only the mtu needs storing before
682 binding the port.
683 """
684 net_info = self.neutron_api.get_network(
685 share_network_subnet['neutron_net_id'])
687 mtu_dict = {
688 'mtu': net_info['mtu'],
689 }
690 share_network_subnet.update(mtu_dict)
692 if self.label != 'admin' and save_db: 692 ↛ exitline 692 didn't return from function '_save_neutron_network_mtu' because the condition on line 692 was always true
693 self.db.share_network_subnet_update(
694 context, share_network_subnet['id'], mtu_dict)
696 def allocate_network(self, context, share_server, share_network=None,
697 share_network_subnet=None, **kwargs):
698 ports = super(NeutronBindNetworkPlugin, self).allocate_network(
699 context, share_server, share_network, share_network_subnet,
700 **kwargs)
701 # If vnic type is 'normal' we expect a neutron agent to bind the
702 # ports. This action requires a vnic to be spawned by the driver.
703 # Therefore we do not wait for the port binding here, but
704 # return the unbound ports and expect the share manager to call
705 # update_network_allocation after the share server was created, in
706 # order to update the ports with the correct binding.
707 if self.config.neutron_vnic_type != 'normal':
708 self._wait_for_ports_bind(ports, share_server)
709 if self._is_neutron_multi_segment(share_network_subnet):
710 # update segment information after port bind
711 super(NeutronBindNetworkPlugin,
712 self)._save_neutron_network_data(context,
713 share_network_subnet)
714 for num, port in enumerate(ports):
715 port_info = {
716 'network_type': share_network_subnet['network_type'],
717 'segmentation_id':
718 share_network_subnet['segmentation_id'],
719 'cidr': share_network_subnet['cidr'],
720 'ip_version': share_network_subnet['ip_version'],
721 }
722 ports[num] = self.db.network_allocation_update(
723 context, port['id'], port_info)
724 return ports
726 @utils.retry(retry_param=exception.NetworkException, retries=20)
727 def _wait_for_network_segment(self, share_server, host):
728 network_id = share_server['share_network_subnet']['neutron_net_id']
729 network = self.neutron_api.get_network(network_id)
730 for segment in network['segments']: 730 ↛ 734line 730 didn't jump to line 734 because the loop on line 730 didn't complete
731 if segment['provider:physical_network'] == (
732 self.config.neutron_physical_net_name):
733 return segment['provider:segmentation_id']
734 msg = _('Network segment not found on host %s') % host
735 raise exception.NetworkException(msg)
737 def extend_network_allocations(self, context, share_server):
738 """Extend network to target host.
740 This will create port bindings on target host without activating them.
741 If network segment does not exist on target host, it will be created.
743 :return: list of port bindings with new segmentation id on target host
744 """
745 vnic_type = self.config.neutron_vnic_type
746 host_id = self.config.neutron_host_id
748 active_port_bindings = (
749 self.db.network_allocations_get_for_share_server(
750 context, share_server['id'], label='user'))
751 if len(active_port_bindings) == 0: 751 ↛ 752line 751 didn't jump to line 752 because the condition on line 751 was never true
752 raise exception.NetworkException(
753 'Can not extend network with no active bindings')
755 # Create port binding on destination backend. It's safe to call neutron
756 # api bind_port_to_host if the port is already bound to destination
757 # host.
758 for port in active_port_bindings:
759 self.neutron_api.bind_port_to_host(port['id'], host_id,
760 vnic_type)
762 # Wait for network segment to be created on destination host.
763 vlan = self._wait_for_network_segment(share_server, host_id)
764 for port in active_port_bindings:
765 port['segmentation_id'] = vlan
767 return active_port_bindings
769 def delete_extended_allocations(self, context, share_server):
770 host_id = self.config.neutron_host_id
771 ports = self.db.network_allocations_get_for_share_server(
772 context, share_server['id'], label='user')
773 for port in ports:
774 try:
775 self.neutron_api.delete_port_binding(port['id'], host_id)
776 except exception.NetworkException as e:
777 msg = 'Failed to delete port binding on port %{port}s: %{err}s'
778 LOG.warning(msg, {'port': port['id'], 'err': e})
780 def cutover_network_allocations(self, context, src_share_server):
781 src_host = share_utils.extract_host(src_share_server['host'], 'host')
782 dest_host = self.config.neutron_host_id
783 ports = self.db.network_allocations_get_for_share_server(
784 context, src_share_server['id'], label='user')
785 for port in ports:
786 self.neutron_api.activate_port_binding(port['id'], dest_host)
787 self.neutron_api.delete_port_binding(port['id'], src_host)
788 return ports
791class NeutronBindSingleNetworkPlugin(NeutronSingleNetworkPlugin,
792 NeutronBindNetworkPlugin):
793 pass