]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add database relationship between router and ports
authorMark McClain <mmcclain@yahoo-inc.com>
Wed, 8 Oct 2014 18:49:20 +0000 (18:49 +0000)
committerKyle Mestery <mestery@mestery.com>
Wed, 8 Oct 2014 23:57:11 +0000 (23:57 +0000)
Add an explicit schema relationship between a router and its ports. This
change ensures referential integrity among the entities and prevents orphaned
ports.

Change-Id: I09e8a694cdff7f64a642a39b45cbd12422132806
Closes-Bug: #1378866
(cherry picked from commit 93012915a3445a8ac8a0b30b702df30febbbb728)

neutron/db/l3_db.py
neutron/db/l3_dvr_db.py
neutron/db/migration/alembic_migrations/versions/544673ac99ab_add_router_port_table.py [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/HEAD

index 709c99cb0dd8fd1007ec0edad54bf38bfad059d7..0f8a56c0efb24a73ba7a242df101b83be6051c4e 100644 (file)
@@ -47,6 +47,26 @@ API_TO_DB_COLUMN_MAP = {'port_id': 'fixed_port_id'}
 CORE_ROUTER_ATTRS = ('id', 'name', 'tenant_id', 'admin_state_up', 'status')
 
 
+class RouterPort(model_base.BASEV2):
+    router_id = sa.Column(
+        sa.String(36),
+        sa.ForeignKey('routers.id', ondelete="CASCADE"),
+        primary_key=True)
+    port_id = sa.Column(
+        sa.String(36),
+        sa.ForeignKey('ports.id', ondelete="CASCADE"),
+        primary_key=True)
+    # The port_type attribute is redundant as the port table already specifies
+    # it in DEVICE_OWNER.However, this redundancy enables more efficient
+    # queries on router ports, and also prevents potential error-prone
+    # conditions which might originate from users altering the DEVICE_OWNER
+    # property of router ports.
+    port_type = sa.Column(sa.String(255))
+    port = orm.relationship(
+        models_v2.Port,
+        backref=orm.backref('routerport', uselist=False, cascade="all,delete"))
+
+
 class Router(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
     """Represents a v2 neutron router."""
 
@@ -55,6 +75,10 @@ class Router(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
     admin_state_up = sa.Column(sa.Boolean)
     gw_port_id = sa.Column(sa.String(36), sa.ForeignKey('ports.id'))
     gw_port = orm.relationship(models_v2.Port, lazy='joined')
+    attached_ports = orm.relationship(
+        RouterPort,
+        backref='router',
+        lazy='dynamic')
 
 
 class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
@@ -76,6 +100,7 @@ class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
     # aysnchronous backend is unavailable when the floating IP is disassociated
     last_known_router_id = sa.Column(sa.String(36))
     status = sa.Column(sa.String(16))
+    router = orm.relationship(Router, backref='floating_ips')
 
 
 class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
@@ -259,7 +284,13 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
         with context.session.begin(subtransactions=True):
             router.gw_port = self._core_plugin._get_port(context.elevated(),
                                                          gw_port['id'])
+            router_port = RouterPort(
+                router_id=router.id,
+                port_id=gw_port['id'],
+                port_type=DEVICE_OWNER_ROUTER_GW
+            )
             context.session.add(router)
+            context.session.add(router_port)
 
     def _validate_gw_info(self, context, gw_port, info):
         network_id = info['network_id'] if info else None
@@ -281,11 +312,12 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
                 raise l3.RouterExternalGatewayInUseByFloatingIp(
                     router_id=router_id, net_id=router.gw_port['network_id'])
             with context.session.begin(subtransactions=True):
-                gw_port_id = router.gw_port['id']
+                gw_port = router.gw_port
                 router.gw_port = None
                 context.session.add(router)
+                context.session.expire(gw_port)
             self._core_plugin.delete_port(
-                admin_ctx, gw_port_id, l3_port_check=False)
+                admin_ctx, gw_port['id'], l3_port_check=False)
 
     def _create_gw_port(self, context, router_id, router, new_network):
         new_valid_gw_port_attachment = (
@@ -295,7 +327,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
             subnets = self._core_plugin._get_subnets_by_network(context,
                                                                 new_network)
             for subnet in subnets:
-                self._check_for_dup_router_subnet(context, router_id,
+                self._check_for_dup_router_subnet(context, router,
                                                   new_network, subnet['id'],
                                                   subnet['cidr'])
             self._create_router_gw_port(context, router, new_network)
@@ -317,11 +349,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
             admin_ctx, filters={'router_id': [router_id]}):
             raise l3.RouterInUse(router_id=router_id)
         device_owner = self._get_device_owner(context, router)
-        device_filter = {'device_id': [router_id],
-                         'device_owner': [device_owner]}
-        port_count = self._core_plugin.get_ports_count(
-            admin_ctx, filters=device_filter)
-        if port_count:
+        if any(rp.port_type == device_owner
+               for rp in router.attached_ports.all()):
             raise l3.RouterInUse(router_id=router_id)
         return router
 
@@ -335,18 +364,13 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
             if vpnservice:
                 vpnservice.check_router_in_use(context, id)
 
+            router_ports = router.attached_ports.all()
+            # Set the router's gw_port to None to avoid a constraint violation.
+            router.gw_port = None
+            for rp in router_ports:
+                self._core_plugin._delete_port(context.elevated(), rp.port.id)
             context.session.delete(router)
 
-            # Delete the gw port after the router has been removed to
-            # avoid a constraint violation.
-            device_filter = {'device_id': [id],
-                             'device_owner': [DEVICE_OWNER_ROUTER_GW]}
-            ports = self._core_plugin.get_ports(context.elevated(),
-                                                filters=device_filter)
-            if ports:
-                self._core_plugin._delete_port(context.elevated(),
-                                               ports[0]['id'])
-
     def get_router(self, context, id, fields=None):
         router = self._get_router(context, id)
         return self._make_router_dict(router, fields)
@@ -367,15 +391,13 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
         return self._get_collection_count(context, Router,
                                           filters=filters)
 
-    def _check_for_dup_router_subnet(self, context, router_id,
+    def _check_for_dup_router_subnet(self, context, router,
                                      network_id, subnet_id, subnet_cidr):
         try:
-            rport_qry = context.session.query(models_v2.Port)
-            rports = rport_qry.filter_by(device_id=router_id)
             # It's possible these ports are on the same network, but
             # different subnets.
             new_ipnet = netaddr.IPNetwork(subnet_cidr)
-            for p in rports:
+            for p in (rp.port for rp in router.attached_ports):
                 for ip in p['fixed_ips']:
                     if ip['subnet_id'] == subnet_id:
                         msg = (_("Router already has a port on subnet %s")
@@ -415,7 +437,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
             raise n_exc.BadRequest(resource='router', msg=msg)
         return port_id_specified, subnet_id_specified
 
-    def _add_interface_by_port(self, context, router_id, port_id, owner):
+    def _add_interface_by_port(self, context, router, port_id, owner):
         with context.session.begin(subtransactions=True):
             port = self._core_plugin._get_port(context, port_id)
             if port['device_id']:
@@ -428,19 +450,19 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
                 raise n_exc.BadRequest(resource='router', msg=msg)
             subnet_id = fixed_ips[0]['subnet_id']
             subnet = self._core_plugin._get_subnet(context, subnet_id)
-            self._check_for_dup_router_subnet(context, router_id,
+            self._check_for_dup_router_subnet(context, router,
                                               port['network_id'],
                                               subnet['id'],
                                               subnet['cidr'])
-            port.update({'device_id': router_id, 'device_owner': owner})
+            port.update({'device_id': router.id, 'device_owner': owner})
             return port
 
-    def _add_interface_by_subnet(self, context, router_id, subnet_id, owner):
+    def _add_interface_by_subnet(self, context, router, subnet_id, owner):
         subnet = self._core_plugin._get_subnet(context, subnet_id)
         if not subnet['gateway_ip']:
             msg = _('Subnet for router interface must have a gateway IP')
             raise n_exc.BadRequest(resource='router', msg=msg)
-        self._check_for_dup_router_subnet(context, router_id,
+        self._check_for_dup_router_subnet(context, router,
                                           subnet['network_id'],
                                           subnet_id,
                                           subnet['cidr'])
@@ -453,7 +475,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
              'fixed_ips': [fixed_ip],
              'mac_address': attributes.ATTR_NOT_SPECIFIED,
              'admin_state_up': True,
-             'device_id': router_id,
+             'device_id': router.id,
              'device_owner': owner,
              'name': ''}})
 
@@ -468,18 +490,27 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
         }
 
     def add_router_interface(self, context, router_id, interface_info):
+        router = self._get_router(context, router_id)
         add_by_port, add_by_sub = self._validate_interface_info(interface_info)
         device_owner = self._get_device_owner(context, router_id)
 
         if add_by_port:
             port = self._add_interface_by_port(
-                context, router_id, interface_info['port_id'], device_owner)
+                context, router, interface_info['port_id'], device_owner)
         elif add_by_sub:
             port = self._add_interface_by_subnet(
-                context, router_id, interface_info['subnet_id'], device_owner)
+                context, router, interface_info['subnet_id'], device_owner)
+
+        with context.session.begin(subtransactions=True):
+            router_port = RouterPort(
+                port_id=port['id'],
+                router_id=router.id,
+                port_type=device_owner
+            )
+            context.session.add(router_port)
 
         return self._make_router_interface_info(
-            router_id, port['tenant_id'], port['id'],
+            router.id, port['tenant_id'], port['id'],
             port['fixed_ips'][0]['subnet_id'])
 
     def _confirm_router_interface_not_in_use(self, context, router_id,
@@ -494,9 +525,15 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
 
     def _remove_interface_by_port(self, context, router_id,
                                   port_id, subnet_id, owner):
-        port_db = self._core_plugin._get_port(context, port_id)
-        if not (port_db['device_owner'] == owner and
-                port_db['device_id'] == router_id):
+        qry = context.session.query(RouterPort)
+        qry = qry.filter_by(
+            port_id=port_id,
+            router_id=router_id,
+            port_type=owner
+        )
+        try:
+            port_db = qry.one().port
+        except exc.NoResultFound:
             raise l3.RouterInterfaceNotFound(router_id=router_id,
                                              port_id=port_id)
         port_subnet_id = port_db['fixed_ips'][0]['subnet_id']
@@ -517,11 +554,12 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
         subnet = self._core_plugin._get_subnet(context, subnet_id)
 
         try:
-            rport_qry = context.session.query(models_v2.Port)
-            ports = rport_qry.filter_by(
-                device_id=router_id,
-                device_owner=owner,
-                network_id=subnet['network_id'])
+            rport_qry = context.session.query(models_v2.Port).join(RouterPort)
+            ports = rport_qry.filter(
+                RouterPort.router_id == router_id,
+                RouterPort.port_type == owner,
+                models_v2.Port.network_id == subnet['network_id']
+            )
 
             for p in ports:
                 if p['fixed_ips'][0]['subnet_id'] == subnet_id:
@@ -570,10 +608,12 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
         return self._fields(res, fields)
 
     def _get_interface_ports_for_network(self, context, network_id):
-        router_intf_qry = context.session.query(models_v2.Port)
-        return router_intf_qry.filter_by(
-            network_id=network_id,
-            device_owner=DEVICE_OWNER_ROUTER_INTF)
+        router_intf_qry = context.session.query(RouterPort)
+        router_intf_qry = router_intf_qry.join(models_v2.Port)
+        return router_intf_qry.filter(
+            models_v2.Port.network_id == network_id,
+            RouterPort.port_type == DEVICE_OWNER_ROUTER_INTF
+        )
 
     def _get_router_for_floatingip(self, context, internal_port,
                                    internal_subnet_id,
@@ -588,16 +628,16 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
         router_intf_ports = self._get_interface_ports_for_network(
             context, internal_port['network_id'])
 
-        for intf_p in router_intf_ports:
-            if intf_p['fixed_ips'][0]['subnet_id'] == internal_subnet_id:
-                router_id = intf_p['device_id']
-                router_gw_qry = context.session.query(models_v2.Port)
-                has_gw_port = router_gw_qry.filter_by(
-                    network_id=external_network_id,
-                    device_id=router_id,
-                    device_owner=DEVICE_OWNER_ROUTER_GW).count()
-                if has_gw_port:
-                    return router_id
+        # This joins on port_id so is not a cross-join
+        routerport_qry = router_intf_ports.join(models_v2.IPAllocation)
+        routerport_qry = routerport_qry.filter(
+            models_v2.IPAllocation.subnet_id == internal_subnet_id
+        )
+
+        router_port = routerport_qry.first()
+
+        if router_port and router_port.router.gw_port:
+            return router_port.router.id
 
         raise l3.ExternalGatewayForFloatingIPNotFound(
             subnet_id=internal_subnet_id,
@@ -936,9 +976,16 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
         device_owners = device_owners or [DEVICE_OWNER_ROUTER_INTF]
         if not router_ids:
             return []
-        filters = {'device_id': router_ids,
-                   'device_owner': device_owners}
-        interfaces = self._core_plugin.get_ports(context, filters)
+        qry = context.session.query(RouterPort)
+        qry = qry.filter(
+            Router.id.in_(router_ids),
+            RouterPort.port_type.in_(device_owners)
+        )
+
+        # TODO(markmcclain): This is suboptimal but was left to reduce
+        # changeset size since it is late in cycle
+        ports = [rp.port.id for rp in qry]
+        interfaces = self._core_plugin.get_ports(context, {'id': ports})
         if interfaces:
             self._populate_subnet_for_ports(context, interfaces)
         return interfaces
index 4b605ffbf97f12b717627bf0711b801a2dbc98fe..b6e826b8c1dad9cd72e50f0e997cb62cc1d1a898 100644 (file)
@@ -81,13 +81,11 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
         self, context, router_id, router_db, data, gw_info):
         """Update the model to support the dvr case of a router."""
         if not attributes.is_attr_set(gw_info) and data.get('distributed'):
-            admin_ctx = context.elevated()
-            filters = {'device_id': [router_id],
-                       'device_owner': [l3_const.DEVICE_OWNER_ROUTER_INTF]}
-            ports = self._core_plugin.get_ports(admin_ctx, filters=filters)
-            for p in ports:
-                port_db = self._core_plugin._get_port(admin_ctx, p['id'])
-                port_db.update({'device_owner': DEVICE_OWNER_DVR_INTERFACE})
+            old_owner = l3_const.DEVICE_OWNER_ROUTER_INTF
+            new_owner = DEVICE_OWNER_DVR_INTERFACE
+            for rp in router_db.attached_ports.filter_by(port_type=old_owner):
+                rp.port_type = new_owner
+                rp.port.device_owner = new_owner
 
     def _update_router_db(self, context, router_id, data, gw_info):
         with context.session.begin(subtransactions=True):
@@ -119,7 +117,7 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
                                     router, new_network)
         if router.extra_attributes.distributed and router.gw_port:
             snat_p_list = self.create_snat_intf_ports_if_not_exists(
-                context.elevated(), router['id'])
+                context.elevated(), router)
             if not snat_p_list:
                 LOG.debug("SNAT interface ports not created: %s", snat_p_list)
 
@@ -134,12 +132,15 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
                      self)._get_device_owner(context, router)
 
     def _get_interface_ports_for_network(self, context, network_id):
-        router_intf_qry = (context.session.query(models_v2.Port).
-                           filter_by(network_id=network_id))
-        return (router_intf_qry.
-                filter(models_v2.Port.device_owner.in_(
-                       [l3_const.DEVICE_OWNER_ROUTER_INTF,
-                        DEVICE_OWNER_DVR_INTERFACE])))
+        router_intf_qry = context.session.query(l3_db.RouterPort)
+        router_intf_qry = router_intf_qry.join(models_v2.Port)
+
+        return router_intf_qry.filter(
+            models_v2.Port.network_id == network_id,
+            l3_db.RouterPort.port_type.in_(
+                [l3_const.DEVICE_OWNER_ROUTER_INTF, DEVICE_OWNER_DVR_INTERFACE]
+            )
+        )
 
     def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
         previous_router_id = floatingip_db.router_id
@@ -208,14 +209,22 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
 
         if add_by_port:
             port = self._add_interface_by_port(
-                context, router_id, interface_info['port_id'], device_owner)
+                context, router, interface_info['port_id'], device_owner)
         elif add_by_sub:
             port = self._add_interface_by_subnet(
-                context, router_id, interface_info['subnet_id'], device_owner)
+                context, router, interface_info['subnet_id'], device_owner)
+
+        with context.session.begin(subtransactions=True):
+            router_port = l3_db.RouterPort(
+                port_id=port['id'],
+                router_id=router.id,
+                port_type=device_owner
+            )
+            context.session.add(router_port)
 
         if router.extra_attributes.distributed and router.gw_port:
             self.add_csnat_router_interface_port(
-                context.elevated(), router_id, port['network_id'],
+                context.elevated(), router, port['network_id'],
                 port['fixed_ips'][0]['subnet_id'])
 
         router_interface_info = self._make_router_interface_info(
@@ -257,9 +266,16 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
         """Query router interfaces that relate to list of router_ids."""
         if not router_ids:
             return []
-        filters = {'device_id': router_ids,
-                   'device_owner': [DEVICE_OWNER_DVR_SNAT]}
-        interfaces = self._core_plugin.get_ports(context, filters)
+        qry = context.session.query(l3_db.RouterPort)
+        qry = qry.filter(
+            l3_db.RouterPort.router_id.in_(router_ids),
+            l3_db.RouterPort.port_type == DEVICE_OWNER_DVR_SNAT
+        )
+
+        # TODO(markmcclain): This is suboptimal but was left to reduce
+        # changeset size since it is late in cycle
+        ports = [rp.port.id for rp in qry]
+        interfaces = self._core_plugin.get_ports(context, {'id': ports})
         LOG.debug("Return the SNAT ports: %s", interfaces)
         if interfaces:
             self._populate_subnet_for_ports(context, interfaces)
@@ -447,12 +463,19 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
 
     def get_snat_interface_ports_for_router(self, context, router_id):
         """Return all existing snat_router_interface ports."""
-        filters = {'device_id': [router_id],
-                   'device_owner': [DEVICE_OWNER_DVR_SNAT]}
-        return self._core_plugin.get_ports(context, filters)
+        # TODO(markmcclain): This is suboptimal but was left to reduce
+        # changeset size since it is late in cycle
+        qry = context.session.query(l3_db.RouterPort)
+        qry = qry.filter_by(
+            router_id=router_id,
+            port_type=DEVICE_OWNER_DVR_SNAT
+        )
+
+        ports = [rp.port.id for rp in qry]
+        return self._core_plugin.get_ports(context, {'id': ports})
 
     def add_csnat_router_interface_port(
-            self, context, router_id, network_id, subnet_id, do_pop=True):
+            self, context, router, network_id, subnet_id, do_pop=True):
         """Add SNAT interface to the specified router and subnet."""
         snat_port = self._core_plugin.create_port(
             context,
@@ -460,19 +483,27 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
                       'network_id': network_id,
                       'mac_address': attributes.ATTR_NOT_SPECIFIED,
                       'fixed_ips': [{'subnet_id': subnet_id}],
-                      'device_id': router_id,
+                      'device_id': router.id,
                       'device_owner': DEVICE_OWNER_DVR_SNAT,
                       'admin_state_up': True,
                       'name': ''}})
         if not snat_port:
             msg = _("Unable to create the SNAT Interface Port")
             raise n_exc.BadRequest(resource='router', msg=msg)
-        elif do_pop:
+
+        with context.session.begin(subtransactions=True):
+            router_port = l3_db.RouterPort(
+                port_id=snat_port['id'],
+                router_id=router.id,
+                port_type=DEVICE_OWNER_DVR_SNAT
+            )
+            context.session.add(router_port)
+
+        if do_pop:
             return self._populate_subnet_for_ports(context, [snat_port])
         return snat_port
 
-    def create_snat_intf_ports_if_not_exists(
-        self, context, router_id):
+    def create_snat_intf_ports_if_not_exists(self, context, router):
         """Function to return the snat interface port list.
 
         This function will return the snat interface port list
@@ -480,24 +511,27 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
         new ports and then return the list.
         """
         port_list = self.get_snat_interface_ports_for_router(
-            context, router_id)
+            context, router.id)
         if port_list:
             self._populate_subnet_for_ports(context, port_list)
             return port_list
         port_list = []
-        filters = {
-            'device_id': [router_id],
-            'device_owner': [DEVICE_OWNER_DVR_INTERFACE]}
-        int_ports = self._core_plugin.get_ports(context, filters)
+
+        int_ports = (
+            rp.port for rp in
+            router.attached_ports.filter_by(
+                port_type=DEVICE_OWNER_DVR_INTERFACE
+            )
+        )
         LOG.info(_('SNAT interface port list does not exist,'
                    ' so create one: %s'), port_list)
         for intf in int_ports:
-            if intf.get('fixed_ips'):
+            if intf.fixed_ips:
                 # Passing the subnet for the port to make sure the IP's
                 # are assigned on the right subnet if multiple subnet
                 # exists
                 snat_port = self.add_csnat_router_interface_port(
-                    context, router_id, intf['network_id'],
+                    context, router, intf['network_id'],
                     intf['fixed_ips'][0]['subnet_id'], do_pop=False)
                 port_list.append(snat_port)
         if port_list:
@@ -539,11 +573,18 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
         # Each csnat router interface port is associated
         # with a subnet, so we need to pass the subnet id to
         # delete the right ports.
-        device_filter = {
-            'device_id': [router['id']],
-            'device_owner': [DEVICE_OWNER_DVR_SNAT]}
+
+        # TODO(markmcclain): This is suboptimal but was left to reduce
+        # changeset size since it is late in cycle
+        ports = (
+            rp.port.id for rp in
+            router.attached_ports.filter_by(port_type=DEVICE_OWNER_DVR_SNAT)
+        )
+
         c_snat_ports = self._core_plugin.get_ports(
-            context, filters=device_filter)
+            context,
+            filters={'id': ports}
+        )
         for p in c_snat_ports:
             if subnet_id is None:
                 self._core_plugin.delete_port(context,
diff --git a/neutron/db/migration/alembic_migrations/versions/544673ac99ab_add_router_port_table.py b/neutron/db/migration/alembic_migrations/versions/544673ac99ab_add_router_port_table.py
new file mode 100644 (file)
index 0000000..cf3190b
--- /dev/null
@@ -0,0 +1,65 @@
+# Copyright 2014 OpenStack Foundation
+#
+#    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.
+#
+
+"""add router port relationship
+
+Revision ID: 544673ac99ab
+Revises: 1680e1f0c4dc
+Create Date: 2014-01-14 11:58:13.754747
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '544673ac99ab'
+down_revision = '1680e1f0c4dc'
+
+from alembic import op
+import sqlalchemy as sa
+
+SQL_STATEMENT = (
+    "insert into routerports "
+    "select "
+    "p.device_id as router_id, p.id as port_id, p.device_owner as port_type "
+    "from ports p join routers r on (p.device_id=r.id) "
+    "where "
+    "(r.tenant_id=p.tenant_id AND p.device_owner='network:router_interface') "
+    "OR (p.tenant_id='' AND p.device_owner='network:router_gateway')"
+)
+
+
+def upgrade():
+    op.create_table(
+        'routerports',
+        sa.Column('router_id', sa.String(length=36), nullable=False),
+        sa.Column('port_id', sa.String(length=36), nullable=False),
+        sa.Column('port_type', sa.String(length=255)),
+        sa.PrimaryKeyConstraint('router_id', 'port_id'),
+        sa.ForeignKeyConstraint(
+            ['router_id'],
+            ['routers.id'],
+            ondelete='CASCADE'
+        ),
+        sa.ForeignKeyConstraint(
+            ['port_id'],
+            ['ports.id'],
+            ondelete='CASCADE'
+        ),
+    )
+
+    op.execute(SQL_STATEMENT)
+
+
+def downgrade():
+    op.drop_table('routerports')
index aa8f506d6e668c859c5f37cd814dcebcfbcfd704..3f554aa166c1790641d56df604d52684f47480b3 100644 (file)
@@ -1 +1 @@
-1680e1f0c4dc
+544673ac99ab