1 # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
|
2 |
|
3 # Copyright (c) 2014, 2016, Oracle and/or its affiliates. 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. |
|
16 # |
|
17 # @author: Girish Moodalbail, Oracle, Inc. |
|
18 |
|
19 import rad.client as radcli |
|
20 import rad.connect as radcon |
|
21 import rad.bindings.com.oracle.solaris.rad.evscntl_1 as evsbind |
|
22 |
|
23 from oslo_concurrency import lockutils |
|
24 from oslo_config import cfg |
|
25 from oslo_db import api as oslo_db_api |
|
26 from oslo_log import log as logging |
|
27 from oslo_utils import importutils |
|
28 |
|
29 from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api |
|
30 from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api |
|
31 from neutron.api.rpc.handlers import dhcp_rpc |
|
32 from neutron.api.rpc.handlers import l3_rpc |
|
33 from neutron.api.rpc.handlers import metadata_rpc |
|
34 from neutron.api.v2 import attributes |
|
35 from neutron.common import constants |
|
36 from neutron.common import exceptions |
|
37 from neutron.common import rpc as n_rpc |
|
38 from neutron.common import topics |
|
39 from neutron.db import agents_db |
|
40 from neutron.db import agentschedulers_db |
|
41 from neutron.db import api as db |
|
42 from neutron.db import db_base_plugin_v2 |
|
43 from neutron.db import external_net_db |
|
44 from neutron.db import l3_agentschedulers_db |
|
45 from neutron.db import l3_attrs_db |
|
46 from neutron.db import l3_gwmode_db |
|
47 from neutron.db import models_v2 |
|
48 from neutron.db import portbindings_db |
|
49 from neutron.db import quota_db |
|
50 from neutron.db import securitygroups_db |
|
51 from neutron.extensions import external_net |
|
52 from neutron.extensions import providernet |
|
53 from neutron.plugins.common import constants as svc_constants |
|
54 from neutron.plugins.ml2 import models |
|
55 |
|
56 LOG = logging.getLogger(__name__) |
|
57 # Only import the vpn server code if it exists. |
|
58 try: |
|
59 sp = cfg.CONF.service_plugins |
|
60 vpns = 'vpnaas' |
|
61 if vpns in sp: |
|
62 try: |
|
63 from neutron_vpnaas.db.vpn import vpn_db |
|
64 LOG.debug("Loading VPNaaS service driver.") |
|
65 except ImportError: |
|
66 pass |
|
67 else: |
|
68 LOG.debug("vpnaas service_plugin not configured") |
|
69 except: |
|
70 pass |
|
71 |
|
72 evs_controller_opts = [ |
|
73 cfg.StrOpt('evs_controller', default='ssh://evsuser@localhost', |
|
74 help=_("An URI that specifies an EVS controller")) |
|
75 ] |
|
76 |
|
77 cfg.CONF.register_opts(evs_controller_opts, "EVS") |
|
78 |
|
79 |
|
80 class EVSControllerError(exceptions.NeutronException): |
|
81 message = _("EVS controller: %(errmsg)s") |
|
82 |
|
83 def __init__(self, evs_errmsg): |
|
84 super(EVSControllerError, self).__init__(errmsg=evs_errmsg) |
|
85 |
|
86 |
|
87 class EVSOpNotSupported(exceptions.NeutronException): |
|
88 message = _("Operation not supported by EVS plugin: %(opname)s") |
|
89 |
|
90 def __init__(self, evs_errmsg): |
|
91 super(EVSOpNotSupported, self).__init__(opname=evs_errmsg) |
|
92 |
|
93 |
|
94 class EVSNotFound(exceptions.NeutronException): |
|
95 message = _("Network %(net_id)s could not be found in EVS") |
|
96 |
|
97 |
|
98 class EVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, |
|
99 agentschedulers_db.DhcpAgentSchedulerDbMixin, |
|
100 external_net_db.External_net_db_mixin, |
|
101 l3_agentschedulers_db.L3AgentSchedulerDbMixin, |
|
102 l3_gwmode_db.L3_NAT_db_mixin): |
|
103 """Implements v2 Neutron Plug-in API specification. |
|
104 |
|
105 All the neutron API calls to create/delete/retrieve Network/Subnet/Port |
|
106 are forwarded to EVS controller through Solaris RAD. The RAD connection |
|
107 to EVS Controller is over SSH. In order that this plugin can communicate |
|
108 with EVS Controller non-interactively and securely, one should setup SSH |
|
109 authentication with pre-shared keys between the host running neutron-server |
|
110 and the host running EVS controller. |
|
111 |
|
112 The following table maps OpenStack Neutron resources and attributes to |
|
113 Solaris Elastic Virtual Switch resources and attributes |
|
114 |
|
115 |---------------------+------------------+------------------------------| |
|
116 | OpenStack Neutron | Solaris EVS | Comments | |
|
117 |---------------------+------------------+------------------------------| |
|
118 | Network | EVS | Represents an isolated L2 | |
|
119 | -- name | -- name | segment; implemented either | |
|
120 | -- id | -- uuid | through VLANs or VXLANs | |
|
121 | -- tenant_id | -- tenant | | |
|
122 | -- shared | Always False | | |
|
123 | -- admin_state_up | Always True | | |
|
124 | -- status | Always ACTIVE | | |
|
125 | -- provider: | | | |
|
126 | network_type | -- l2-type | (either VLAN or VXLAN) | |
|
127 | -- provider: | | | |
|
128 | segmentation_id | -- vlanid/vni | | |
|
129 | | | | |
|
130 | | | | |
|
131 | Subnet | IPnet | An IP network represents | |
|
132 | -- name | -- name | a block of either IPv4 | |
|
133 | -- id | -- uuid | or IPv6 addresses (subnet) | |
|
134 | -- network_id | -- evs | along with a default router | |
|
135 | -- tenant_id | -- tenant | for the block | |
|
136 | -- cidr | -- subnet | | |
|
137 | -- gateway_ip | -- defrouter | | |
|
138 | -- allocation_pools | -- start/stop | | |
|
139 | -- dns_nameservers | -- OpenStack:\ | | |
|
140 | | dns_nameservers | | |
|
141 | -- host_routes | -- OpenStack:\ | | |
|
142 | | host_routes | | |
|
143 | -- enable_dhcp | -- OpenStack:\ | | |
|
144 | | enable_dhcp | | |
|
145 | -- shared | Always False | | |
|
146 | | | | |
|
147 | Port | VPort | A VPort represents the point | |
|
148 | -- name | -- name | of attachment between the | |
|
149 | -- id | -- uuid | VNIC and an EVS. It | |
|
150 | -- network_id | -- evs | encapsulates various network | |
|
151 | -- tenant_id | -- tenant | configuration parameters ( | |
|
152 | -- status | -- status | MAC addresses, IP addresses, | |
|
153 | -- mac_address | -- macaddr | and SLAs) | |
|
154 | -- fixed_ips | -- ipaddr | | |
|
155 | -- device_id | -- OpenStack:\ | | |
|
156 | | device_id | | |
|
157 | -- device_owner | -- OpenStack:\ | | |
|
158 | | device_owner | | |
|
159 | -- security_groups | -- Not Supported | | |
|
160 | -- admin_state_up | Always UP | | |
|
161 |---------------------+------------------+------------------------------| |
|
162 """ |
|
163 |
|
164 _supported_extension_aliases = ["provider", "external-net", "router", |
|
165 "ext-gw-mode", "quotas", "agent", |
|
166 "l3_agent_scheduler", |
|
167 "dhcp_agent_scheduler"] |
|
168 |
|
169 def __init__(self): |
|
170 self.network_scheduler = importutils.import_object( |
|
171 cfg.CONF.network_scheduler_driver |
|
172 ) |
|
173 self.router_scheduler = importutils.import_object( |
|
174 cfg.CONF.router_scheduler_driver |
|
175 ) |
|
176 self._setup_rpc() |
|
177 self._rad_connection = None |
|
178 |
|
179 @property |
|
180 def rad_connection(self): |
|
181 # Since there is no connect_uri() yet, we need to do |
|
182 # parsing of ssh://user@hostname ourselves |
|
183 suh = cfg.CONF.EVS.evs_controller.split('://') |
|
184 if len(suh) != 2 or suh[0] != 'ssh' or not suh[1].strip(): |
|
185 raise SystemExit(_("Specified evs_controller is invalid")) |
|
186 uh = suh[1].split('@') |
|
187 if len(uh) != 2 or not uh[0].strip() or not uh[1].strip(): |
|
188 raise SystemExit(_("'user' and 'hostname' need to be specified " |
|
189 "for evs_controller")) |
|
190 |
|
191 if (self._rad_connection is not None and |
|
192 self._rad_connection._closed is None): |
|
193 return self._rad_connection |
|
194 |
|
195 LOG.debug(_("Connecting to EVS Controller at %s as %s") % |
|
196 (uh[1], uh[0])) |
|
197 self._rad_connection = radcon.connect_ssh(uh[1], user=uh[0]) |
|
198 return self._rad_connection |
|
199 |
|
200 def _setup_rpc(self): |
|
201 # RPC support |
|
202 self.service_topics = {svc_constants.CORE: topics.PLUGIN, |
|
203 svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN} |
|
204 self.conn = n_rpc.create_connection(new=True) |
|
205 self.endpoints = [dhcp_rpc.DhcpRpcCallback(), |
|
206 l3_rpc.L3RpcCallback(), |
|
207 agents_db.AgentExtRpcCallback(), |
|
208 metadata_rpc.MetadataRpcCallback()] |
|
209 for svc_topic in self.service_topics.values(): |
|
210 self.conn.create_consumer(svc_topic, self.endpoints, fanout=False) |
|
211 # Consume from all consumers in a thread |
|
212 self.conn.consume_in_threads() |
|
213 self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() |
|
214 self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotifyAPI() |
|
215 |
|
216 # needed by AgentSchedulerDbMixin() |
|
217 self.agent_notifiers[constants.AGENT_TYPE_DHCP] = \ |
|
218 self.dhcp_agent_notifier |
|
219 self.agent_notifiers[constants.AGENT_TYPE_L3] = \ |
|
220 self.l3_agent_notifier |
|
221 |
|
222 @property |
|
223 def supported_extension_aliases(self): |
|
224 return self._supported_extension_aliases |
|
225 |
|
226 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
227 def _evs_controller_addIPnet(self, tenantname, evsname, ipnetname, |
|
228 propstr): |
|
229 LOG.debug(_("Adding IPnet: %s with properties: %s for tenant: %s " |
|
230 "and for evs: %s") % |
|
231 (ipnetname, propstr, tenantname, evsname)) |
|
232 |
|
233 pat = radcli.ADRGlobPattern({'name': evsname, |
|
234 'tenant': tenantname}) |
|
235 try: |
|
236 evs = self.rad_connection.get_object(evsbind.EVS(), pat) |
|
237 ipnet = evs.addIPnet(propstr, ipnetname) |
|
238 except radcli.ObjectError as oe: |
|
239 raise EVSControllerError(oe.get_payload().errmsg) |
|
240 return ipnet |
|
241 |
|
242 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
243 def _evs_controller_updateIPnet(self, ipnetuuid, propstr): |
|
244 LOG.debug(_("Updating IPnet with id: %s with property string: %s") % |
|
245 (ipnetuuid, propstr)) |
|
246 pat = radcli.ADRGlobPattern({'uuid': ipnetuuid}) |
|
247 try: |
|
248 ipnetlist = self.rad_connection.list_objects(evsbind.IPnet(), pat) |
|
249 if not ipnetlist: |
|
250 return |
|
251 assert len(ipnetlist) == 1 |
|
252 ipnet = self.rad_connection.get_object(ipnetlist[0]) |
|
253 ipnet.setProperty(propstr) |
|
254 except radcli.ObjectError as oe: |
|
255 raise EVSControllerError(oe.get_payload().errmsg) |
|
256 |
|
257 def _subnet_pool_to_evs_pool(self, subnet): |
|
258 poolstr = "" |
|
259 # obtain the optional allocation pool |
|
260 pools = subnet.get('allocation_pools') |
|
261 if not pools or pools is attributes.ATTR_NOT_SPECIFIED: |
|
262 return poolstr |
|
263 |
|
264 for pool in pools: |
|
265 if poolstr: |
|
266 poolstr += "," |
|
267 # if start and end address is same, EVS expects the address |
|
268 # to be provided as-is instead of x.x.x.x-x.x.x.x |
|
269 if pool['start'] == pool['end']: |
|
270 poolstr += pool['start'] |
|
271 else: |
|
272 poolstr += "%s-%s" % (pool['start'], pool['end']) |
|
273 return poolstr |
|
274 |
|
275 def create_subnet(self, context, subnet): |
|
276 """Creates a subnet(IPnet) for a given network(EVS). |
|
277 |
|
278 An IP network represents a block of either IPv4 or IPv6 addresses |
|
279 (i.e., subnet) along with a default router for the block. Only one |
|
280 IPnet can be associated with an EVS. All the zones/VNICs that |
|
281 connect to the EVS, through a VPort, will get an IP address from the |
|
282 IPnet associated with the EVS. |
|
283 """ |
|
284 |
|
285 if (subnet['subnet']['host_routes'] is not |
|
286 attributes.ATTR_NOT_SPECIFIED): |
|
287 raise EVSOpNotSupported(_("setting --host-route for a subnet " |
|
288 "not supported")) |
|
289 |
|
290 poolstr = self._subnet_pool_to_evs_pool(subnet['subnet']) |
|
291 |
|
292 with context.session.begin(subtransactions=True): |
|
293 # create the subnet in the DB |
|
294 db_subnet = super(EVSNeutronPluginV2, self).create_subnet(context, |
|
295 subnet) |
|
296 ipnetname = db_subnet['name'] |
|
297 if not ipnetname: |
|
298 ipnetname = None |
|
299 evsname = db_subnet['network_id'] |
|
300 tenantname = db_subnet['tenant_id'] |
|
301 proplist = ['subnet=%s' % db_subnet['cidr']] |
|
302 defrouter = db_subnet['gateway_ip'] |
|
303 if not defrouter: |
|
304 defrouter = '0.0.0.0' if db_subnet['ip_version'] == 4 else '::' |
|
305 proplist.append('defrouter=%s' % defrouter) |
|
306 proplist.append('uuid=%s' % db_subnet['id']) |
|
307 if poolstr: |
|
308 proplist.append('pool=%s' % (poolstr)) |
|
309 self._evs_controller_addIPnet(tenantname, evsname, ipnetname, |
|
310 ",".join(proplist)) |
|
311 |
|
312 return db_subnet |
|
313 |
|
314 def update_subnet(self, context, id, subnet): |
|
315 LOG.debug(_("Updating Subnet: %s with %s") % (id, subnet)) |
|
316 if (set(subnet['subnet'].keys()) - set(('enable_dhcp', |
|
317 'allocation_pools', |
|
318 'dns_nameservers', |
|
319 'ipv6_address_mode', |
|
320 'ipv6_ra_mode'))): |
|
321 raise EVSOpNotSupported(_("only following subnet attributes " |
|
322 "enable-dhcp, allocation-pool, " |
|
323 "dns-nameserver, ipv6-address-mode, " |
|
324 "and ipv6-ra-mode can be updated")) |
|
325 |
|
326 poolstr = self._subnet_pool_to_evs_pool(subnet['subnet']) |
|
327 |
|
328 with context.session.begin(subtransactions=True): |
|
329 # update subnet in DB |
|
330 retval = super(EVSNeutronPluginV2, self).\ |
|
331 update_subnet(context, id, subnet) |
|
332 # update EVS IPnet with allocation pool info |
|
333 if poolstr: |
|
334 self._evs_controller_updateIPnet(id, "pool=%s" % poolstr) |
|
335 |
|
336 return retval |
|
337 |
|
338 def get_subnet(self, context, id, fields=None): |
|
339 LOG.debug(_("Getting subnet: %s"), id) |
|
340 subnet = super(EVSNeutronPluginV2, self).get_subnet(context, id, None) |
|
341 return self._fields(subnet, fields) |
|
342 |
|
343 def get_subnets(self, context, filters=None, fields=None, |
|
344 sorts=None, limit=None, marker=None, page_reverse=False): |
|
345 subnets = super(EVSNeutronPluginV2, self).\ |
|
346 get_subnets(context, filters, None, sorts, limit, marker, |
|
347 page_reverse) |
|
348 return [self._fields(subnet, fields) for subnet in subnets] |
|
349 |
|
350 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
351 def _evs_controller_removeIPnet(self, tenantname, evsname, ipnetuuid, |
|
352 auto_created_ports): |
|
353 LOG.debug(_("Removing IPnet with id: %s for tenant: %s for evs: %s") % |
|
354 (ipnetuuid, tenantname, evsname)) |
|
355 pat = radcli.ADRGlobPattern({'name': evsname, 'tenant': tenantname}) |
|
356 try: |
|
357 evs = self.rad_connection.get_object(evsbind.EVS(), pat) |
|
358 if auto_created_ports: |
|
359 LOG.debug(_("Need to remove following ports %s before " |
|
360 "removing the IPnet") % (auto_created_ports)) |
|
361 for port in auto_created_ports: |
|
362 try: |
|
363 evs.removeVPort(port['id'], "force=yes") |
|
364 except radcli.ObjectError as oe: |
|
365 # '43' corresponds to EVS' EVS_ENOENT_VPORT error code |
|
366 if oe.get_payload().err == 43: |
|
367 LOG.debug(_("VPort %s could not be found") % |
|
368 (port['id'])) |
|
369 evs.removeIPnet(ipnetuuid) |
|
370 except (radcli.NotFoundError, radcli.ObjectError) as oe: |
|
371 # '42' corresponds to EVS' EVS_ENOENT_IPNET error code |
|
372 if oe.get_payload() is None or oe.get_payload().err == 42: |
|
373 # EVS doesn't have that IPnet, return success to delete |
|
374 # the IPnet from Neutron DB. |
|
375 LOG.debug(_("IPnet could not be found in EVS.")) |
|
376 return |
|
377 raise EVSControllerError(oe.get_payload().errmsg) |
|
378 |
|
379 def delete_subnet(self, context, id): |
|
380 subnet = self.get_subnet(context, id) |
|
381 if not subnet: |
|
382 return |
|
383 |
|
384 with context.session.begin(subtransactions=True): |
|
385 # get a list of ports automatically created by Neutron |
|
386 auto_created_ports = context.session.query(models_v2.Port).\ |
|
387 filter(models_v2.Port.device_owner. |
|
388 in_(db_base_plugin_v2.AUTO_DELETE_PORT_OWNERS)).all() |
|
389 # delete subnet in DB |
|
390 super(EVSNeutronPluginV2, self).delete_subnet(context, id) |
|
391 self._evs_controller_removeIPnet(subnet['tenant_id'], |
|
392 subnet['network_id'], id, |
|
393 auto_created_ports) |
|
394 |
|
395 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
396 def _evs_controller_createEVS(self, tenantname, evsname, propstr): |
|
397 LOG.debug(_("Adding EVS: %s with properties: %s for tenant: %s") % |
|
398 (evsname, propstr, tenantname)) |
|
399 try: |
|
400 evs = self.rad_connection.\ |
|
401 get_object(evsbind.EVSController()).\ |
|
402 createEVS(propstr, tenantname, evsname) |
|
403 except radcli.ObjectError as oe: |
|
404 raise EVSControllerError(oe.get_payload().errmsg) |
|
405 return evs |
|
406 |
|
407 def _extend_network_dict(self, network, evs): |
|
408 for prop in evs.props: |
|
409 if prop.name == 'l2-type': |
|
410 network[providernet.NETWORK_TYPE] = prop.value |
|
411 elif prop.name == 'vlanid' or prop.name == 'vni': |
|
412 network[providernet.SEGMENTATION_ID] = int(prop.value) |
|
413 |
|
414 def create_network(self, context, network): |
|
415 """Creates a network(EVS) for a given tenant. |
|
416 |
|
417 An Elastic Virtual Switch (EVS) is a virtual switch that spans |
|
418 one or more servers (physical machines). It represents an isolated L2 |
|
419 segment, and the isolation is implemented either through VLANs or |
|
420 VXLANs. An EVS provides network connectivity between the Virtual |
|
421 Machines connected to it. There are two main resources associated with |
|
422 an EVS: IPnet and VPort. |
|
423 """ |
|
424 |
|
425 if network['network']['admin_state_up'] is False: |
|
426 raise EVSOpNotSupported(_("setting admin_state_up=False for a " |
|
427 "network not supported")) |
|
428 |
|
429 if network['network']['shared'] is True: |
|
430 raise EVSOpNotSupported(_("setting shared=True for a " |
|
431 "network not supported")) |
|
432 |
|
433 evsname = network['network']['name'] |
|
434 if not evsname: |
|
435 evsname = None |
|
436 |
|
437 tenantname = self._get_tenant_id_for_create(context, |
|
438 network['network']) |
|
439 proplist = [] |
|
440 network_type = network['network'][providernet.NETWORK_TYPE] |
|
441 if attributes.is_attr_set(network_type): |
|
442 proplist.append('l2-type=%s' % network_type) |
|
443 |
|
444 segment_id = network['network'][providernet.SEGMENTATION_ID] |
|
445 if attributes.is_attr_set(segment_id): |
|
446 if (not attributes.is_attr_set(network_type) or |
|
447 len(network_type) == 0): |
|
448 raise EVSControllerError(_("provider:network_type must be " |
|
449 "specified when provider:" |
|
450 "segmentation_id is provided")) |
|
451 |
|
452 if network_type == 'vxlan': |
|
453 proplist.append('vni=%d' % segment_id) |
|
454 elif network_type == 'vlan': |
|
455 proplist.append('vlanid=%d' % segment_id) |
|
456 else: |
|
457 raise EVSControllerError(_("specified " |
|
458 "provider:network_type '%s' not " |
|
459 "supported") % network_type) |
|
460 |
|
461 propstr = None |
|
462 if proplist: |
|
463 propstr = ",".join(proplist) |
|
464 |
|
465 with context.session.begin(subtransactions=True): |
|
466 # create the network in DB |
|
467 net = super(EVSNeutronPluginV2, self).create_network(context, |
|
468 network) |
|
469 self._process_l3_create(context, net, network['network']) |
|
470 # if --router:external is not set, the above function does |
|
471 # not update net with router:external set to False |
|
472 if net.get(external_net.EXTERNAL) is None: |
|
473 net[external_net.EXTERNAL] = False |
|
474 |
|
475 # create EVS on the EVS controller |
|
476 if propstr: |
|
477 propstr += ",uuid=%s" % net['id'] |
|
478 else: |
|
479 propstr = "uuid=%s" % net['id'] |
|
480 evs = self._evs_controller_createEVS(tenantname, evsname, propstr) |
|
481 |
|
482 # add provider information into net |
|
483 self._extend_network_dict(net, evs) |
|
484 |
|
485 return net |
|
486 |
|
487 def update_network(self, context, id, network): |
|
488 raise EVSOpNotSupported(_("net-update")) |
|
489 |
|
490 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
491 def _evs_controller_getEVS(self, evsuuid): |
|
492 LOG.debug(_("Getting EVS: %s"), evsuuid) |
|
493 try: |
|
494 evslist = self.rad_connection.\ |
|
495 get_object(evsbind.EVSController()).\ |
|
496 getEVSInfo('evs=%s' % evsuuid) |
|
497 except radcli.ObjectError as oe: |
|
498 raise EVSControllerError(oe.getpayload().errmsg) |
|
499 if not evslist: |
|
500 LOG.error(_("EVS framework does not have Neutron network " |
|
501 "'%s' defined"), evsuuid) |
|
502 return None |
|
503 return evslist[0] |
|
504 |
|
505 def get_network(self, context, id, fields=None): |
|
506 with context.session.begin(subtransactions=True): |
|
507 net = super(EVSNeutronPluginV2, self).get_network(context, |
|
508 id, None) |
|
509 # call EVS controller to get provider network information |
|
510 evs = self._evs_controller_getEVS(net['id']) |
|
511 if evs: |
|
512 self._extend_network_dict(net, evs) |
|
513 return self._fields(net, fields) |
|
514 |
|
515 def get_networks(self, context, filters=None, fields=None, |
|
516 sorts=None, limit=None, marker=None, page_reverse=False): |
|
517 |
|
518 with context.session.begin(subtransactions=True): |
|
519 nets = super(EVSNeutronPluginV2, self).\ |
|
520 get_networks(context, filters, None, sorts, limit, marker, |
|
521 page_reverse) |
|
522 for net in nets: |
|
523 evs = self._evs_controller_getEVS(net['id']) |
|
524 if evs: |
|
525 self._extend_network_dict(net, evs) |
|
526 return [self._fields(net, fields) for net in nets] |
|
527 |
|
528 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
529 def _evs_controller_deleteEVS(self, tenantname, evsuuid): |
|
530 LOG.debug(_("Removing EVS with id: %s for tenant: %s") % |
|
531 (evsuuid, tenantname)) |
|
532 try: |
|
533 self.rad_connection.\ |
|
534 get_object(evsbind.EVSController()).\ |
|
535 deleteEVS(evsuuid, tenantname) |
|
536 except (radcli.NotFoundError, radcli.ObjectError) as oe: |
|
537 # '41' corresponds to EVS' EVS_ENOENT_EVS error code |
|
538 if oe.get_payload() is None or oe.get_payload().err == 41: |
|
539 # EVS doesn't have that EVS, return success to delete |
|
540 # the EVS from Neutron DB. |
|
541 LOG.debug(_("EVS could not be found in EVS backend.")) |
|
542 return |
|
543 raise EVSControllerError(oe.get_payload().errmsg) |
|
544 |
|
545 def delete_network(self, context, id): |
|
546 with context.session.begin(subtransactions=True): |
|
547 network = self._get_network(context, id) |
|
548 |
|
549 qry_network_ports = context.session.query(models_v2.Port).\ |
|
550 filter_by(network_id=id).filter(models_v2.Port.device_owner. |
|
551 in_(db_base_plugin_v2. |
|
552 AUTO_DELETE_PORT_OWNERS)) |
|
553 |
|
554 auto_created_ports = qry_network_ports.all() |
|
555 qry_network_ports.delete(synchronize_session=False) |
|
556 |
|
557 port_in_use = context.session.query(models_v2.Port).filter_by( |
|
558 network_id=id).first() |
|
559 |
|
560 if port_in_use: |
|
561 raise exceptions.NetworkInUse(net_id=id) |
|
562 |
|
563 # clean up subnets |
|
564 subnets = self._get_subnets_by_network(context, id) |
|
565 for subnet in subnets: |
|
566 super(EVSNeutronPluginV2, self).delete_subnet(context, |
|
567 subnet['id']) |
|
568 self._evs_controller_removeIPnet(subnet['tenant_id'], |
|
569 subnet['network_id'], |
|
570 subnet['id'], |
|
571 auto_created_ports) |
|
572 |
|
573 context.session.delete(network) |
|
574 self._evs_controller_deleteEVS(network['tenant_id'], id) |
|
575 |
|
576 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
577 def _evs_controller_addVPort(self, tenantname, evsname, vportname, |
|
578 propstr): |
|
579 LOG.debug(_("Adding VPort: %s with properties: %s for tenant: %s " |
|
580 "and for evs: %s") % |
|
581 (vportname, propstr, tenantname, evsname)) |
|
582 |
|
583 try: |
|
584 pat = radcli.ADRGlobPattern({'name': evsname, |
|
585 'tenant': tenantname}) |
|
586 evs = self.rad_connection.get_object(evsbind.EVS(), pat) |
|
587 vport = evs.addVPort(propstr, vportname) |
|
588 except radcli.ObjectError as oe: |
|
589 raise EVSControllerError(oe.get_payload().errmsg) |
|
590 return vport |
|
591 |
|
592 @oslo_db_api.wrap_db_retry(max_retries=db.MAX_RETRIES, |
|
593 retry_on_request=True, |
|
594 retry_on_deadlock=True) |
|
595 def create_port(self, context, port): |
|
596 """Creates a port(VPort) for a given network(EVS). |
|
597 |
|
598 A VPort represents the point of attachment between the VNIC and an |
|
599 EVS. It encapsulates various network configuration parameters such as |
|
600 -- SLAs (maxbw, cos, and priority) |
|
601 -- IP address and |
|
602 -- MAC address, et al |
|
603 This configuration is inherited by the VNIC when it connects to the |
|
604 VPort. |
|
605 """ |
|
606 if port['port']['admin_state_up'] is False: |
|
607 raise EVSOpNotSupported(_("setting admin_state_up=False for a " |
|
608 "port not supported")) |
|
609 |
|
610 with context.session.begin(subtransactions=True): |
|
611 # for external gateway ports and floating ips, tenant_id |
|
612 # is not set, but EVS does not like it. |
|
613 tenant_id = self._get_tenant_id_for_create(context, port['port']) |
|
614 if not tenant_id: |
|
615 network = self._get_network(context, |
|
616 port['port']['network_id']) |
|
617 port['port']['tenant_id'] = network['tenant_id'] |
|
618 # create the port in the DB |
|
619 db_port = super(EVSNeutronPluginV2, self).create_port(context, |
|
620 port) |
|
621 # Neutron allows to create a port on a network that doesn't |
|
622 # yet have subnet associated with it, however EVS doesn't |
|
623 # support this. |
|
624 if not db_port['fixed_ips']: |
|
625 raise EVSOpNotSupported(_("creating a port on a network that " |
|
626 "does not yet have subnet " |
|
627 "associated with it is not " |
|
628 "supported")) |
|
629 tenantname = db_port['tenant_id'] |
|
630 vportname = db_port['name'] |
|
631 if not vportname: |
|
632 vportname = None |
|
633 evs_id = db_port['network_id'] |
|
634 proplist = ['macaddr=%s' % db_port['mac_address']] |
|
635 proplist.append('ipaddr=%s' % |
|
636 db_port['fixed_ips'][0].get('ip_address')) |
|
637 proplist.append('uuid=%s' % db_port['id']) |
|
638 |
|
639 self._evs_controller_addVPort(tenantname, evs_id, vportname, |
|
640 ",".join(proplist)) |
|
641 return db_port |
|
642 |
|
643 def update_port(self, context, id, port): |
|
644 # EVS does not allow updating certain attributes, so check for it |
|
645 state = port['port'].get('admin_state_up') |
|
646 if state and state is False: |
|
647 raise EVSOpNotSupported(_("updating port's admin_state_up to " |
|
648 "False is not supported")) |
|
649 |
|
650 # Get the original port and fail if any attempt is being made |
|
651 # to change fixed_ips of the port since EVS doesn't support it |
|
652 original_port = super(EVSNeutronPluginV2, self).get_port(context, id) |
|
653 original_ips = original_port['fixed_ips'] |
|
654 update_ips = port['port'].get('fixed_ips') |
|
655 if (update_ips and |
|
656 (len(update_ips) != 1 or |
|
657 update_ips[0]['subnet_id'] != original_ips[0]['subnet_id'] or |
|
658 update_ips[0]['ip_address'] != original_ips[0]['ip_address'])): |
|
659 raise EVSOpNotSupported(_("updating port's fixed_ips " |
|
660 "is not supported")) |
|
661 LOG.debug(_("Updating port %s with %s") % (id, port)) |
|
662 db_port = super(EVSNeutronPluginV2, self).update_port(context, |
|
663 id, port) |
|
664 return db_port |
|
665 |
|
666 def get_port(self, context, id, fields=None): |
|
667 LOG.debug(_("Getting port: %s"), id) |
|
668 port = super(EVSNeutronPluginV2, self).get_port(context, id, None) |
|
669 return self._fields(port, fields) |
|
670 |
|
671 def get_ports(self, context, filters=None, fields=None, |
|
672 sorts=None, limit=None, marker=None, page_reverse=False): |
|
673 ports = super(EVSNeutronPluginV2, self).\ |
|
674 get_ports(context, filters, None, sorts, limit, marker, |
|
675 page_reverse) |
|
676 return [self._fields(port, fields) for port in ports] |
|
677 |
|
678 @lockutils.synchronized('evs-plugin', 'neutron-') |
|
679 def _evs_controller_removeVPort(self, tenantname, evsname, vportuuid): |
|
680 LOG.debug(_("Removing VPort with id: %s for tenant: %s for evs: %s") % |
|
681 (vportuuid, tenantname, evsname)) |
|
682 pat = radcli.ADRGlobPattern({'name': evsname, |
|
683 'tenant': tenantname}) |
|
684 try: |
|
685 evs = self.rad_connection.get_object(evsbind.EVS(), pat) |
|
686 evs.removeVPort(vportuuid, "force=yes") |
|
687 except (radcli.NotFoundError, radcli.ObjectError) as oe: |
|
688 # '43' corresponds to EVS' EVS_ENOENT_VPORT error code |
|
689 if oe.get_payload() is None or oe.get_payload().err == 43: |
|
690 # EVS doesn't have that VPort, return success to delete |
|
691 # the VPort from Neutron DB. |
|
692 LOG.debug(_("VPort could not be found in EVS.")) |
|
693 else: |
|
694 raise EVSControllerError(oe.get_payload().errmsg) |
|
695 |
|
696 def delete_port(self, context, id, l3_port_check=True): |
|
697 if l3_port_check: |
|
698 self.prevent_l3_port_deletion(context, id) |
|
699 self.disassociate_floatingips(context, id) |
|
700 port = self.get_port(context, id) |
|
701 if not port: |
|
702 return |
|
703 with context.session.begin(subtransactions=True): |
|
704 super(EVSNeutronPluginV2, self).delete_port(context, id) |
|
705 self._evs_controller_removeVPort(port['tenant_id'], |
|
706 port['network_id'], |
|
707 port['id']) |
|