Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

quantum l3 + floating IP support

bp quantum-l3-fw-nat

router & floating IP API calls, plugin db, and agent implemented
and unit tested

Change-Id: I6ee61396d22e2fd7840aa2ff7d1f6f4a2c6e54d4
  • Loading branch information...
commit 3005d16fe3588bdf4b928e79f640b991df9fae3b 1 parent 09749a9
Dan Wendlandt authored August 15, 2012 salv-orlando committed August 17, 2012
20  bin/quantum-l3-agent
... ...
@@ -0,0 +1,20 @@
  1
+#!/usr/bin/env python
  2
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
  3
+
  4
+# Copyright (c) 2012 Openstack, LLC.
  5
+# All Rights Reserved.
  6
+#
  7
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
  8
+#    not use this file except in compliance with the License. You may obtain
  9
+#    a copy of the License at
  10
+#
  11
+#         http://www.apache.org/licenses/LICENSE-2.0
  12
+#
  13
+#    Unless required by applicable law or agreed to in writing, software
  14
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  15
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  16
+#    License for the specific language governing permissions and limitations
  17
+#    under the License.
  18
+
  19
+from quantum.agent.l3_agent import main
  20
+main()
19  etc/l3_agent.ini
... ...
@@ -0,0 +1,19 @@
  1
+[DEFAULT]
  2
+# Show debugging output in log (sets DEBUG log level output)
  3
+# debug = True
  4
+
  5
+# L3 requires that an inteface driver be set.  Choose the one that best
  6
+# matches your plugin.
  7
+
  8
+# OVS
  9
+interface_driver = quantum.agent.linux.interface.OVSInterfaceDriver
  10
+# LinuxBridge
  11
+#interface_driver = quantum.agent.linux.interface.BridgeInterfaceDriver
  12
+
  13
+# The Quantum user information for accessing the Quantum API.
  14
+auth_url = http://localhost:35357/v2.0
  15
+auth_region = RegionOne
  16
+admin_tenant_name = %SERVICE_TENANT_NAME%
  17
+admin_user = %SERVICE_USER%
  18
+admin_password = %SERVICE_PASSWORD%
  19
+
414  quantum/agent/l3_agent.py
... ...
@@ -0,0 +1,414 @@
  1
+"""
  2
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
  3
+#
  4
+# Copyright 2012 Nicira Networks, Inc.  All rights reserved.
  5
+#
  6
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
  7
+#    not use this file except in compliance with the License. You may obtain
  8
+#    a copy of the License at
  9
+#
  10
+#         http://www.apache.org/licenses/LICENSE-2.0
  11
+#
  12
+#    Unless required by applicable law or agreed to in writing, software
  13
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15
+#    License for the specific language governing permissions and limitations
  16
+#    under the License.
  17
+#
  18
+# @author: Dan Wendlandt, Nicira, Inc
  19
+#
  20
+"""
  21
+
  22
+import logging
  23
+import sys
  24
+import time
  25
+
  26
+import netaddr
  27
+
  28
+from quantum.agent.common import config
  29
+from quantum.agent.linux import interface
  30
+from quantum.agent.linux import ip_lib
  31
+from quantum.agent.linux import iptables_manager
  32
+from quantum.agent.linux import utils as linux_utils
  33
+from quantum.db import l3_db
  34
+from quantum.openstack.common import cfg
  35
+from quantum.openstack.common import importutils
  36
+from quantumclient.v2_0 import client
  37
+
  38
+LOG = logging.getLogger(__name__)
  39
+NS_PREFIX = 'qrouter-'
  40
+INTERNAL_DEV_PREFIX = 'qr-'
  41
+EXTERNAL_DEV_PREFIX = 'qgw-'
  42
+
  43
+
  44
+class RouterInfo(object):
  45
+
  46
+    def __init__(self, router_id, root_helper):
  47
+        self.router_id = router_id
  48
+        self.ex_gw_port = None
  49
+        self.internal_ports = []
  50
+        self.floating_ips = []
  51
+        self.root_helper = root_helper
  52
+
  53
+        self.iptables_manager = iptables_manager.IptablesManager(
  54
+            root_helper=root_helper,
  55
+            #FIXME(danwent): use_ipv6=True,
  56
+            namespace=self.ns_name())
  57
+
  58
+    def ns_name(self):
  59
+        return NS_PREFIX + self.router_id
  60
+
  61
+
  62
+class L3NATAgent(object):
  63
+
  64
+    OPTS = [
  65
+        cfg.StrOpt('admin_user'),
  66
+        cfg.StrOpt('admin_password'),
  67
+        cfg.StrOpt('admin_tenant_name'),
  68
+        cfg.StrOpt('auth_url'),
  69
+        cfg.StrOpt('auth_strategy', default='keystone'),
  70
+        cfg.StrOpt('auth_region'),
  71
+        cfg.StrOpt('root_helper', default='sudo'),
  72
+        cfg.StrOpt('external_network_bridge', default='br-ex',
  73
+                   help="Name of bridge used for external network traffic."),
  74
+        cfg.StrOpt('interface_driver',
  75
+                   help="The driver used to manage the virtual interface."),
  76
+        cfg.IntOpt('polling_interval',
  77
+                   default=3,
  78
+                   help="The time in seconds between state poll requests."),
  79
+        cfg.StrOpt('metadata_ip', default='127.0.0.1',
  80
+                   help="IP address used by Nova metadata server."),
  81
+        cfg.IntOpt('metadata_port',
  82
+                   default=8775,
  83
+                   help="TCP Port used by Nova metadata server."),
  84
+        #FIXME(danwent): not currently used
  85
+        cfg.BoolOpt('send_arp_for_ha',
  86
+                    default=True,
  87
+                    help="Send gratuitious ARP when router IP is configured")
  88
+    ]
  89
+
  90
+    def __init__(self, conf):
  91
+        self.conf = conf
  92
+
  93
+        if not conf.interface_driver:
  94
+            LOG.error(_('You must specify an interface driver'))
  95
+            sys.exit(1)
  96
+        try:
  97
+            self.driver = importutils.import_object(conf.interface_driver,
  98
+                                                    conf)
  99
+        except:
  100
+            LOG.exception(_("Error importing interface driver '%s'"
  101
+                            % conf.interface_driver))
  102
+            sys.exit(1)
  103
+
  104
+        self.polling_interval = conf.polling_interval
  105
+
  106
+        if not ip_lib.device_exists(self.conf.external_network_bridge):
  107
+            raise Exception("external network bridge '%s' does not exist"
  108
+                            % self.conf.external_network_bridge)
  109
+
  110
+        self.qclient = client.Client(
  111
+            username=self.conf.admin_user,
  112
+            password=self.conf.admin_password,
  113
+            tenant_name=self.conf.admin_tenant_name,
  114
+            auth_url=self.conf.auth_url,
  115
+            auth_strategy=self.conf.auth_strategy,
  116
+            auth_region=self.conf.auth_region
  117
+        )
  118
+
  119
+        # disable forwarding
  120
+        linux_utils.execute(['sysctl', '-w', 'net.ipv4.ip_forward=0'],
  121
+                            self.conf.root_helper, check_exit_code=False)
  122
+
  123
+        self._destroy_router_namespaces()
  124
+
  125
+        # enable forwarding
  126
+        linux_utils.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1'],
  127
+                            self.conf.root_helper, check_exit_code=False)
  128
+
  129
+    def _destroy_router_namespaces(self):
  130
+        """Destroy all router namespaces on the host to eliminate
  131
+        all stale linux devices, iptables rules, and namespaces.
  132
+        """
  133
+        root_ip = ip_lib.IPWrapper(self.conf.root_helper)
  134
+        for ns in root_ip.get_namespaces(self.conf.root_helper):
  135
+            if ns.startswith(NS_PREFIX):
  136
+                ns_ip = ip_lib.IPWrapper(self.conf.root_helper,
  137
+                                         namespace=ns)
  138
+                for d in ns_ip.get_devices():
  139
+                    if d.name.startswith(INTERNAL_DEV_PREFIX):
  140
+                        # device is on default bridge
  141
+                        self.driver.unplug(d.name)
  142
+                    elif d.name.startswith(EXTERNAL_DEV_PREFIX):
  143
+                        self.driver.unplug(d.name,
  144
+                                           bridge=
  145
+                                           self.conf.external_network_bridge)
  146
+
  147
+                # FIXME(danwent): disabling actual deletion of namespace
  148
+                # until we figure out why it fails.  Having deleted all
  149
+                # devices, the only harm here should be the clutter of
  150
+                # the namespace lying around.
  151
+
  152
+                # ns_ip.netns.delete(ns)
  153
+
  154
+    def daemon_loop(self):
  155
+
  156
+        #TODO(danwent): this simple diff logic does not handle if a
  157
+        # resource is modified (i.e., ip change on port, or floating ip
  158
+        # mapped from one IP to another).  Will fix this properly with
  159
+        # update notifications.
  160
+        # Likewise, it does not handle removing routers
  161
+
  162
+        self.router_info = {}
  163
+        while True:
  164
+            try:
  165
+                #TODO(danwent): provide way to limit this to a single
  166
+                # router, for model where agent runs in dedicated VM
  167
+                for r in self.qclient.list_routers()['routers']:
  168
+                    if r['id'] not in self.router_info:
  169
+                        self.router_info[r['id']] = (RouterInfo(r['id'],
  170
+                                                     self.conf.root_helper))
  171
+                    ri = self.router_info[r['id']]
  172
+                    self.process_router(ri)
  173
+            except:
  174
+                LOG.exception("Error running l3_nat daemon_loop")
  175
+
  176
+            time.sleep(self.polling_interval)
  177
+
  178
+    def _set_subnet_info(self, port):
  179
+        ips = port['fixed_ips']
  180
+        if not ips:
  181
+            raise Exception("Router port %s has no IP address" % port['id'])
  182
+        if len(ips) > 1:
  183
+            LOG.error("Ignoring multiple IPs on router port %s" % port['id'])
  184
+        port['subnet'] = self.qclient.show_subnet(
  185
+            ips[0]['subnet_id'])['subnet']
  186
+        prefixlen = netaddr.IPNetwork(port['subnet']['cidr']).prefixlen
  187
+        port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen)
  188
+
  189
+    def process_router(self, ri):
  190
+
  191
+        ex_gw_port = self._get_ex_gw_port(ri)
  192
+
  193
+        internal_ports = self.qclient.list_ports(
  194
+            device_id=ri.router_id,
  195
+            device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF)['ports']
  196
+
  197
+        existing_port_ids = set([p['id'] for p in ri.internal_ports])
  198
+        current_port_ids = set([p['id'] for p in internal_ports])
  199
+
  200
+        for p in internal_ports:
  201
+            if p['id'] not in existing_port_ids:
  202
+                self._set_subnet_info(p)
  203
+                ri.internal_ports.append(p)
  204
+                self.internal_network_added(ri, ex_gw_port, p['id'],
  205
+                                            p['ip_cidr'], p['mac_address'])
  206
+
  207
+        port_ids_to_remove = existing_port_ids - current_port_ids
  208
+        for p in ri.internal_ports:
  209
+            if p['id'] in port_ids_to_remove:
  210
+                ri.internal_ports.remove(p)
  211
+                self.internal_network_removed(ri, ex_gw_port, p['id'],
  212
+                                              p['ip_cidr'])
  213
+
  214
+        internal_cidrs = [p['ip_cidr'] for p in ri.internal_ports]
  215
+
  216
+        if ex_gw_port and not ri.ex_gw_port:
  217
+            self._set_subnet_info(ex_gw_port)
  218
+            self.external_gateway_added(ri, ex_gw_port, internal_cidrs)
  219
+        elif not ex_gw_port and ri.ex_gw_port:
  220
+            self.external_gateway_removed(ri, ri.ex_gw_port,
  221
+                                          internal_cidrs)
  222
+
  223
+        if ri.ex_gw_port or ex_gw_port:
  224
+            self.process_router_floating_ips(ri, ex_gw_port)
  225
+
  226
+        ri.ex_gw_port = ex_gw_port
  227
+
  228
+    def process_router_floating_ips(self, ri, ex_gw_port):
  229
+        floating_ips = self.qclient.list_floatingips(
  230
+            router_id=ri.router_id)['floatingips']
  231
+        existing_floating_ip_ids = set([fip['id'] for fip in ri.floating_ips])
  232
+        cur_floating_ip_ids = set([fip['id'] for fip in floating_ips])
  233
+
  234
+        for fip in floating_ips:
  235
+            if fip['port_id']:
  236
+                if fip['id'] not in existing_floating_ip_ids:
  237
+                    ri.floating_ips.append(fip)
  238
+                    self.floating_ip_added(ri, ex_gw_port,
  239
+                                           fip['floating_ip_address'],
  240
+                                           fip['fixed_ip_address'])
  241
+
  242
+        floating_ip_ids_to_remove = (existing_floating_ip_ids -
  243
+                                     cur_floating_ip_ids)
  244
+        for fip in ri.floating_ips:
  245
+            if fip['id'] in floating_ip_ids_to_remove:
  246
+                ri.floating_ips.remove(fip)
  247
+                self.floating_ip_removed(ri, ri.ex_gw_port,
  248
+                                         fip['floating_ip_address'],
  249
+                                         fip['fixed_ip_address'])
  250
+
  251
+    def _get_ex_gw_port(self, ri):
  252
+        ports = self.qclient.list_ports(
  253
+            device_id=ri.router_id,
  254
+            device_owner=l3_db.DEVICE_OWNER_ROUTER_GW)['ports']
  255
+        if not ports:
  256
+            return None
  257
+        elif len(ports) == 1:
  258
+            return ports[0]
  259
+        else:
  260
+            LOG.error("Ignoring multiple gateway ports for router %s"
  261
+                      % ri.router_id)
  262
+
  263
+    def get_internal_device_name(self, port_id):
  264
+        return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
  265
+
  266
+    def get_external_device_name(self, port_id):
  267
+        return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN]
  268
+
  269
+    def external_gateway_added(self, ri, ex_gw_port, internal_cidrs):
  270
+
  271
+        interface_name = self.get_external_device_name(ex_gw_port['id'])
  272
+        ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
  273
+        if not ip_lib.device_exists(interface_name,
  274
+                                    root_helper=self.conf.root_helper,
  275
+                                    namespace=ri.ns_name()):
  276
+            self.driver.plug(None, ex_gw_port['id'], interface_name,
  277
+                             ex_gw_port['mac_address'],
  278
+                             bridge=self.conf.external_network_bridge,
  279
+                             namespace=ri.ns_name())
  280
+        self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']],
  281
+                            namespace=ri.ns_name())
  282
+
  283
+        gw_ip = ex_gw_port['subnet']['gateway_ip']
  284
+        if ex_gw_port['subnet']['gateway_ip']:
  285
+            cmd = ['route', 'add', 'default', 'gw', gw_ip]
  286
+            ip_wrapper = ip_lib.IPWrapper(self.conf.root_helper,
  287
+                                          namespace=ri.ns_name())
  288
+            ip_wrapper.netns.execute(cmd)
  289
+
  290
+        for (c, r) in self.external_gateway_filter_rules():
  291
+            ri.iptables_manager.ipv4['filter'].add_rule(c, r)
  292
+        for (c, r) in self.external_gateway_nat_rules(ex_gw_ip,
  293
+                                                      internal_cidrs):
  294
+            ri.iptables_manager.ipv4['nat'].add_rule(c, r)
  295
+        ri.iptables_manager.apply()
  296
+
  297
+    def external_gateway_removed(self, ri, ex_gw_port, internal_cidrs):
  298
+
  299
+        interface_name = self.get_external_device_name(ex_gw_port['id'])
  300
+        if ip_lib.device_exists(interface_name,
  301
+                                root_helper=self.conf.root_helper,
  302
+                                namespace=ri.ns_name()):
  303
+            self.driver.unplug(interface_name)
  304
+
  305
+        ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
  306
+        for c, r in self.external_gateway_filter_rules():
  307
+            ri.iptables_manager.ipv4['filter'].remove_rule(c, r)
  308
+        for c, r in self.external_gateway_nat_rules(ex_gw_ip, internal_cidrs):
  309
+            ri.iptables_manager.ipv4['nat'].remove_rule(c, r)
  310
+        ri.iptables_manager.apply()
  311
+
  312
+    def external_gateway_filter_rules(self):
  313
+        return [('INPUT', '-s 0.0.0.0/0 -d %s '
  314
+                          '-p tcp -m tcp --dport %s '
  315
+                          '-j ACCEPT' %
  316
+                          (self.conf.metadata_ip, self.conf.metadata_port))]
  317
+
  318
+    def external_gateway_nat_rules(self, ex_gw_ip, internal_cidrs):
  319
+        rules = [('PREROUTING', '-s 0.0.0.0/0 -d 169.254.169.254/32 '
  320
+                  '-p tcp -m tcp --dport 80 -j DNAT '
  321
+                  '--to-destination %s:%s' %
  322
+                  (self.conf.metadata_ip, self.conf.metadata_port))]
  323
+        for cidr in internal_cidrs:
  324
+            rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr))
  325
+        return rules
  326
+
  327
+    def internal_network_added(self, ri, ex_gw_port, port_id,
  328
+                               internal_cidr, mac_address):
  329
+        interface_name = self.get_internal_device_name(port_id)
  330
+        if not ip_lib.device_exists(interface_name,
  331
+                                    root_helper=self.conf.root_helper,
  332
+                                    namespace=ri.ns_name()):
  333
+            self.driver.plug(None, port_id, interface_name, mac_address,
  334
+                             namespace=ri.ns_name())
  335
+
  336
+        self.driver.init_l3(interface_name, [internal_cidr],
  337
+                            namespace=ri.ns_name())
  338
+
  339
+        if ex_gw_port:
  340
+            ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
  341
+            for c, r in self.internal_network_nat_rules(ex_gw_ip,
  342
+                                                        internal_cidr):
  343
+                ri.iptables_manager.ipv4['nat'].add_rule(c, r)
  344
+            ri.iptables_manager.apply()
  345
+
  346
+    def internal_network_removed(self, ri, ex_gw_port, port_id, internal_cidr):
  347
+        interface_name = self.get_internal_device_name(port_id)
  348
+        if ip_lib.device_exists(interface_name,
  349
+                                root_helper=self.conf.root_helper,
  350
+                                namespace=ri.ns_name()):
  351
+            self.driver.unplug(interface_name)
  352
+
  353
+        if ex_gw_port:
  354
+            ex_gw_ip = ex_gw_port['fixed_ips'][0]['ip_address']
  355
+            for c, r in self.internal_network_nat_rules(ex_gw_ip,
  356
+                                                        internal_cidr):
  357
+                ri.iptables_manager.ipv4['nat'].remove_rule(c, r)
  358
+            ri.iptables_manager.apply()
  359
+
  360
+    def internal_network_nat_rules(self, ex_gw_ip, internal_cidr):
  361
+        return [('snat', '-s %s -j SNAT --to-source %s' %
  362
+                 (internal_cidr, ex_gw_ip)),
  363
+                ('POSTROUTING', '-s %s -d %s/32 -j ACCEPT' %
  364
+                 (internal_cidr, self.conf.metadata_ip))]
  365
+
  366
+    def floating_ip_added(self, ri, ex_gw_port, floating_ip, fixed_ip):
  367
+        ip_cidr = str(floating_ip) + '/32'
  368
+        interface_name = self.get_external_device_name(ex_gw_port['id'])
  369
+        device = ip_lib.IPDevice(interface_name, self.conf.root_helper,
  370
+                                 namespace=ri.ns_name())
  371
+
  372
+        if not ip_cidr in [addr['cidr'] for addr in device.addr.list()]:
  373
+            net = netaddr.IPNetwork(ip_cidr)
  374
+            device.addr.add(net.version, ip_cidr, str(net.broadcast))
  375
+
  376
+        for chain, rule in self.floating_forward_rules(floating_ip, fixed_ip):
  377
+            ri.iptables_manager.ipv4['nat'].add_rule(chain, rule)
  378
+        ri.iptables_manager.apply()
  379
+
  380
+    def floating_ip_removed(self, ri, ex_gw_port, floating_ip, fixed_ip):
  381
+        ip_cidr = str(floating_ip) + '/32'
  382
+        net = netaddr.IPNetwork(ip_cidr)
  383
+        interface_name = self.get_external_device_name(ex_gw_port['id'])
  384
+
  385
+        device = ip_lib.IPDevice(interface_name, self.conf.root_helper,
  386
+                                 namespace=ri.ns_name())
  387
+        device.addr.delete(net.version, ip_cidr)
  388
+
  389
+        for chain, rule in self.floating_forward_rules(floating_ip, fixed_ip):
  390
+            ri.iptables_manager.ipv4['nat'].remove_rule(chain, rule)
  391
+        ri.iptables_manager.apply()
  392
+
  393
+    def floating_forward_rules(self, floating_ip, fixed_ip):
  394
+        return [('PREROUTING', '-d %s -j DNAT --to %s' %
  395
+                 (floating_ip, fixed_ip)),
  396
+                ('OUTPUT', '-d %s -j DNAT --to %s' %
  397
+                 (floating_ip, fixed_ip)),
  398
+                ('float-snat', '-s %s -j SNAT --to %s' %
  399
+                 (fixed_ip, floating_ip))]
  400
+
  401
+
  402
+def main():
  403
+    conf = config.setup_conf()
  404
+    conf.register_opts(L3NATAgent.OPTS)
  405
+    conf.register_opts(interface.OPTS)
  406
+    conf(sys.argv)
  407
+    config.setup_logging(conf)
  408
+
  409
+    mgr = L3NATAgent(conf)
  410
+    mgr.daemon_loop()
  411
+
  412
+
  413
+if __name__ == '__main__':
  414
+    main()
24  quantum/api/v2/base.py
@@ -32,6 +32,7 @@
32 32
 
33 33
 FAULT_MAP = {exceptions.NotFound: webob.exc.HTTPNotFound,
34 34
              exceptions.InUse: webob.exc.HTTPConflict,
  35
+             exceptions.BadRequest: webob.exc.HTTPBadRequest,
35 36
              exceptions.ResourceExhausted: webob.exc.HTTPServiceUnavailable,
36 37
              exceptions.MacAddressGenerationFailure:
37 38
              webob.exc.HTTPServiceUnavailable,
@@ -132,8 +133,11 @@ def _verbose(request):
132 133
 
133 134
 
134 135
 class Controller(object):
135  
-    def __init__(self, plugin, collection, resource,
136  
-                 attr_info, allow_bulk=False):
  136
+
  137
+    def __init__(self, plugin, collection, resource, attr_info,
  138
+                 allow_bulk=False, member_actions=None):
  139
+        if member_actions is None:
  140
+            member_actions = []
137 141
         self._plugin = plugin
138 142
         self._collection = collection
139 143
         self._resource = resource
@@ -143,6 +147,7 @@ def __init__(self, plugin, collection, resource,
143 147
         self._policy_attrs = [name for (name, info) in self._attr_info.items()
144 148
                               if info.get('required_by_policy')]
145 149
         self._publisher_id = notifier_api.publisher_id('network')
  150
+        self._member_actions = member_actions
146 151
 
147 152
     def _is_native_bulk_supported(self):
148 153
         native_bulk_attr_name = ("_%s__native_bulk_support"
@@ -157,6 +162,7 @@ def _view(self, data, fields_to_strip=None):
157 162
         # make sure fields_to_strip is iterable
158 163
         if not fields_to_strip:
159 164
             fields_to_strip = []
  165
+
160 166
         return dict(item for item in data.iteritems()
161 167
                     if self._is_visible(item[0])
162 168
                     and not item[0] in fields_to_strip)
@@ -170,6 +176,14 @@ def _do_field_list(self, original_fields):
170 176
             original_fields.extend(self._policy_attrs)
171 177
         return original_fields, fields_to_add
172 178
 
  179
+    def __getattr__(self, name):
  180
+        if name in self._member_actions:
  181
+            def _handle_action(request, id, body=None):
  182
+                return getattr(self._plugin, name)(request.context, id, body)
  183
+            return _handle_action
  184
+        else:
  185
+            raise AttributeError
  186
+
173 187
     def _items(self, request, do_authz=False):
174 188
         """Retrieves and formats a list of elements of the requested entity"""
175 189
         # NOTE(salvatore-orlando): The following ensures that fields which
@@ -545,8 +559,10 @@ def _validate_network_tenant_ownership(self, request, resource_item):
545 559
             })
546 560
 
547 561
 
548  
-def create_resource(collection, resource, plugin, params, allow_bulk=False):
549  
-    controller = Controller(plugin, collection, resource, params, allow_bulk)
  562
+def create_resource(collection, resource, plugin, params, allow_bulk=False,
  563
+                    member_actions=None):
  564
+    controller = Controller(plugin, collection, resource, params, allow_bulk,
  565
+                            member_actions=member_actions)
550 566
 
551 567
     # NOTE(jkoelker) To anyone wishing to add "proper" xml support
552 568
     #                this is where you do it
10  quantum/common/exceptions.py
@@ -34,6 +34,10 @@ class QuantumException(OpenstackException):
34 34
     message = _("An unknown exception occurred.")
35 35
 
36 36
 
  37
+class BadRequest(QuantumException):
  38
+    message = _('Bad %(resource)s request: %(msg)s')
  39
+
  40
+
37 41
 class NotFound(QuantumException):
38 42
     pass
39 43
 
@@ -86,13 +90,13 @@ class NetworkInUse(InUse):
86 90
 
87 91
 class SubnetInUse(InUse):
88 92
     message = _("Unable to complete operation on subnet %(subnet_id)s. "
89  
-                "There is used by one or more ports.")
  93
+                "One or more ports have an IP allocation from this subnet.")
90 94
 
91 95
 
92 96
 class PortInUse(InUse):
93 97
     message = _("Unable to complete operation on port %(port_id)s "
94  
-                "for network %(net_id)s. The attachment '%(att_id)s"
95  
-                "is plugged into the logical port.")
  98
+                "for network %(net_id)s. Port already has an attached"
  99
+                "device %(device_id)s.")
96 100
 
97 101
 
98 102
 class MacAddressInUse(InUse):
3  quantum/db/db_base_plugin_v2.py
@@ -1067,11 +1067,12 @@ def get_port(self, context, id, fields=None, verbose=None):
1067 1067
         return self._make_port_dict(port, fields)
1068 1068
 
1069 1069
     def get_ports(self, context, filters=None, fields=None, verbose=None):
1070  
-        fixed_ips = filters.pop('fixed_ips', [])
  1070
+        fixed_ips = filters.pop('fixed_ips', []) if filters else []
1071 1071
         ports = self._get_collection(context, models_v2.Port,
1072 1072
                                      self._make_port_dict,
1073 1073
                                      filters=filters, fields=fields,
1074 1074
                                      verbose=verbose)
  1075
+
1075 1076
         if ports and fixed_ips:
1076 1077
             filtered_ports = []
1077 1078
             for port in ports:
546  quantum/db/l3_db.py
... ...
@@ -0,0 +1,546 @@
  1
+"""
  2
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
  3
+#
  4
+# Copyright 2012 Nicira Networks, Inc.  All rights reserved.
  5
+#
  6
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
  7
+#    not use this file except in compliance with the License. You may obtain
  8
+#    a copy of the License at
  9
+#
  10
+#         http://www.apache.org/licenses/LICENSE-2.0
  11
+#
  12
+#    Unless required by applicable law or agreed to in writing, software
  13
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15
+#    License for the specific language governing permissions and limitations
  16
+#    under the License.
  17
+#
  18
+# @author: Dan Wendlandt, Nicira, Inc
  19
+#
  20
+"""
  21
+
  22
+import logging
  23
+
  24
+import sqlalchemy as sa
  25
+from sqlalchemy import orm
  26
+from sqlalchemy.orm import exc
  27
+import webob.exc as w_exc
  28
+
  29
+from quantum.api.v2 import attributes
  30
+from quantum.common import exceptions as q_exc
  31
+from quantum.common import utils
  32
+from quantum.db import model_base
  33
+from quantum.db import models_v2
  34
+from quantum.extensions import l3
  35
+from quantum.openstack.common import cfg
  36
+
  37
+
  38
+LOG = logging.getLogger(__name__)
  39
+
  40
+l3_opts = [
  41
+    cfg.StrOpt('metadata_ip_address', default='127.0.0.1'),
  42
+    cfg.IntOpt('metadata_port', default=8775)
  43
+]
  44
+
  45
+# Register the configuration options
  46
+cfg.CONF.register_opts(l3_opts)
  47
+
  48
+DEVICE_OWNER_ROUTER_INTF = "network:router_interface"
  49
+DEVICE_OWNER_ROUTER_GW = "network:router_gateway"
  50
+DEVICE_OWNER_FLOATINGIP = "network:floatingip"
  51
+
  52
+
  53
+class Router(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
  54
+    """Represents a v2 quantum router."""
  55
+    name = sa.Column(sa.String(255))
  56
+    status = sa.Column(sa.String(16))
  57
+    admin_state_up = sa.Column(sa.Boolean)
  58
+    gw_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id',
  59
+                                                        ondelete="CASCADE"))
  60
+    gw_port = orm.relationship(models_v2.Port)
  61
+
  62
+
  63
+class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
  64
+    """Represents a floating IP, which may or many not be
  65
+       allocated to a tenant, and may or may not be associated with
  66
+       an internal port/ip address/router.
  67
+    """
  68
+    floating_ip_address = sa.Column(sa.String(64), nullable=False)
  69
+    floating_network_id = sa.Column(sa.String(36), nullable=False)
  70
+    floating_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'),
  71
+                                 nullable=False)
  72
+    fixed_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'))
  73
+    fixed_ip_address = sa.Column(sa.String(64))
  74
+    router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'))
  75
+
  76
+
  77
+class L3_NAT_db_mixin(l3.RouterPluginBase):
  78
+    """Mixin class to add L3/NAT router methods to db_plugin_base_v2"""
  79
+
  80
+    def _get_router(self, context, id, verbose=None):
  81
+        try:
  82
+            router = self._get_by_id(context, Router, id, verbose=verbose)
  83
+        except exc.NoResultFound:
  84
+            raise l3.RouterNotFound(router_id=id)
  85
+        except exc.MultipleResultsFound:
  86
+            LOG.error('Multiple routers match for %s' % id)
  87
+            raise l3.RouterNotFound(router_id=id)
  88
+        return router
  89
+
  90
+    def _make_router_dict(self, router, fields=None):
  91
+        res = {'id': router['id'],
  92
+               'name': router['name'],
  93
+               'tenant_id': router['tenant_id'],
  94
+               'admin_state_up': router['admin_state_up'],
  95
+               'status': router['status'],
  96
+               'external_gateway_info': None}
  97
+        if router['gw_port_id']:
  98
+            nw_id = router.gw_port['network_id']
  99
+            res['external_gateway_info'] = {'network_id': nw_id}
  100
+        return self._fields(res, fields)
  101
+
  102
+    def create_router(self, context, router):
  103
+        r = router['router']
  104
+        has_gw_info = False
  105
+        if 'external_gateway_info' in r:
  106
+            has_gw_info = True
  107
+            gw_info = r['external_gateway_info']
  108
+            del r['external_gateway_info']
  109
+        tenant_id = self._get_tenant_id_for_create(context, r)
  110
+        with context.session.begin(subtransactions=True):
  111
+            # pre-generate id so it will be available when
  112
+            # configuring external gw port
  113
+            router_db = Router(id=utils.str_uuid(),
  114
+                               tenant_id=tenant_id,
  115
+                               name=r['name'],
  116
+                               admin_state_up=r['admin_state_up'],
  117
+                               status="ACTIVE")
  118
+            context.session.add(router_db)
  119
+            if has_gw_info:
  120
+                self._update_router_gw_info(context, router_db['id'], gw_info)
  121
+        return self._make_router_dict(router_db)
  122
+
  123
+    def update_router(self, context, id, router):
  124
+        r = router['router']
  125
+        has_gw_info = False
  126
+        if 'external_gateway_info' in r:
  127
+            has_gw_info = True
  128
+            gw_info = r['external_gateway_info']
  129
+            del r['external_gateway_info']
  130
+        with context.session.begin(subtransactions=True):
  131
+            if has_gw_info:
  132
+                self._update_router_gw_info(context, id, gw_info)
  133
+            router_db = self._get_router(context, id)
  134
+            # Ensure we actually have something to update
  135
+            if r.keys():
  136
+                router_db.update(r)
  137
+        return self._make_router_dict(router_db)
  138
+
  139
+    def _update_router_gw_info(self, context, router_id, info):
  140
+        # TODO(salvatore-orlando): guarantee atomic behavior also across
  141
+        # operations that span beyond the model classes handled by this
  142
+        # class (e.g.: delete_port)
  143
+        router = self._get_router(context, router_id)
  144
+        gw_port = router.gw_port
  145
+
  146
+        network_id = info.get('network_id', None) if info else None
  147
+        if network_id:
  148
+            #FIXME(danwent): confirm net-id is valid external network
  149
+            self._get_network(context, network_id)
  150
+
  151
+        # figure out if we need to delete existing port
  152
+        if gw_port and gw_port['network_id'] != network_id:
  153
+            with context.session.begin(subtransactions=True):
  154
+                router.update({'gw_port_id': None})
  155
+                context.session.add(router)
  156
+            self.delete_port(context, gw_port['id'])
  157
+
  158
+        if network_id is not None and (gw_port is None or
  159
+                                       gw_port['network_id'] != network_id):
  160
+            # Port has no 'tenant-id', as it is hidden from user
  161
+            gw_port = self.create_port(context, {
  162
+                'port':
  163
+                {'network_id': network_id,
  164
+                 'mac_address': attributes.ATTR_NOT_SPECIFIED,
  165
+                 'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
  166
+                 'device_id': router_id,
  167
+                 'device_owner': DEVICE_OWNER_ROUTER_GW,
  168
+                 'admin_state_up': True,
  169
+                 'name': ''}})
  170
+
  171
+            if not len(gw_port['fixed_ips']):
  172
+                self.delete_port(context, gw_port['id'])
  173
+                msg = ('No IPs available for external network %s' %
  174
+                       network_id)
  175
+                raise q_exc.BadRequest(resource='router', msg=msg)
  176
+
  177
+            with context.session.begin(subtransactions=True):
  178
+                router.update({'gw_port_id': gw_port['id']})
  179
+                context.session.add(router)
  180
+
  181
+    def delete_router(self, context, id):
  182
+        with context.session.begin(subtransactions=True):
  183
+            router = self._get_router(context, id)
  184
+
  185
+            device_filter = {'device_id': [id],
  186
+                             'device_owner': [DEVICE_OWNER_ROUTER_INTF]}
  187
+            ports = self.get_ports(context, filters=device_filter)
  188
+            if ports:
  189
+                raise l3.RouterInUse(router_id=id)
  190
+            # NOTE(salvatore-orlando): gw port will be automatically deleted
  191
+            # thanks to cascading on the ORM relationship
  192
+            context.session.delete(router)
  193
+
  194
+    def get_router(self, context, id, fields=None, verbose=None):
  195
+        router = self._get_router(context, id, verbose=verbose)
  196
+        return self._make_router_dict(router, fields)
  197
+
  198
+    def get_routers(self, context, filters=None, fields=None, verbose=None):
  199
+        return self._get_collection(context, Router,
  200
+                                    self._make_router_dict,
  201
+                                    filters=filters, fields=fields,
  202
+                                    verbose=verbose)
  203
+
  204
+    def _check_for_dup_router_subnet(self, context, router_id,
  205
+                                     network_id, subnet_id):
  206
+        try:
  207
+            rport_qry = context.session.query(models_v2.Port)
  208
+            rports = rport_qry.filter_by(
  209
+                device_id=router_id,
  210
+                device_owner=DEVICE_OWNER_ROUTER_INTF,
  211
+                network_id=network_id).all()
  212
+            # its possible these ports on on the same network, but
  213
+            # different subnet
  214
+            for p in rports:
  215
+                for ip in p['fixed_ips']:
  216
+                    if ip['subnet_id'] == subnet_id:
  217
+                        msg = ("Router already has a port on subnet %s"
  218
+                               % subnet_id)
  219
+                        raise q_exc.BadRequest(resource='router', msg=msg)
  220
+
  221
+        except exc.NoResultFound:
  222
+            pass
  223
+
  224
+    def add_router_interface(self, context, router_id, interface_info):
  225
+        # make sure router exists - will raise if not
  226
+        self._get_router(context, router_id)
  227
+        if not interface_info:
  228
+            msg = "Either subnet_id or port_id must be specified"
  229
+            raise q_exc.BadRequest(resource='router', msg=msg)
  230
+
  231
+        if 'port_id' in interface_info:
  232
+            if 'subnet_id' in interface_info:
  233
+                msg = "cannot specify both subnet-id and port-id"
  234
+                raise q_exc.BadRequest(resource='router', msg=msg)
  235
+
  236
+            port = self._get_port(context, interface_info['port_id'])
  237
+            if port['device_id']:
  238
+                raise q_exc.PortInUse(net_id=port['network_id'],
  239
+                                      port_id=port['id'],
  240
+                                      device_id=port['device_id'])
  241
+            fixed_ips = [ip for ip in port['fixed_ips']]
  242
+            if len(fixed_ips) != 1:
  243
+                msg = 'Router port must have exactly one fixed IP'
  244
+                raise q_exc.BadRequest(resource='router', msg=msg)
  245
+            self._check_for_dup_router_subnet(context, router_id,
  246
+                                              port['network_id'],
  247
+                                              fixed_ips[0]['subnet_id'])
  248
+            port.update({'device_id': router_id,
  249
+                         'device_owner': DEVICE_OWNER_ROUTER_INTF})
  250
+        elif 'subnet_id' in interface_info:
  251
+            subnet_id = interface_info['subnet_id']
  252
+            subnet = self._get_subnet(context, subnet_id)
  253
+            # Ensure the subnet has a gateway
  254
+            if not subnet['gateway_ip']:
  255
+                msg = 'Subnet for router interface must have a gateway IP'
  256
+                raise q_exc.BadRequest(resource='router', msg=msg)
  257
+            self._check_for_dup_router_subnet(context, router_id,
  258
+                                              subnet['network_id'], subnet_id)
  259
+            fixed_ip = {'ip_address': subnet['gateway_ip'],
  260
+                        'subnet_id': subnet['id']}
  261
+            port = self.create_port(context, {
  262
+                'port':
  263
+                {'network_id': subnet['network_id'],
  264
+                 'fixed_ips': [fixed_ip],
  265
+                 'mac_address': attributes.ATTR_NOT_SPECIFIED,
  266
+                 'admin_state_up': True,
  267
+                 'device_id': router_id,
  268
+                 'device_owner': DEVICE_OWNER_ROUTER_INTF,
  269
+                 'name': ''}})
  270
+        return {'port_id': port['id'],
  271
+                'subnet_id': port['fixed_ips'][0]['subnet_id']}
  272
+
  273
+    def remove_router_interface(self, context, router_id, interface_info):
  274
+        # make sure router exists
  275
+        router = self._get_router(context, router_id)
  276
+
  277
+        if not interface_info:
  278
+            msg = "Either subnet_id or port_id must be specified"
  279
+            raise q_exc.BadRequest(resource='router', msg=msg)
  280
+        if 'port_id' in interface_info:
  281
+            port_db = self._get_port(context, interface_info['port_id'])
  282
+            if 'subnet_id' in interface_info:
  283
+                port_subnet_id = port_db['fixed_ips'][0]['subnet_id']
  284
+                if port_subnet_id != interface_info['subnet_id']:
  285
+                    raise w_exc.HTTPConflict("subnet_id %s on port does not "
  286
+                                             "match requested one (%s)"
  287
+                                             % (port_subnet_id,
  288
+                                                interface_info['subnet_id']))
  289
+            if port_db['device_id'] != router_id:
  290
+                raise w_exc.HTTPConflict("port_id %s not used by router" %
  291
+                                         port_db['id'])
  292
+            self.delete_port(context, port_db['id'])
  293
+        elif 'subnet_id' in interface_info:
  294
+            subnet_id = interface_info['subnet_id']
  295
+            subnet = self._get_subnet(context, subnet_id)
  296
+            found = False
  297
+
  298
+            try:
  299
+                rport_qry = context.session.query(models_v2.Port)
  300
+                ports = rport_qry.filter_by(
  301
+                    device_id=router_id,
  302
+                    device_owner=DEVICE_OWNER_ROUTER_INTF,
  303
+                    network_id=subnet['network_id']).all()
  304
+
  305
+                for p in ports:
  306
+                    if p['fixed_ips'][0]['subnet_id'] == subnet_id:
  307
+                        self.delete_port(context, p['id'])
  308
+                        found = True
  309
+                        break
  310
+            except exc.NoResultFound:
  311
+                pass
  312
+
  313
+            if not found:
  314
+                raise w_exc.HTTPNotFound("Router %(router_id)s has no "
  315
+                                         "interface on subnet %(subnet_id)s"
  316
+                                         % locals())
  317
+
  318
+    def _get_floatingip(self, context, id, verbose=None):
  319
+        try:
  320
+            floatingip = self._get_by_id(context, FloatingIP, id,
  321
+                                         verbose=verbose)
  322
+        except exc.NoResultFound:
  323
+            raise l3.FloatingIPNotFound(floatingip_id=id)
  324
+        except exc.MultipleResultsFound:
  325
+            LOG.error('Multiple floating ips match for %s' % id)
  326
+            raise l3.FloatingIPNotFound(floatingip_id=id)
  327
+        return floatingip
  328
+
  329
+    def _make_floatingip_dict(self, floatingip, fields=None):
  330
+        res = {'id': floatingip['id'],
  331
+               'tenant_id': floatingip['tenant_id'],
  332
+               'floating_ip_address': floatingip['floating_ip_address'],
  333
+               'floating_network_id': floatingip['floating_network_id'],
  334
+               'router_id': floatingip['router_id'],
  335
+               'port_id': floatingip['fixed_port_id'],
  336
+               'fixed_ip_address': floatingip['fixed_ip_address']}
  337
+        return self._fields(res, fields)
  338
+
  339
+    def _get_router_for_internal_subnet(self, context, internal_port,
  340
+                                        internal_subnet_id):
  341
+        subnet_db = self._get_subnet(context, internal_subnet_id)
  342
+        if not subnet_db['gateway_ip']:
  343
+            msg = ('Cannot add floating IP to port on subnet %s '
  344
+                   'which has no gateway_ip' % internal_subnet_id)
  345
+            raise q_exc.BadRequest(resource='floatingip', msg=msg)
  346
+
  347
+        #FIXME(danwent): can do join, but cannot use standard F-K syntax?
  348
+        # just do it inefficiently for now
  349
+        port_qry = context.session.query(models_v2.Port)
  350
+        ports = port_qry.filter_by(network_id=internal_port['network_id'])
  351
+        for p in ports:
  352
+            ips = [ip['ip_address'] for ip in p['fixed_ips']]
  353
+            if len(ips) != 1:
  354
+                continue
  355
+            fixed = p['fixed_ips'][0]
  356
+            if (fixed['ip_address'] == subnet_db['gateway_ip'] and
  357
+                    fixed['subnet_id'] == internal_subnet_id):
  358
+                router_qry = context.session.query(Router)
  359
+                try:
  360
+                    router = router_qry.filter_by(id=p['device_id']).one()
  361
+                    #TODO(danwent): confirm that this router has a floating
  362
+                    # ip enabled gateway with support for this floating IP
  363
+                    # network
  364
+                    return router['id']
  365
+                except exc.NoResultFound:
  366
+                    pass
  367
+
  368
+        raise l3.ExternalGatewayForFloatingIPNotFound(
  369
+            subnet_id=internal_subnet_id,
  370
+            port_id=internal_port['id'])
  371
+
  372
+    def get_assoc_data(self, context, fip):
  373
+        """When a floating IP is associated with an internal port,
  374
+        we need to extract/determine some data associated with the
  375
+        internal port, including the internal_ip_address, and router_id.
  376
+        We also need to confirm that this internal port is owned by the
  377
+        tenant who owns the floating IP.
  378
+        """
  379
+        internal_port = self._get_port(context, fip['port_id'])
  380
+        if not internal_port['tenant_id'] == fip['tenant_id']:
  381
+            msg = ('Port %s is associated with a different tenant'
  382
+                   'and therefore cannot be found to floating IP %s'
  383
+                   % (fip['port_id'], fip['id']))
  384
+            raise q_exc.BadRequest(resource='floating', msg=msg)
  385
+
  386
+        internal_subnet_id = None
  387
+        if 'fixed_ip_address' in fip and fip['fixed_ip_address']:
  388
+            internal_ip_address = fip['fixed_ip_address']
  389
+            for ip in internal_port['fixed_ips']:
  390
+                if ip['ip_address'] == internal_ip_address:
  391
+                    internal_subnet_id = ip['subnet_id']
  392
+            if not internal_subnet_id:
  393
+                msg = ('Port %s does not have fixed ip %s' %
  394
+                       (internal_port['id'], internal_ip_address))
  395
+                raise q_exc.BadRequest(resource='floatingip', msg=msg)
  396
+        else:
  397
+            ips = [ip['ip_address'] for ip in internal_port['fixed_ips']]
  398
+            if len(ips) == 0:
  399
+                msg = ('Cannot add floating IP to port %s that has'
  400
+                       'no fixed IP addresses' % internal_port['id'])
  401
+                raise q_exc.BadRequest(resource='floatingip', msg=msg)
  402
+            if len(ips) > 1:
  403
+                msg = ('Port %s has multiple fixed IPs.  Must provide'
  404
+                       ' a specific IP when assigning a floating IP' %
  405
+                       internal_port['id'])
  406
+                raise q_exc.BadRequest(resource='floatingip', msg=msg)
  407
+            internal_ip_address = internal_port['fixed_ips'][0]['ip_address']
  408
+            internal_subnet_id = internal_port['fixed_ips'][0]['subnet_id']
  409
+
  410
+        router_id = self._get_router_for_internal_subnet(context,
  411
+                                                         internal_port,
  412
+                                                         internal_subnet_id)
  413
+        return (fip['port_id'], internal_ip_address, router_id)
  414
+
  415
+    def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
  416
+        port_id = internal_ip_address = router_id = None
  417
+        if 'port_id' in fip and fip['port_id']:
  418
+            port_qry = context.session.query(FloatingIP)
  419
+            try:
  420
+                port_qry.filter_by(fixed_port_id=fip['port_id']).one()
  421
+                raise l3.FloatingIPPortAlreadyAssociated(
  422
+                    port_id=fip['port_id'])
  423
+            except exc.NoResultFound:
  424
+                pass
  425
+            port_id, internal_ip_address, router_id = self.get_assoc_data(
  426
+                context,
  427
+                fip)
  428
+            # Assign external address for floating IP
  429
+            # fetch external gateway port
  430
+            ports = self.get_ports(context, filters={'device_id': [router_id]})
  431
+            if not ports:
  432
+                msg = ("The router %s needed for association a floating ip "
  433
+                       "to port %s does not have an external gateway"
  434
+                       % (router_id, port_id))
  435
+                raise q_exc.BadRequest(resource='floatingip', msg=msg)
  436
+            # retrieve external subnet identifier
  437
+            # NOTE: by design we cannot have more than 1 IP on ext gw port
  438
+            ext_subnet_id = ports[0]['fixed_ips'][0]['subnet_id']
  439
+            # ensure floating ip address is taken from this subnet
  440
+            for fixed_ip in external_port['fixed_ips']:
  441
+                if fixed_ip['subnet_id'] == ext_subnet_id:
  442
+                    floatingip_db.update(
  443
+                        {'floating_ip_address': fixed_ip['ip_address'],
  444
+                         'floating_port_id': external_port['id']})
  445
+        else:
  446
+            # fallback choice (first IP address on external port)
  447
+            floatingip_db.update(
  448
+                {'floating_ip_address':
  449
+                    external_port['fixed_ips'][0]['ip_address'],
  450
+                 'floating_port_id':
  451
+                    external_port['id']})
  452
+
  453
+        floatingip_db.update({'fixed_ip_address': internal_ip_address,
  454
+                              'fixed_port_id': port_id,
  455
+                              'router_id': router_id})
  456
+
  457
+    def create_floatingip(self, context, floatingip):
  458
+        fip = floatingip['floatingip']
  459
+        tenant_id = self._get_tenant_id_for_create(context, fip)
  460
+        fip_id = utils.str_uuid()
  461
+
  462
+        #TODO(danwent): validate that network_id is valid floatingip-network
  463
+
  464
+        # This external port is never exposed to the tenant.
  465
+        # it is used purely for internal system and admin use when
  466
+        # managing floating IPs.
  467
+        external_port = self.create_port(context, {
  468
+            'port':
  469
+            {'network_id': fip['floating_network_id'],
  470
+             'mac_address': attributes.ATTR_NOT_SPECIFIED,
  471
+             'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
  472
+             'admin_state_up': True,
  473
+             'device_id': fip_id,
  474
+             'device_owner': DEVICE_OWNER_FLOATINGIP,
  475
+             'name': ''}})
  476
+        # Ensure IP addresses are allocated on external port
  477
+        if not external_port['fixed_ips']:
  478
+            msg = "Unable to find any IP address on external network"
  479
+            # remove the external port
  480
+            self.delete_port(context, external_port['id'])
  481
+            raise q_exc.BadRequest(resource='floatingip', msg=msg)
  482
+
  483
+        try:
  484
+            with context.session.begin(subtransactions=True):
  485
+                floatingip_db = FloatingIP(
  486
+                    id=fip_id,
  487
+                    tenant_id=tenant_id,
  488
+                    floating_network_id=fip['floating_network_id'])
  489
+                fip['tenant_id'] = tenant_id
  490
+                # Update association with internal port
  491
+                # and define external IP address
  492
+                self._update_fip_assoc(context, fip,
  493
+                                       floatingip_db, external_port)
  494
+                context.session.add(floatingip_db)
  495
+        # TODO(salvatore-orlando): Avoid broad catch
  496
+        # Maybe by introducing base class for L3 exceptions
  497
+        except Exception:
  498
+            LOG.exception("Floating IP association failed")
  499
+            # Remove the port created for internal purposes
  500
+            self.delete_port(context, external_port['id'])
  501
+            raise
  502
+
  503
+        return self._make_floatingip_dict(floatingip_db)
  504
+
  505
+    def update_floatingip(self, context, id, floatingip):
  506
+        fip = floatingip['floatingip']
  507
+        with context.session.begin(subtransactions=True):
  508
+            floatingip_db = self._get_floatingip(context, id)
  509
+            fip['tenant_id'] = floatingip_db['tenant_id']
  510
+            fip['id'] = id
  511
+            fip_port_id = floatingip_db['floating_port_id']
  512
+            self._update_fip_assoc(context, fip, floatingip_db,
  513
+                                   self.get_port(context, fip_port_id))
  514
+        return self._make_floatingip_dict(floatingip_db)
  515
+
  516
+    def delete_floatingip(self, context, id):
  517
+        floatingip = self._get_floatingip(context, id)
  518
+        with context.session.begin(subtransactions=True):
  519
+            context.session.delete(floatingip)
  520
+        self.delete_port(context, floatingip['floating_port_id'])
  521
+
  522
+    def get_floatingip(self, context, id, fields=None, verbose=None):
  523
+        floatingip = self._get_floatingip(context, id, verbose=verbose)
  524
+        return self._make_floatingip_dict(floatingip, fields)
  525
+
  526
+    def get_floatingips(self, context, filters=None, fields=None,
  527
+                        verbose=None):
  528
+        return self._get_collection(context, FloatingIP,
  529
+                                    self._make_floatingip_dict,
  530
+                                    filters=filters, fields=fields,
  531
+                                    verbose=verbose)
  532
+
  533
+    def disassociate_floatingips(self, context, port_id):
  534
+        with context.session.begin(subtransactions=True):
  535
+            try:
  536
+                fip_qry = context.session.query(FloatingIP)
  537
+                floating_ip = fip_qry.filter_by(fixed_port_id=port_id).one()
  538
+                floating_ip.update({'fixed_port_id': None,
  539
+                                    'fixed_ip_address': None,
  540
+                                    'router_id': None})
  541
+            except exc.NoResultFound:
  542
+                return
  543
+            except exc.MultipleResultsFound:
  544
+                # should never happen
  545
+                raise Exception('Multiple floating IPs found for port %s'
  546
+                                % port_id)
210  quantum/extensions/l3.py
... ...
@@ -0,0 +1,210 @@
  1
+"""
  2
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
  3
+#
  4
+# Copyright 2012 Nicira Networks, Inc.  All rights reserved.
  5
+#
  6
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
  7
+#    not use this file except in compliance with the License. You may obtain
  8
+#    a copy of the License at
  9
+#
  10
+#         http://www.apache.org/licenses/LICENSE-2.0
  11
+#
  12
+#    Unless required by applicable law or agreed to in writing, software
  13
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15
+#    License for the specific language governing permissions and limitations
  16
+#    under the License.
  17
+#
  18
+# @author: Dan Wendlandt, Nicira, Inc
  19
+#
  20
+"""
  21
+
  22
+from abc import abstractmethod
  23
+
  24
+from quantum.api.v2 import attributes as attr
  25
+from quantum.api.v2 import base
  26
+from quantum.common import exceptions as qexception
  27
+from quantum.extensions import extensions
  28
+from quantum import manager
  29
+from quantum.openstack.common import cfg
  30
+from quantum import quota
  31
+
  32
+
  33
+# L3 Exceptions
  34
+class RouterNotFound(qexception.NotFound):
  35
+    message = _("Router %(router_id)s could not be found")
  36
+
  37
+
  38
+class RouterInUse(qexception.InUse):
  39
+    message = _("Router %(router_id)s still has active ports")
  40
+
  41
+
  42
+class FloatingIPNotFound(qexception.NotFound):