nat support for neutron
[plstackapi.git] / planetstack / neutron_extension / 1:2013.2.2-0ubuntu1~cloud0 / ovs_neutron_plugin.py
1 # vim: tabstop=4 shiftwidth=4 softtabstop=4
2 # Copyright 2011 Nicira Networks, 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.
16 # @author: Somik Behera, Nicira Networks, Inc.
17 # @author: Brad Hall, Nicira Networks, Inc.
18 # @author: Dan Wendlandt, Nicira Networks, Inc.
19 # @author: Dave Lapsley, Nicira Networks, Inc.
20 # @author: Aaron Rosen, Nicira Networks, Inc.
21 # @author: Bob Kukura, Red Hat, Inc.
22 # @author: Seetharama Ayyadevara, Freescale Semiconductor, Inc.
23
24 import sys
25
26 from oslo.config import cfg
27
28 from neutron.agent import securitygroups_rpc as sg_rpc
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.v2 import attributes
32 from neutron.common import constants as q_const
33 from neutron.common import exceptions as q_exc
34 from neutron.common import rpc as q_rpc
35 from neutron.common import topics
36 from neutron.common import utils
37 from neutron.db import agents_db
38 from neutron.db import agentschedulers_db
39 from neutron.db import allowedaddresspairs_db as addr_pair_db
40 from neutron.db import db_base_plugin_v2
41 from neutron.db import dhcp_rpc_base
42 from neutron.db import external_net_db
43 from neutron.db import extradhcpopt_db
44 from neutron.db import extraroute_db
45 from neutron.db import l3_agentschedulers_db
46 from neutron.db import l3_gwmode_db
47 from neutron.db import l3_rpc_base
48 from neutron.db import portbindings_db
49 from neutron.db import quota_db  # noqa
50 from neutron.db import securitygroups_rpc_base as sg_db_rpc
51 from neutron.extensions import allowedaddresspairs as addr_pair
52 from neutron.extensions import extra_dhcp_opt as edo_ext
53 from neutron.extensions import portbindings
54 from neutron.extensions import providernet as provider
55 from neutron.extensions import nat
56 from neutron import manager
57 from neutron.openstack.common import importutils
58 from neutron.openstack.common import log as logging
59 from neutron.openstack.common import rpc
60 from neutron.openstack.common.rpc import proxy
61 from neutron.plugins.common import constants as svc_constants
62 from neutron.plugins.common import utils as plugin_utils
63 from neutron.plugins.openvswitch.common import config  # noqa
64 from neutron.plugins.openvswitch.common import constants
65 from neutron.plugins.openvswitch import ovs_db_v2
66
67
68 LOG = logging.getLogger(__name__)
69
70
71 class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
72                       l3_rpc_base.L3RpcCallbackMixin,
73                       sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
74
75     # history
76     #   1.0 Initial version
77     #   1.1 Support Security Group RPC
78
79     RPC_API_VERSION = '1.1'
80
81     def __init__(self, notifier, tunnel_type):
82         self.notifier = notifier
83         self.tunnel_type = tunnel_type
84
85     def create_rpc_dispatcher(self):
86         '''Get the rpc dispatcher for this manager.
87
88         If a manager would like to set an rpc API version, or support more than
89         one class as the target of rpc messages, override this method.
90         '''
91         return q_rpc.PluginRpcDispatcher([self,
92                                           agents_db.AgentExtRpcCallback()])
93
94     @classmethod
95     def get_port_from_device(cls, device):
96         port = ovs_db_v2.get_port_from_device(device)
97         if port:
98             port['device'] = device
99         return port
100
101     def get_device_details(self, rpc_context, **kwargs):
102         """Agent requests device details."""
103         agent_id = kwargs.get('agent_id')
104         device = kwargs.get('device')
105         LOG.debug(_("Device %(device)s details requested from %(agent_id)s"),
106                   {'device': device, 'agent_id': agent_id})
107         port = ovs_db_v2.get_port(device)
108         if port:
109             binding = ovs_db_v2.get_network_binding(None, port['network_id'])
110             entry = {'device': device,
111                      'network_id': port['network_id'],
112                      'port_id': port['id'],
113                      'admin_state_up': port['admin_state_up'],
114                      'network_type': binding.network_type,
115                      'segmentation_id': binding.segmentation_id,
116                      'physical_network': binding.physical_network}
117             new_status = (q_const.PORT_STATUS_ACTIVE if port['admin_state_up']
118                           else q_const.PORT_STATUS_DOWN)
119             if port['status'] != new_status:
120                 ovs_db_v2.set_port_status(port['id'], new_status)
121         else:
122             entry = {'device': device}
123             LOG.debug(_("%s can not be found in database"), device)
124         return entry
125
126     def update_device_down(self, rpc_context, **kwargs):
127         """Device no longer exists on agent."""
128         agent_id = kwargs.get('agent_id')
129         device = kwargs.get('device')
130         host = kwargs.get('host')
131         port = ovs_db_v2.get_port(device)
132         LOG.debug(_("Device %(device)s no longer exists on %(agent_id)s"),
133                   {'device': device, 'agent_id': agent_id})
134         if port:
135             entry = {'device': device,
136                      'exists': True}
137             plugin = manager.NeutronManager.get_plugin()
138             if (host and
139                 not plugin.get_port_host(rpc_context, port['id']) == host):
140                 LOG.debug(_("Device %(device)s not bound to the"
141                             " agent host %(host)s"),
142                           {'device': device, 'host': host})
143             elif port['status'] != q_const.PORT_STATUS_DOWN:
144                 # Set port status to DOWN
145                 ovs_db_v2.set_port_status(port['id'],
146                                           q_const.PORT_STATUS_DOWN)
147         else:
148             entry = {'device': device,
149                      'exists': False}
150             LOG.debug(_("%s can not be found in database"), device)
151         return entry
152
153     def update_device_up(self, rpc_context, **kwargs):
154         """Device is up on agent."""
155         agent_id = kwargs.get('agent_id')
156         device = kwargs.get('device')
157         host = kwargs.get('host')
158         port = ovs_db_v2.get_port(device)
159         LOG.debug(_("Device %(device)s up on %(agent_id)s"),
160                   {'device': device, 'agent_id': agent_id})
161         plugin = manager.NeutronManager.get_plugin()
162         if port:
163             if (host and
164                 not plugin.get_port_host(rpc_context, port['id']) == host):
165                 LOG.debug(_("Device %(device)s not bound to the"
166                             " agent host %(host)s"),
167                           {'device': device, 'host': host})
168                 return
169             elif port['status'] != q_const.PORT_STATUS_ACTIVE:
170                 ovs_db_v2.set_port_status(port['id'],
171                                           q_const.PORT_STATUS_ACTIVE)
172         else:
173             LOG.debug(_("%s can not be found in database"), device)
174
175     def tunnel_sync(self, rpc_context, **kwargs):
176         """Update new tunnel.
177
178         Updates the datbase with the tunnel IP. All listening agents will also
179         be notified about the new tunnel IP.
180         """
181         tunnel_ip = kwargs.get('tunnel_ip')
182         # Update the database with the IP
183         tunnel = ovs_db_v2.add_tunnel_endpoint(tunnel_ip)
184         tunnels = ovs_db_v2.get_tunnel_endpoints()
185         entry = dict()
186         entry['tunnels'] = tunnels
187         # Notify all other listening agents
188         self.notifier.tunnel_update(rpc_context, tunnel.ip_address,
189                                     tunnel.id, self.tunnel_type)
190         # Return the list of tunnels IP's to the agent
191         return entry
192
193
194 class AgentNotifierApi(proxy.RpcProxy,
195                        sg_rpc.SecurityGroupAgentRpcApiMixin):
196     '''Agent side of the openvswitch rpc API.
197
198     API version history:
199         1.0 - Initial version.
200
201     '''
202
203     BASE_RPC_API_VERSION = '1.0'
204
205     def __init__(self, topic):
206         super(AgentNotifierApi, self).__init__(
207             topic=topic, default_version=self.BASE_RPC_API_VERSION)
208         self.topic_network_delete = topics.get_topic_name(topic,
209                                                           topics.NETWORK,
210                                                           topics.DELETE)
211         self.topic_port_update = topics.get_topic_name(topic,
212                                                        topics.PORT,
213                                                        topics.UPDATE)
214         self.topic_tunnel_update = topics.get_topic_name(topic,
215                                                          constants.TUNNEL,
216                                                          topics.UPDATE)
217
218     def network_delete(self, context, network_id):
219         self.fanout_cast(context,
220                          self.make_msg('network_delete',
221                                        network_id=network_id),
222                          topic=self.topic_network_delete)
223
224     def port_update(self, context, port, network_type, segmentation_id,
225                     physical_network):
226         self.fanout_cast(context,
227                          self.make_msg('port_update',
228                                        port=port,
229                                        network_type=network_type,
230                                        segmentation_id=segmentation_id,
231                                        physical_network=physical_network),
232                          topic=self.topic_port_update)
233
234     def tunnel_update(self, context, tunnel_ip, tunnel_id, tunnel_type):
235         self.fanout_cast(context,
236                          self.make_msg('tunnel_update',
237                                        tunnel_ip=tunnel_ip,
238                                        tunnel_id=tunnel_id,
239                                        tunnel_type=tunnel_type),
240                          topic=self.topic_tunnel_update)
241
242
243 class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
244                          external_net_db.External_net_db_mixin,
245                          extraroute_db.ExtraRoute_db_mixin,
246                          l3_gwmode_db.L3_NAT_db_mixin,
247                          sg_db_rpc.SecurityGroupServerRpcMixin,
248                          l3_agentschedulers_db.L3AgentSchedulerDbMixin,
249                          agentschedulers_db.DhcpAgentSchedulerDbMixin,
250                          portbindings_db.PortBindingMixin,
251                          extradhcpopt_db.ExtraDhcpOptMixin,
252                          addr_pair_db.AllowedAddressPairsMixin):
253
254     """Implement the Neutron abstractions using Open vSwitch.
255
256     Depending on whether tunneling is enabled, either a GRE, VXLAN tunnel or
257     a new VLAN is created for each network. An agent is relied upon to
258     perform the actual OVS configuration on each host.
259
260     The provider extension is also supported. As discussed in
261     https://bugs.launchpad.net/neutron/+bug/1023156, this class could
262     be simplified, and filtering on extended attributes could be
263     handled, by adding support for extended attributes to the
264     NeutronDbPluginV2 base class. When that occurs, this class should
265     be updated to take advantage of it.
266
267     The port binding extension enables an external application relay
268     information to and from the plugin.
269     """
270
271     # This attribute specifies whether the plugin supports or not
272     # bulk/pagination/sorting operations. Name mangling is used in
273     # order to ensure it is qualified by class
274     __native_bulk_support = True
275     __native_pagination_support = True
276     __native_sorting_support = True
277
278     _supported_extension_aliases = ["provider", "external-net", "router",
279                                     "ext-gw-mode", "binding", "quotas",
280                                     "security-group", "agent", "extraroute",
281                                     "l3_agent_scheduler",
282                                     "dhcp_agent_scheduler",
283                                     "extra_dhcp_opt",
284                                     "allowed-address-pairs",
285                                     "nat"]
286
287     @property
288     def supported_extension_aliases(self):
289         if not hasattr(self, '_aliases'):
290             aliases = self._supported_extension_aliases[:]
291             sg_rpc.disable_security_group_extension_if_noop_driver(aliases)
292             self._aliases = aliases
293         return self._aliases
294
295     def __init__(self, configfile=None):
296         self.base_binding_dict = {
297             portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
298             portbindings.CAPABILITIES: {
299                 portbindings.CAP_PORT_FILTER:
300                 'security-group' in self.supported_extension_aliases}}
301         ovs_db_v2.initialize()
302         self._parse_network_vlan_ranges()
303         ovs_db_v2.sync_vlan_allocations(self.network_vlan_ranges)
304         self.tenant_network_type = cfg.CONF.OVS.tenant_network_type
305         if self.tenant_network_type not in [constants.TYPE_LOCAL,
306                                             constants.TYPE_VLAN,
307                                             constants.TYPE_GRE,
308                                             constants.TYPE_VXLAN,
309                                             constants.TYPE_NONE]:
310             LOG.error(_("Invalid tenant_network_type: %s. "
311                       "Server terminated!"),
312                       self.tenant_network_type)
313             sys.exit(1)
314         self.enable_tunneling = cfg.CONF.OVS.enable_tunneling
315         self.tunnel_type = None
316         if self.enable_tunneling:
317             self.tunnel_type = cfg.CONF.OVS.tunnel_type or constants.TYPE_GRE
318         elif cfg.CONF.OVS.tunnel_type:
319             self.tunnel_type = cfg.CONF.OVS.tunnel_type
320             self.enable_tunneling = True
321         self.tunnel_id_ranges = []
322         if self.enable_tunneling:
323             self._parse_tunnel_id_ranges()
324             ovs_db_v2.sync_tunnel_allocations(self.tunnel_id_ranges)
325         elif self.tenant_network_type in constants.TUNNEL_NETWORK_TYPES:
326             LOG.error(_("Tunneling disabled but tenant_network_type is '%s'. "
327                       "Server terminated!"), self.tenant_network_type)
328             sys.exit(1)
329         self.setup_rpc()
330         self.network_scheduler = importutils.import_object(
331             cfg.CONF.network_scheduler_driver
332         )
333         self.router_scheduler = importutils.import_object(
334             cfg.CONF.router_scheduler_driver
335         )
336
337     def setup_rpc(self):
338         # RPC support
339         self.service_topics = {svc_constants.CORE: topics.PLUGIN,
340                                svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN}
341         self.conn = rpc.create_connection(new=True)
342         self.notifier = AgentNotifierApi(topics.AGENT)
343         self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
344             dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
345         )
346         self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
347             l3_rpc_agent_api.L3AgentNotify
348         )
349         self.callbacks = OVSRpcCallbacks(self.notifier, self.tunnel_type)
350         self.dispatcher = self.callbacks.create_rpc_dispatcher()
351         for svc_topic in self.service_topics.values():
352             self.conn.create_consumer(svc_topic, self.dispatcher, fanout=False)
353         # Consume from all consumers in a thread
354         self.conn.consume_in_thread()
355
356     def _parse_network_vlan_ranges(self):
357         try:
358             self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
359                 cfg.CONF.OVS.network_vlan_ranges)
360         except Exception as ex:
361             LOG.error(_("%s. Server terminated!"), ex)
362             sys.exit(1)
363         LOG.info(_("Network VLAN ranges: %s"), self.network_vlan_ranges)
364
365     def _parse_tunnel_id_ranges(self):
366         for entry in cfg.CONF.OVS.tunnel_id_ranges:
367             entry = entry.strip()
368             try:
369                 tun_min, tun_max = entry.split(':')
370                 self.tunnel_id_ranges.append((int(tun_min), int(tun_max)))
371             except ValueError as ex:
372                 LOG.error(_("Invalid tunnel ID range: "
373                             "'%(range)s' - %(e)s. Server terminated!"),
374                           {'range': entry, 'e': ex})
375                 sys.exit(1)
376         LOG.info(_("Tunnel ID ranges: %s"), self.tunnel_id_ranges)
377
378     def _extend_network_dict_provider(self, context, network):
379         binding = ovs_db_v2.get_network_binding(context.session,
380                                                 network['id'])
381         network[provider.NETWORK_TYPE] = binding.network_type
382         if binding.network_type in constants.TUNNEL_NETWORK_TYPES:
383             network[provider.PHYSICAL_NETWORK] = None
384             network[provider.SEGMENTATION_ID] = binding.segmentation_id
385         elif binding.network_type == constants.TYPE_FLAT:
386             network[provider.PHYSICAL_NETWORK] = binding.physical_network
387             network[provider.SEGMENTATION_ID] = None
388         elif binding.network_type == constants.TYPE_VLAN:
389             network[provider.PHYSICAL_NETWORK] = binding.physical_network
390             network[provider.SEGMENTATION_ID] = binding.segmentation_id
391         elif binding.network_type == constants.TYPE_LOCAL:
392             network[provider.PHYSICAL_NETWORK] = None
393             network[provider.SEGMENTATION_ID] = None
394
395     def _process_provider_create(self, context, attrs):
396         network_type = attrs.get(provider.NETWORK_TYPE)
397         physical_network = attrs.get(provider.PHYSICAL_NETWORK)
398         segmentation_id = attrs.get(provider.SEGMENTATION_ID)
399
400         network_type_set = attributes.is_attr_set(network_type)
401         physical_network_set = attributes.is_attr_set(physical_network)
402         segmentation_id_set = attributes.is_attr_set(segmentation_id)
403
404         if not (network_type_set or physical_network_set or
405                 segmentation_id_set):
406             return (None, None, None)
407
408         if not network_type_set:
409             msg = _("provider:network_type required")
410             raise q_exc.InvalidInput(error_message=msg)
411         elif network_type == constants.TYPE_FLAT:
412             if segmentation_id_set:
413                 msg = _("provider:segmentation_id specified for flat network")
414                 raise q_exc.InvalidInput(error_message=msg)
415             else:
416                 segmentation_id = constants.FLAT_VLAN_ID
417         elif network_type == constants.TYPE_VLAN:
418             if not segmentation_id_set:
419                 msg = _("provider:segmentation_id required")
420                 raise q_exc.InvalidInput(error_message=msg)
421             if not utils.is_valid_vlan_tag(segmentation_id):
422                 msg = (_("provider:segmentation_id out of range "
423                          "(%(min_id)s through %(max_id)s)") %
424                        {'min_id': q_const.MIN_VLAN_TAG,
425                         'max_id': q_const.MAX_VLAN_TAG})
426                 raise q_exc.InvalidInput(error_message=msg)
427         elif network_type in constants.TUNNEL_NETWORK_TYPES:
428             if not self.enable_tunneling:
429                 msg = _("%s networks are not enabled") % network_type
430                 raise q_exc.InvalidInput(error_message=msg)
431             if physical_network_set:
432                 msg = _("provider:physical_network specified for %s "
433                         "network") % network_type
434                 raise q_exc.InvalidInput(error_message=msg)
435             else:
436                 physical_network = None
437             if not segmentation_id_set:
438                 msg = _("provider:segmentation_id required")
439                 raise q_exc.InvalidInput(error_message=msg)
440         elif network_type == constants.TYPE_LOCAL:
441             if physical_network_set:
442                 msg = _("provider:physical_network specified for local "
443                         "network")
444                 raise q_exc.InvalidInput(error_message=msg)
445             else:
446                 physical_network = None
447             if segmentation_id_set:
448                 msg = _("provider:segmentation_id specified for local "
449                         "network")
450                 raise q_exc.InvalidInput(error_message=msg)
451             else:
452                 segmentation_id = None
453         else:
454             msg = _("provider:network_type %s not supported") % network_type
455             raise q_exc.InvalidInput(error_message=msg)
456
457         if network_type in [constants.TYPE_VLAN, constants.TYPE_FLAT]:
458             if physical_network_set:
459                 if physical_network not in self.network_vlan_ranges:
460                     msg = _("Unknown provider:physical_network "
461                             "%s") % physical_network
462                     raise q_exc.InvalidInput(error_message=msg)
463             elif 'default' in self.network_vlan_ranges:
464                 physical_network = 'default'
465             else:
466                 msg = _("provider:physical_network required")
467                 raise q_exc.InvalidInput(error_message=msg)
468
469         return (network_type, physical_network, segmentation_id)
470
471     def create_network(self, context, network):
472         (network_type, physical_network,
473          segmentation_id) = self._process_provider_create(context,
474                                                           network['network'])
475
476         session = context.session
477         #set up default security groups
478         tenant_id = self._get_tenant_id_for_create(
479             context, network['network'])
480         self._ensure_default_security_group(context, tenant_id)
481
482         with session.begin(subtransactions=True):
483             if not network_type:
484                 # tenant network
485                 network_type = self.tenant_network_type
486                 if network_type == constants.TYPE_NONE:
487                     raise q_exc.TenantNetworksDisabled()
488                 elif network_type == constants.TYPE_VLAN:
489                     (physical_network,
490                      segmentation_id) = ovs_db_v2.reserve_vlan(session)
491                 elif network_type in constants.TUNNEL_NETWORK_TYPES:
492                     segmentation_id = ovs_db_v2.reserve_tunnel(session)
493                 # no reservation needed for TYPE_LOCAL
494             else:
495                 # provider network
496                 if network_type in [constants.TYPE_VLAN, constants.TYPE_FLAT]:
497                     ovs_db_v2.reserve_specific_vlan(session, physical_network,
498                                                     segmentation_id)
499                 elif network_type in constants.TUNNEL_NETWORK_TYPES:
500                     ovs_db_v2.reserve_specific_tunnel(session, segmentation_id)
501                 # no reservation needed for TYPE_LOCAL
502             net = super(OVSNeutronPluginV2, self).create_network(context,
503                                                                  network)
504             ovs_db_v2.add_network_binding(session, net['id'], network_type,
505                                           physical_network, segmentation_id)
506
507             self._process_l3_create(context, net, network['network'])
508             self._extend_network_dict_provider(context, net)
509             # note - exception will rollback entire transaction
510         LOG.debug(_("Created network: %s"), net['id'])
511         return net
512
513     def update_network(self, context, id, network):
514         provider._raise_if_updates_provider_attributes(network['network'])
515
516         session = context.session
517         with session.begin(subtransactions=True):
518             net = super(OVSNeutronPluginV2, self).update_network(context, id,
519                                                                  network)
520             self._process_l3_update(context, net, network['network'])
521             self._extend_network_dict_provider(context, net)
522         return net
523
524     def delete_network(self, context, id):
525         session = context.session
526         with session.begin(subtransactions=True):
527             binding = ovs_db_v2.get_network_binding(session, id)
528             super(OVSNeutronPluginV2, self).delete_network(context, id)
529             if binding.network_type in constants.TUNNEL_NETWORK_TYPES:
530                 ovs_db_v2.release_tunnel(session, binding.segmentation_id,
531                                          self.tunnel_id_ranges)
532             elif binding.network_type in [constants.TYPE_VLAN,
533                                           constants.TYPE_FLAT]:
534                 ovs_db_v2.release_vlan(session, binding.physical_network,
535                                        binding.segmentation_id,
536                                        self.network_vlan_ranges)
537             # the network_binding record is deleted via cascade from
538             # the network record, so explicit removal is not necessary
539         self.notifier.network_delete(context, id)
540
541     def get_network(self, context, id, fields=None):
542         session = context.session
543         with session.begin(subtransactions=True):
544             net = super(OVSNeutronPluginV2, self).get_network(context,
545                                                               id, None)
546             self._extend_network_dict_provider(context, net)
547         return self._fields(net, fields)
548
549     def get_networks(self, context, filters=None, fields=None,
550                      sorts=None,
551                      limit=None, marker=None, page_reverse=False):
552         session = context.session
553         with session.begin(subtransactions=True):
554             nets = super(OVSNeutronPluginV2,
555                          self).get_networks(context, filters, None, sorts,
556                                             limit, marker, page_reverse)
557             for net in nets:
558                 self._extend_network_dict_provider(context, net)
559
560         return [self._fields(net, fields) for net in nets]
561
562     def create_port(self, context, port):
563         # Set port status as 'DOWN'. This will be updated by agent
564         port['port']['status'] = q_const.PORT_STATUS_DOWN
565         port_data = port['port']
566         session = context.session
567         with session.begin(subtransactions=True):
568             self._ensure_default_security_group_on_port(context, port)
569             sgids = self._get_security_groups_on_port(context, port)
570             dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
571             port = super(OVSNeutronPluginV2, self).create_port(context, port)
572             self._process_portbindings_create_and_update(context,
573                                                          port_data, port)
574             self._process_port_create_security_group(context, port, sgids)
575             self._process_port_create_extra_dhcp_opts(context, port,
576                                                       dhcp_opts)
577             port[addr_pair.ADDRESS_PAIRS] = (
578                 self._process_create_allowed_address_pairs(
579                     context, port,
580                     port_data.get(addr_pair.ADDRESS_PAIRS)))
581         self.notify_security_groups_member_updated(context, port)
582         return port
583
584     def _extend_port_dict_nat(self, context, port):
585         forward = ovs_db_v2.get_port_forwarding(context.session, port['id'])
586         if forward:
587             port[nat.FORWARD_PORTS] = forward
588         else:
589             port[nat.FORWARD_PORTS] = None
590
591     def _process_nat_update(self, context, attrs, id):
592         forward_ports = attrs.get(nat.FORWARD_PORTS)
593         forward_ports_set = attributes.is_attr_set(forward_ports)
594
595         if not forward_ports_set:
596             return None
597
598         # LOG.info("forward ports %s" % forward_ports)
599         valid_protocols = ["tcp", "udp"]
600         for entry in forward_ports:
601             if not isinstance(entry, dict):
602                 msg = _("nat:forward_ports: must specify a list of dicts (ex: 'l4_protocol=tcp,l4_port=80')")
603                 raise q_exc.InvalidInput(error_message=msg)
604             if not ("l4_protocol" in entry and "l4_port" in entry):
605                 msg = _("nat:forward_ports: dict is missing l4_protocol and l4_port (ex: 'l4_protocol=tcp,l4_port=80')")
606                 raise q_exc.InvalidInput(error_message=msg)
607             if entry['l4_protocol'] not in valid_protocols:
608                 msg = _("nat:forward_ports: invalid protocol (only tcp and udp allowed)")
609                 raise q_exc.InvalidInput(error_message=msg)
610             try:
611                 l4_port = int(entry['l4_port'])
612             except:
613                 msg = _("nat:forward_ports: l4_port must be an integer")
614                 raise q_exc.InvalidInput(error_message=msg)
615
616         return forward_ports
617
618     def get_port(self, context, id, fields=None):
619         session = context.session
620         with session.begin(subtransactions=True):
621             port = super(OVSNeutronPluginV2, self).get_port(context, id, None)
622             self._extend_port_dict_nat(context, port)
623         return self._fields(port, fields)
624
625     def get_ports(self, context, filters=None, fields=None):
626         session = context.session
627         with session.begin(subtransactions=True):
628             ports = super(OVSNeutronPluginV2, self).get_ports(context, filters,
629                                                           None)
630             for port in ports:
631                 self._extend_port_dict_nat(context, port)
632
633         return [self._fields(port, fields) for port in ports]
634
635     def update_port(self, context, id, port):
636         forward_ports = self._process_nat_update(context, port['port'], id)
637
638         session = context.session
639         need_port_update_notify = False
640         changed_fixed_ips = 'fixed_ips' in port['port']
641         with session.begin(subtransactions=True):
642             original_port = super(OVSNeutronPluginV2, self).get_port(
643                 context, id)
644             updated_port = super(OVSNeutronPluginV2, self).update_port(
645                 context, id, port)
646             if addr_pair.ADDRESS_PAIRS in port['port']:
647                 self._delete_allowed_address_pairs(context, id)
648                 self._process_create_allowed_address_pairs(
649                     context, updated_port,
650                     port['port'][addr_pair.ADDRESS_PAIRS])
651                 need_port_update_notify = True
652             elif changed_fixed_ips:
653                 self._check_fixed_ips_and_address_pairs_no_overlap(
654                     context, updated_port)
655
656             if forward_ports:
657                 ovs_db_v2.clear_port_forwarding(session, updated_port['id'])
658                 ovs_db_v2.add_port_forwarding(session, updated_port['id'], forward_ports)\r
659             self._extend_port_dict_nat(context, updated_port)
660
661             need_port_update_notify |= self.update_security_group_on_port(
662                 context, id, port, original_port, updated_port)
663             self._process_portbindings_create_and_update(context,
664                                                          port['port'],
665                                                          updated_port)
666             need_port_update_notify |= self._update_extra_dhcp_opts_on_port(
667                 context, id, port, updated_port)
668
669         need_port_update_notify |= self.is_security_group_member_updated(
670             context, original_port, updated_port)
671         if original_port['admin_state_up'] != updated_port['admin_state_up']:
672             need_port_update_notify = True
673
674         if need_port_update_notify:
675             binding = ovs_db_v2.get_network_binding(None,
676                                                     updated_port['network_id'])
677             self.notifier.port_update(context, updated_port,
678                                       binding.network_type,
679                                       binding.segmentation_id,
680                                       binding.physical_network)
681         return updated_port
682
683     def delete_port(self, context, id, l3_port_check=True):
684
685         # if needed, check to see if this is a port owned by
686         # and l3-router.  If so, we should prevent deletion.
687         if l3_port_check:
688             self.prevent_l3_port_deletion(context, id)
689
690         session = context.session
691         with session.begin(subtransactions=True):
692             self.disassociate_floatingips(context, id)
693             port = self.get_port(context, id)
694             self._delete_port_security_group_bindings(context, id)
695             super(OVSNeutronPluginV2, self).delete_port(context, id)
696
697         self.notify_security_groups_member_updated(context, port)