summaryrefslogtreecommitdiff
path: root/neutron/plugins/bigswitch/plugin.py
blob: b52dd3bf5e4fc545b60ed686411e5ede27a0ae68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
# Copyright 2012 Big Switch Networks, Inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
Neutron REST Proxy Plug-in for Big Switch and FloodLight Controllers.

NeutronRestProxy provides a generic neutron plugin that translates all plugin
function calls to equivalent authenticated REST calls to a set of redundant
external network controllers. It also keeps persistent store for all neutron
state to allow for re-sync of the external controller(s), if required.

The local state on the plugin also allows for local response and fast-fail
semantics where it can be determined based on the local persistent store.

Network controller specific code is decoupled from this plugin and expected
to reside on the controller itself (via the REST interface).

This allows for:
 - independent authentication and redundancy schemes between neutron and the
   network controller
 - independent upgrade/development cycles between neutron and the controller
   as it limits the proxy code upgrade requirement to neutron release cycle
   and the controller specific code upgrade requirement to controller code
 - ability to sync the controller with neutron for independent recovery/reset

External REST API used by proxy is the same API as defined for neutron (JSON
subset) with some additional parameters (gateway on network-create and macaddr
on port-attach) on an additional PUT to do a bulk dump of all persistent data.
"""

import copy
import functools
import httplib
import re

import eventlet
from oslo.config import cfg
from sqlalchemy.orm import exc as sqlexc

from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api import extensions as neutron_extensions
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.handlers import dhcp_rpc
from neutron.api.rpc.handlers import securitygroups_rpc
from neutron.common import constants as const
from neutron.common import exceptions
from neutron.common import rpc as n_rpc
from neutron.common import topics
from neutron.common import utils
from neutron import context as qcontext
from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import allowedaddresspairs_db as addr_pair_db
from neutron.db import api as db
from neutron.db import db_base_plugin_v2
from neutron.db import external_net_db
from neutron.db import extradhcpopt_db
from neutron.db import l3_db
from neutron.db import models_v2
from neutron.db import securitygroups_db as sg_db
from neutron.db import securitygroups_rpc_base as sg_db_rpc
from neutron.extensions import allowedaddresspairs as addr_pair
from neutron.extensions import external_net
from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.extensions import portbindings
from neutron import manager
from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging
from neutron.plugins.bigswitch import config as pl_config
from neutron.plugins.bigswitch.db import porttracker_db
from neutron.plugins.bigswitch import extensions
from neutron.plugins.bigswitch import servermanager
from neutron.plugins.bigswitch import version
from neutron.plugins.common import constants as pconst

LOG = logging.getLogger(__name__)

SYNTAX_ERROR_MESSAGE = _('Syntax error in server config file, aborting plugin')
METADATA_SERVER_IP = '169.254.169.254'


class AgentNotifierApi(n_rpc.RpcProxy,
                       sg_rpc.SecurityGroupAgentRpcApiMixin):

    BASE_RPC_API_VERSION = '1.1'

    def __init__(self, topic):
        super(AgentNotifierApi, self).__init__(
            topic=topic, default_version=self.BASE_RPC_API_VERSION)
        self.topic_port_update = topics.get_topic_name(
            topic, topics.PORT, topics.UPDATE)

    def port_update(self, context, port):
        self.fanout_cast(context,
                         self.make_msg('port_update',
                                       port=port),
                         topic=self.topic_port_update)


class SecurityGroupServerRpcMixin(sg_db_rpc.SecurityGroupServerRpcMixin):

    def get_port_from_device(self, device):
        port_id = re.sub(r"^%s" % const.TAP_DEVICE_PREFIX, "", device)
        port = self.get_port_and_sgs(port_id)
        if port:
            port['device'] = device
        return port

    def get_port_and_sgs(self, port_id):
        """Get port from database with security group info."""

        LOG.debug(_("get_port_and_sgs() called for port_id %s"), port_id)
        session = db.get_session()
        sg_binding_port = sg_db.SecurityGroupPortBinding.port_id

        with session.begin(subtransactions=True):
            query = session.query(
                models_v2.Port,
                sg_db.SecurityGroupPortBinding.security_group_id
            )
            query = query.outerjoin(sg_db.SecurityGroupPortBinding,
                                    models_v2.Port.id == sg_binding_port)
            query = query.filter(models_v2.Port.id.startswith(port_id))
            port_and_sgs = query.all()
            if not port_and_sgs:
                return
            port = port_and_sgs[0][0]
            plugin = manager.NeutronManager.get_plugin()
            port_dict = plugin._make_port_dict(port)
            port_dict['security_groups'] = [
                sg_id for port_, sg_id in port_and_sgs if sg_id]
            port_dict['security_group_rules'] = []
            port_dict['security_group_source_groups'] = []
            port_dict['fixed_ips'] = [ip['ip_address']
                                      for ip in port['fixed_ips']]
        return port_dict


class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
                             external_net_db.External_net_db_mixin):

    supported_extension_aliases = ["binding"]
    servers = None

    @property
    def l3_plugin(self):
        return manager.NeutronManager.get_service_plugins().get(
            pconst.L3_ROUTER_NAT)

    def _get_all_data(self, get_ports=True, get_floating_ips=True,
                      get_routers=True):
        admin_context = qcontext.get_admin_context()
        networks = []
        # this method is used by the ML2 driver so it can't directly invoke
        # the self.get_(ports|networks) methods
        plugin = manager.NeutronManager.get_plugin()
        all_networks = plugin.get_networks(admin_context) or []
        for net in all_networks:
            mapped_network = self._get_mapped_network_with_subnets(net)
            flips_n_ports = mapped_network
            if get_floating_ips:
                flips_n_ports = self._get_network_with_floatingips(
                    mapped_network)

            if get_ports:
                ports = []
                net_filter = {'network_id': [net.get('id')]}
                net_ports = plugin.get_ports(admin_context,
                                             filters=net_filter) or []
                for port in net_ports:
                    mapped_port = self._map_state_and_status(port)
                    mapped_port['attachment'] = {
                        'id': port.get('device_id'),
                        'mac': port.get('mac_address'),
                    }
                    mapped_port = self._extend_port_dict_binding(admin_context,
                                                                 mapped_port)
                    ports.append(mapped_port)
                flips_n_ports['ports'] = ports

            if flips_n_ports:
                networks.append(flips_n_ports)

        data = {'networks': networks}

        if get_routers and self.l3_plugin:
            routers = []
            all_routers = self.l3_plugin.get_routers(admin_context) or []
            for router in all_routers:
                interfaces = []
                mapped_router = self._map_state_and_status(router)
                router_filter = {
                    'device_owner': [const.DEVICE_OWNER_ROUTER_INTF],
                    'device_id': [router.get('id')]
                }
                router_ports = self.get_ports(admin_context,
                                              filters=router_filter) or []
                for port in router_ports:
                    net_id = port.get('network_id')
                    subnet_id = port['fixed_ips'][0]['subnet_id']
                    intf_details = self._get_router_intf_details(admin_context,
                                                                 net_id,
                                                                 subnet_id)
                    interfaces.append(intf_details)
                mapped_router['interfaces'] = interfaces

                routers.append(mapped_router)

            data.update({'routers': routers})
        return data

    def _send_all_data(self, send_ports=True, send_floating_ips=True,
                       send_routers=True, timeout=None,
                       triggered_by_tenant=None):
        """Pushes all data to network ctrl (networks/ports, ports/attachments).

        This gives the controller an option to re-sync it's persistent store
        with neutron's current view of that data.
        """
        data = self._get_all_data(send_ports, send_floating_ips, send_routers)
        data['triggered_by_tenant'] = triggered_by_tenant
        errstr = _("Unable to update remote topology: %s")
        return self.servers.rest_action('PUT', servermanager.TOPOLOGY_PATH,
                                        data, errstr, timeout=timeout)

    def _get_network_with_floatingips(self, network, context=None):
        if context is None:
            context = qcontext.get_admin_context()

        net_id = network['id']
        net_filter = {'floating_network_id': [net_id]}
        if self.l3_plugin:
            fl_ips = self.l3_plugin.get_floatingips(context,
                                                    filters=net_filter) or []
            network['floatingips'] = fl_ips

        return network

    def _get_all_subnets_json_for_network(self, net_id, context=None):
        if context is None:
            context = qcontext.get_admin_context()
        # start a sub-transaction to avoid breaking parent transactions
        with context.session.begin(subtransactions=True):
            subnets = self._get_subnets_by_network(context,
                                                   net_id)
        subnets_details = []
        if subnets:
            for subnet in subnets:
                subnet_dict = self._make_subnet_dict(subnet)
                mapped_subnet = self._map_state_and_status(subnet_dict)
                subnets_details.append(mapped_subnet)

        return subnets_details

    def _get_mapped_network_with_subnets(self, network, context=None):
        # if context is not provided, admin context is used
        if context is None:
            context = qcontext.get_admin_context()
        network = self._map_state_and_status(network)
        subnets = self._get_all_subnets_json_for_network(network['id'],
                                                         context)
        network['subnets'] = subnets
        for subnet in (subnets or []):
            if subnet['gateway_ip']:
                # FIX: For backward compatibility with wire protocol
                network['gateway'] = subnet['gateway_ip']
                break
        else:
            network['gateway'] = ''
        network[external_net.EXTERNAL] = self._network_is_external(
            context, network['id'])
        # include ML2 segmentation types
        network['segmentation_types'] = getattr(self, "segmentation_types", "")
        return network

    def _send_create_network(self, network, context=None):
        tenant_id = network['tenant_id']
        mapped_network = self._get_mapped_network_with_subnets(network,
                                                               context)
        self.servers.rest_create_network(tenant_id, mapped_network)

    def _send_update_network(self, network, context=None):
        net_id = network['id']
        tenant_id = network['tenant_id']
        mapped_network = self._get_mapped_network_with_subnets(network,
                                                               context)
        net_fl_ips = self._get_network_with_floatingips(mapped_network,
                                                        context)
        self.servers.rest_update_network(tenant_id, net_id, net_fl_ips)

    def _send_delete_network(self, network, context=None):
        net_id = network['id']
        tenant_id = network['tenant_id']
        self.servers.rest_delete_network(tenant_id, net_id)

    def _map_state_and_status(self, resource):
        resource = copy.copy(resource)

        resource['state'] = ('UP' if resource.pop('admin_state_up',
                                                  True) else 'DOWN')
        resource.pop('status', None)

        return resource

    def _warn_on_state_status(self, resource):
        if resource.get('admin_state_up', True) is False:
            LOG.warning(_("Setting admin_state_up=False is not supported "
                          "in this plugin version. Ignoring setting for "
                          "resource: %s"), resource)

        if 'status' in resource:
            if resource['status'] != const.NET_STATUS_ACTIVE:
                LOG.warning(_("Operational status is internally set by the "
                              "plugin. Ignoring setting status=%s."),
                            resource['status'])

    def _get_router_intf_details(self, context, intf_id, subnet_id):

        # we will use the network id as interface's id
        net_id = intf_id
        network = self.get_network(context, net_id)
        subnet = self.get_subnet(context, subnet_id)
        mapped_network = self._get_mapped_network_with_subnets(network)
        mapped_subnet = self._map_state_and_status(subnet)

        data = {
            'id': intf_id,
            "network": mapped_network,
            "subnet": mapped_subnet
        }

        return data

    def _extend_port_dict_binding(self, context, port):
        cfg_vif_type = cfg.CONF.NOVA.vif_type.lower()
        if not cfg_vif_type in (portbindings.VIF_TYPE_OVS,
                                portbindings.VIF_TYPE_IVS):
            LOG.warning(_("Unrecognized vif_type in configuration "
                          "[%s]. Defaulting to ovs."),
                        cfg_vif_type)
            cfg_vif_type = portbindings.VIF_TYPE_OVS
        # In ML2, the host_id is already populated
        if portbindings.HOST_ID in port:
            hostid = port[portbindings.HOST_ID]
        elif 'id' in port:
            hostid = porttracker_db.get_port_hostid(context, port['id'])
        else:
            hostid = None
        if hostid:
            port[portbindings.HOST_ID] = hostid
            override = self._check_hostvif_override(hostid)
            if override:
                cfg_vif_type = override
        port[portbindings.VIF_TYPE] = cfg_vif_type

        sg_enabled = sg_rpc.is_firewall_enabled()
        port[portbindings.VIF_DETAILS] = {
            # TODO(rkukura): Replace with new VIF security details
            portbindings.CAP_PORT_FILTER:
            'security-group' in self.supported_extension_aliases,
            portbindings.OVS_HYBRID_PLUG: sg_enabled
        }
        return port

    def _check_hostvif_override(self, hostid):
        for v in cfg.CONF.NOVA.vif_types:
            if hostid in getattr(cfg.CONF.NOVA, "node_override_vif_" + v, []):
                return v
        return False

    def _get_port_net_tenantid(self, context, port):
        net = super(NeutronRestProxyV2Base,
                    self).get_network(context, port["network_id"])
        return net['tenant_id']

    def async_port_create(self, tenant_id, net_id, port):
        try:
            self.servers.rest_create_port(tenant_id, net_id, port)
        except servermanager.RemoteRestError as e:
            # 404 should never be received on a port create unless
            # there are inconsistencies between the data in neutron
            # and the data in the backend.
            # Run a sync to get it consistent.
            if (cfg.CONF.RESTPROXY.auto_sync_on_failure and
                e.status == httplib.NOT_FOUND and
                servermanager.NXNETWORK in e.reason):
                LOG.error(_("Iconsistency with backend controller "
                            "triggering full synchronization."))
                # args depend on if we are operating in ML2 driver
                # or as the full plugin
                topoargs = self.servers.get_topo_function_args
                self._send_all_data(
                    send_ports=topoargs['get_ports'],
                    send_floating_ips=topoargs['get_floating_ips'],
                    send_routers=topoargs['get_routers'],
                    triggered_by_tenant=tenant_id
                )
                # If the full sync worked, the port will be created
                # on the controller so it can be safely marked as active
            else:
                # Any errors that don't result in a successful auto-sync
                # require that the port be placed into the error state.
                LOG.error(
                    _("NeutronRestProxyV2: Unable to create port: %s"), e)
                try:
                    self._set_port_status(port['id'], const.PORT_STATUS_ERROR)
                except exceptions.PortNotFound:
                    # If port is already gone from DB and there was an error
                    # creating on the backend, everything is already consistent
                    pass
                return
        new_status = (const.PORT_STATUS_ACTIVE if port['state'] == 'UP'
                      else const.PORT_STATUS_DOWN)
        try:
            self._set_port_status(port['id'], new_status)
        except exceptions.PortNotFound:
            # This port was deleted before the create made it to the controller
            # so it now needs to be deleted since the normal delete request
            # would have deleted an non-existent port.
            self.servers.rest_delete_port(tenant_id, net_id, port['id'])

    # NOTE(kevinbenton): workaround for eventlet/mysql deadlock
    @utils.synchronized('bsn-port-barrier')
    def _set_port_status(self, port_id, status):
        session = db.get_session()
        try:
            port = session.query(models_v2.Port).filter_by(id=port_id).one()
            port['status'] = status
            session.flush()
        except sqlexc.NoResultFound:
            raise exceptions.PortNotFound(port_id=port_id)


def put_context_in_serverpool(f):
    @functools.wraps(f)
    def wrapper(self, context, *args, **kwargs):
        # core plugin: context is top level object
        # ml2: keeps context in _plugin_context
        self.servers.set_context(getattr(context, '_plugin_context', context))
        return f(self, context, *args, **kwargs)
    return wrapper


class NeutronRestProxyV2(NeutronRestProxyV2Base,
                         addr_pair_db.AllowedAddressPairsMixin,
                         extradhcpopt_db.ExtraDhcpOptMixin,
                         agentschedulers_db.DhcpAgentSchedulerDbMixin,
                         SecurityGroupServerRpcMixin):

    _supported_extension_aliases = ["external-net", "binding",
                                    "extra_dhcp_opt", "quotas",
                                    "dhcp_agent_scheduler", "agent",
                                    "security-group", "allowed-address-pairs"]

    @property
    def supported_extension_aliases(self):
        if not hasattr(self, '_aliases'):
            aliases = self._supported_extension_aliases[:]
            sg_rpc.disable_security_group_extension_by_config(aliases)
            self._aliases = aliases
        return self._aliases

    def __init__(self):
        super(NeutronRestProxyV2, self).__init__()
        LOG.info(_('NeutronRestProxy: Starting plugin. Version=%s'),
                 version.version_string_with_vcs())
        pl_config.register_config()
        self.evpool = eventlet.GreenPool(cfg.CONF.RESTPROXY.thread_pool_size)

        # Include the Big Switch Extensions path in the api_extensions
        neutron_extensions.append_api_extensions_path(extensions.__path__)

        self.add_meta_server_route = cfg.CONF.RESTPROXY.add_meta_server_route

        # init network ctrl connections
        self.servers = servermanager.ServerPool()
        self.servers.get_topo_function = self._get_all_data
        self.servers.get_topo_function_args = {'get_ports': True,
                                               'get_floating_ips': True,
                                               'get_routers': True}

        self.network_scheduler = importutils.import_object(
            cfg.CONF.network_scheduler_driver
        )

        # setup rpc for security and DHCP agents
        self._setup_rpc()

        if cfg.CONF.RESTPROXY.sync_data:
            self._send_all_data()

        LOG.debug(_("NeutronRestProxyV2: initialization done"))

    def _setup_rpc(self):
        self.conn = n_rpc.create_connection(new=True)
        self.topic = topics.PLUGIN
        self.notifier = AgentNotifierApi(topics.AGENT)
        # init dhcp agent support
        self._dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
        self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
            self._dhcp_agent_notifier
        )
        self.endpoints = [securitygroups_rpc.SecurityGroupServerRpcCallback(),
                          dhcp_rpc.DhcpRpcCallback(),
                          agents_db.AgentExtRpcCallback()]
        self.conn.create_consumer(self.topic, self.endpoints,
                                  fanout=False)
        # Consume from all consumers in threads
        self.conn.consume_in_threads()

    @put_context_in_serverpool
    def create_network(self, context, network):
        """Create a network.

        Network represents an L2 network segment which can have a set of
        subnets and ports associated with it.

        :param context: neutron api request context
        :param network: dictionary describing the network

        :returns: a sequence of mappings with the following signature:
        {
            "id": UUID representing the network.
            "name": Human-readable name identifying the network.
            "tenant_id": Owner of network. NOTE: only admin user can specify
                         a tenant_id other than its own.
            "admin_state_up": Sets admin state of network.
                              if down, network does not forward packets.
            "status": Indicates whether network is currently operational
                      (values are "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "subnets": Subnets associated with this network.
        }

        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: create_network() called"))

        self._warn_on_state_status(network['network'])

        with context.session.begin(subtransactions=True):
            self._ensure_default_security_group(
                context,
                network['network']["tenant_id"]
            )
            # create network in DB
            new_net = super(NeutronRestProxyV2, self).create_network(context,
                                                                     network)
            self._process_l3_create(context, new_net, network['network'])
            # create network on the network controller
            self._send_create_network(new_net, context)

        # return created network
        return new_net

    @put_context_in_serverpool
    def update_network(self, context, net_id, network):
        """Updates the properties of a particular Virtual Network.

        :param context: neutron api request context
        :param net_id: uuid of the network to update
        :param network: dictionary describing the updates

        :returns: a sequence of mappings with the following signature:
        {
            "id": UUID representing the network.
            "name": Human-readable name identifying the network.
            "tenant_id": Owner of network. NOTE: only admin user can
                         specify a tenant_id other than its own.
            "admin_state_up": Sets admin state of network.
                              if down, network does not forward packets.
            "status": Indicates whether network is currently operational
                      (values are "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "subnets": Subnets associated with this network.
        }

        :raises: exceptions.NetworkNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2.update_network() called"))

        self._warn_on_state_status(network['network'])

        session = context.session
        with session.begin(subtransactions=True):
            new_net = super(NeutronRestProxyV2, self).update_network(
                context, net_id, network)
            self._process_l3_update(context, new_net, network['network'])

            # update network on network controller
            self._send_update_network(new_net, context)
        return new_net

    # NOTE(kevinbenton): workaround for eventlet/mysql deadlock
    @utils.synchronized('bsn-port-barrier')
    @put_context_in_serverpool
    def delete_network(self, context, net_id):
        """Delete a network.
        :param context: neutron api request context
        :param id: UUID representing the network to delete.

        :returns: None

        :raises: exceptions.NetworkInUse
        :raises: exceptions.NetworkNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: delete_network() called"))

        # Validate args
        orig_net = super(NeutronRestProxyV2, self).get_network(context, net_id)
        with context.session.begin(subtransactions=True):
            self._process_l3_delete(context, net_id)
            ret_val = super(NeutronRestProxyV2, self).delete_network(context,
                                                                     net_id)
            self._send_delete_network(orig_net, context)
            return ret_val

    @put_context_in_serverpool
    def create_port(self, context, port):
        """Create a port, which is a connection point of a device
        (e.g., a VM NIC) to attach an L2 Neutron network.
        :param context: neutron api request context
        :param port: dictionary describing the port

        :returns:
        {
            "id": uuid representing the port.
            "network_id": uuid of network.
            "tenant_id": tenant_id
            "mac_address": mac address to use on this port.
            "admin_state_up": Sets admin state of port. if down, port
                              does not forward packets.
            "status": dicates whether port is currently operational
                      (limit values to "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "fixed_ips": list of subnet IDs and IP addresses to be used on
                         this port
            "device_id": identifies the device (e.g., virtual server) using
                         this port.
        }

        :raises: exceptions.NetworkNotFound
        :raises: exceptions.StateInvalid
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: create_port() called"))

        # Update DB in new session so exceptions rollback changes
        with context.session.begin(subtransactions=True):
            self._ensure_default_security_group_on_port(context, port)
            sgids = self._get_security_groups_on_port(context, port)
            # non-router port status is set to pending. it is then updated
            # after the async rest call completes. router ports are synchronous
            if port['port']['device_owner'] == l3_db.DEVICE_OWNER_ROUTER_INTF:
                port['port']['status'] = const.PORT_STATUS_ACTIVE
            else:
                port['port']['status'] = const.PORT_STATUS_BUILD
            dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
            new_port = super(NeutronRestProxyV2, self).create_port(context,
                                                                   port)
            self._process_port_create_security_group(context, new_port, sgids)
        if (portbindings.HOST_ID in port['port']
            and 'id' in new_port):
            host_id = port['port'][portbindings.HOST_ID]
            porttracker_db.put_port_hostid(context, new_port['id'],
                                           host_id)
        new_port[addr_pair.ADDRESS_PAIRS] = (
            self._process_create_allowed_address_pairs(
                context, new_port,
                port['port'].get(addr_pair.ADDRESS_PAIRS)))
        self._process_port_create_extra_dhcp_opts(context, new_port,
                                                  dhcp_opts)
        new_port = self._extend_port_dict_binding(context, new_port)
        net = super(NeutronRestProxyV2,
                    self).get_network(context, new_port["network_id"])
        if self.add_meta_server_route:
            if new_port['device_owner'] == const.DEVICE_OWNER_DHCP:
                destination = METADATA_SERVER_IP + '/32'
                self._add_host_route(context, destination, new_port)

        # create on network ctrl
        mapped_port = self._map_state_and_status(new_port)
        # ports have to be created synchronously when creating a router
        # port since adding router interfaces is a multi-call process
        if mapped_port['device_owner'] == l3_db.DEVICE_OWNER_ROUTER_INTF:
            self.servers.rest_create_port(net["tenant_id"],
                                          new_port["network_id"],
                                          mapped_port)
        else:
            self.evpool.spawn_n(self.async_port_create, net["tenant_id"],
                                new_port["network_id"], mapped_port)
        self.notify_security_groups_member_updated(context, new_port)
        return new_port

    def get_port(self, context, id, fields=None):
        with context.session.begin(subtransactions=True):
            port = super(NeutronRestProxyV2, self).get_port(context, id,
                                                            fields)
            self._extend_port_dict_binding(context, port)
        return self._fields(port, fields)

    def get_ports(self, context, filters=None, fields=None):
        with context.session.begin(subtransactions=True):
            ports = super(NeutronRestProxyV2, self).get_ports(context, filters,
                                                              fields)
            for port in ports:
                self._extend_port_dict_binding(context, port)
        return [self._fields(port, fields) for port in ports]

    @put_context_in_serverpool
    def update_port(self, context, port_id, port):
        """Update values of a port.

        :param context: neutron api request context
        :param id: UUID representing the port to update.
        :param port: dictionary with keys indicating fields to update.

        :returns: a mapping sequence with the following signature:
        {
            "id": uuid representing the port.
            "network_id": uuid of network.
            "tenant_id": tenant_id
            "mac_address": mac address to use on this port.
            "admin_state_up": sets admin state of port. if down, port
                               does not forward packets.
            "status": dicates whether port is currently operational
                       (limit values to "ACTIVE", "DOWN", "BUILD", and "ERROR")
            "fixed_ips": list of subnet IDs and IP addresses to be used on
                         this port
            "device_id": identifies the device (e.g., virtual server) using
                         this port.
        }

        :raises: exceptions.StateInvalid
        :raises: exceptions.PortNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: update_port() called"))

        self._warn_on_state_status(port['port'])

        # Validate Args
        orig_port = super(NeutronRestProxyV2, self).get_port(context, port_id)
        with context.session.begin(subtransactions=True):
            # Update DB
            new_port = super(NeutronRestProxyV2,
                             self).update_port(context, port_id, port)
            ctrl_update_required = False
            if addr_pair.ADDRESS_PAIRS in port['port']:
                ctrl_update_required |= (
                    self.update_address_pairs_on_port(context, port_id, port,
                                                      orig_port, new_port))
            self._update_extra_dhcp_opts_on_port(context, port_id, port,
                                                 new_port)
            old_host_id = porttracker_db.get_port_hostid(context,
                                                         orig_port['id'])
            if (portbindings.HOST_ID in port['port']
                and 'id' in new_port):
                host_id = port['port'][portbindings.HOST_ID]
                porttracker_db.put_port_hostid(context, new_port['id'],
                                               host_id)
                if old_host_id != host_id:
                    ctrl_update_required = True

            if (new_port.get("device_id") != orig_port.get("device_id") and
                orig_port.get("device_id")):
                ctrl_update_required = True

            if ctrl_update_required:
                # tenant_id must come from network in case network is shared
                net_tenant_id = self._get_port_net_tenantid(context, new_port)
                new_port = self._extend_port_dict_binding(context, new_port)
                mapped_port = self._map_state_and_status(new_port)
                self.servers.rest_update_port(net_tenant_id,
                                              new_port["network_id"],
                                              mapped_port)
            agent_update_required = self.update_security_group_on_port(
                context, port_id, port, orig_port, new_port)
        agent_update_required |= self.is_security_group_member_updated(
            context, orig_port, new_port)

        # return new_port
        return new_port

    # NOTE(kevinbenton): workaround for eventlet/mysql deadlock
    @utils.synchronized('bsn-port-barrier')
    @put_context_in_serverpool
    def delete_port(self, context, port_id, l3_port_check=True):
        """Delete a port.
        :param context: neutron api request context
        :param id: UUID representing the port to delete.

        :raises: exceptions.PortInUse
        :raises: exceptions.PortNotFound
        :raises: exceptions.NetworkNotFound
        :raises: RemoteRestError
        """
        LOG.debug(_("NeutronRestProxyV2: delete_port() called"))

        # if needed, check to see if this is a port owned by
        # and l3-router.  If so, we should prevent deletion.
        if l3_port_check and self.l3_plugin:
            self.l3_plugin.prevent_l3_port_deletion(context, port_id)
        with context.session.begin(subtransactions=True):
            if self.l3_plugin:
                router_ids = self.l3_plugin.disassociate_floatingips(
                    context, port_id, do_notify=False)
            self._delete_port_security_group_bindings(context, port_id)
            port = super(NeutronRestProxyV2, self).get_port(context, port_id)
            # Tenant ID must come from network in case the network is shared
            tenid = self._get_port_net_tenantid(context, port)
            self._delete_port(context, port_id)
            self.servers.rest_delete_port(tenid, port['network_id'], port_id)

        if self.l3_plugin:
            # now that we've left db transaction, we are safe to notify
            self.l3_plugin.notify_routers_updated(context, router_ids)

    @put_context_in_serverpool
    def create_subnet(self, context, subnet):
        LOG.debug(_("NeutronRestProxyV2: create_subnet() called"))

        self._warn_on_state_status(subnet['subnet'])

        with context.session.begin(subtransactions=True):
            # create subnet in DB
            new_subnet = super(NeutronRestProxyV2,
                               self).create_subnet(context, subnet)
            net_id = new_subnet['network_id']
            orig_net = super(NeutronRestProxyV2,
                             self).get_network(context, net_id)
            # update network on network controller
            self._send_update_network(orig_net, context)
        return new_subnet

    @put_context_in_serverpool
    def update_subnet(self, context, id, subnet):
        LOG.debug(_("NeutronRestProxyV2: update_subnet() called"))

        self._warn_on_state_status(subnet['subnet'])

        with context.session.begin(subtransactions=True):
            # update subnet in DB
            new_subnet = super(NeutronRestProxyV2,
                               self).update_subnet(context, id, subnet)
            net_id = new_subnet['network_id']
            orig_net = super(NeutronRestProxyV2,
                             self).get_network(context, net_id)
            # update network on network controller
            self._send_update_network(orig_net, context)
            return new_subnet

    # NOTE(kevinbenton): workaround for eventlet/mysql deadlock
    @utils.synchronized('bsn-port-barrier')
    @put_context_in_serverpool
    def delete_subnet(self, context, id):
        LOG.debug(_("NeutronRestProxyV2: delete_subnet() called"))
        orig_subnet = super(NeutronRestProxyV2, self).get_subnet(context, id)
        net_id = orig_subnet['network_id']
        with context.session.begin(subtransactions=True):
            # delete subnet in DB
            super(NeutronRestProxyV2, self).delete_subnet(context, id)
            orig_net = super(NeutronRestProxyV2, self).get_network(context,
                                                                   net_id)
            # update network on network controller - exception will rollback
            self._send_update_network(orig_net, context)

    def _add_host_route(self, context, destination, port):
        subnet = {}
        for fixed_ip in port['fixed_ips']:
            subnet_id = fixed_ip['subnet_id']
            nexthop = fixed_ip['ip_address']
            subnet['host_routes'] = [{'destination': destination,
                                      'nexthop': nexthop}]
            updated_subnet = self.update_subnet(context,
                                                subnet_id,
                                                {'subnet': subnet})
            payload = {'subnet': updated_subnet}
            self._dhcp_agent_notifier.notify(context, payload,
                                             'subnet.update.end')
            LOG.debug(_("Adding host route: "))
            LOG.debug(_("Destination:%(dst)s nexthop:%(next)s"),
                      {'dst': destination, 'next': nexthop})