]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
implementation for bug 1008180
authorxchenum <xchenum@gmail.com>
Sun, 12 Aug 2012 05:11:05 +0000 (01:11 -0400)
committerxchenum <xchenum@gmail.com>
Mon, 13 Aug 2012 20:16:01 +0000 (16:16 -0400)
implement the database models for the v2 db plugin base to store dns + additional routes
also handles the dns/route remote/update

Change-Id: I06c585b47668ee963324a5064e40a24471da28c4

quantum/api/v2/attributes.py
quantum/api/v2/base.py
quantum/common/config.py
quantum/common/exceptions.py
quantum/db/db_base_plugin_v2.py
quantum/db/models_v2.py
quantum/tests/unit/test_db_plugin.py

index c55caab12b0475b7e1f6c98bd1e26915af1c85ba..80844f1e42a307362f1e71211659f11656ce0414 100644 (file)
@@ -238,12 +238,12 @@ RESOURCE_ATTRIBUTE_MAP = {
         'allocation_pools': {'allow_post': True, 'allow_put': False,
                              'default': ATTR_NOT_SPECIFIED,
                              'is_visible': True},
-        'dns_namesevers': {'allow_post': True, 'allow_put': True,
-                           'default': ATTR_NOT_SPECIFIED,
-                           'is_visible': False},
-        'additional_host_routes': {'allow_post': True, 'allow_put': True,
-                                   'default': ATTR_NOT_SPECIFIED,
-                                   'is_visible': False},
+        'dns_nameservers': {'allow_post': True, 'allow_put': True,
+                            'default': ATTR_NOT_SPECIFIED,
+                            'is_visible': True},
+        'host_routes': {'allow_post': True, 'allow_put': True,
+                        'default': ATTR_NOT_SPECIFIED,
+                        'is_visible': True},
         'tenant_id': {'allow_post': True, 'allow_put': False,
                       'required_by_policy': True,
                       'is_visible': True},
index e499fa9060a3605a35aed7cb41adac5318d6f773..3ff1ea8de16d54e3a1de529f2090044bab3cda14 100644 (file)
@@ -41,6 +41,8 @@ FAULT_MAP = {exceptions.NotFound: webob.exc.HTTPNotFound,
              exceptions.OutOfBoundsAllocationPool: webob.exc.HTTPBadRequest,
              exceptions.InvalidAllocationPool: webob.exc.HTTPBadRequest,
              exceptions.InvalidSharedSetting: webob.exc.HTTPConflict,
+             exceptions.HostRoutesExhausted: webob.exc.HTTPBadRequest,
+             exceptions.DNSNameServersExhausted: webob.exc.HTTPBadRequest,
              }
 
 QUOTAS = quota.QUOTAS
index 6b463f3dee34917b6bd305ae077cccbcaeef5b8a..7fe6e02d9dfea20dc5ccf9fa233c0d26310fa7c7 100644 (file)
@@ -45,6 +45,8 @@ core_opts = [
     cfg.StrOpt('base_mac', default="fa:16:3e:00:00:00"),
     cfg.IntOpt('mac_generation_retries', default=16),
     cfg.BoolOpt('allow_bulk', default=True),
+    cfg.IntOpt('max_dns_nameservers', default=5),
+    cfg.IntOpt('max_subnet_host_routes', default=20),
 ]
 
 # Register the configuration options
index b7f2a137b88f7c96ebe788c23c0e485c05dbeabc..aeb994b7e957eaf6c32c9902760677d3a715e6ae 100644 (file)
@@ -100,6 +100,18 @@ class MacAddressInUse(InUse):
                 "The mac address %(mac)s is in use.")
 
 
+class HostRoutesExhausted(QuantumException):
+    # NOTE(xchenum): probably make sense to use quota exceeded exception?
+    message = _("Unable to complete operation for %(subnet_id)s. "
+                "The number of host routes exceeds the limit %(quota).")
+
+
+class DNSNameServersExhausted(QuantumException):
+    # NOTE(xchenum): probably make sense to use quota exceeded exception?
+    message = _("Unable to complete operation for %(subnet_id)s. "
+                "The number of DNS nameservers exceeds the limit %(quota).")
+
+
 class IpAddressInUse(InUse):
     message = _("Unable to complete operation for network %(net_id)s. "
                 "The IP address %(ip_address)s is in use.")
index 2f27bf2f59ba613f6f58cd003719e0140dc36145..229d9aae413b63630b001cae58252894d4774ca1 100644 (file)
@@ -125,6 +125,20 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
             raise q_exc.PortNotFound(port_id=id)
         return port
 
+    def _get_dns_by_subnet(self, context, subnet_id):
+        try:
+            dns_qry = context.session.query(models_v2.DNSNameServer)
+            return dns_qry.filter_by(subnet_id=subnet_id).all()
+        except exc.NoResultFound:
+            return []
+
+    def _get_route_by_subnet(self, context, subnet_id):
+        try:
+            route_qry = context.session.query(models_v2.Route)
+            return route_qry.filter_by(subnet_id=subnet_id).all()
+        except exc.NoResultFound:
+            return []
+
     def _fields(self, resource, fields):
         if fields:
             return dict(((key, item) for key, item in resource.iteritems()
@@ -588,6 +602,18 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
                         pool_2=r_range,
                         subnet_cidr=subnet_cidr)
 
+    def _validate_host_route(self, route):
+        try:
+            netaddr.IPNetwork(route['destination'])
+            netaddr.IPAddress(route['nexthop'])
+        except netaddr.core.AddrFormatError:
+            err_msg = ("invalid route: %s" % (str(route)))
+            raise q_exc.InvalidInput(error_message=err_msg)
+        except ValueError:
+            # netaddr.IPAddress would raise this
+            err_msg = ("invalid route: %s" % (str(route)))
+            raise q_exc.InvalidInput(error_message=err_msg)
+
     def _allocate_pools_for_subnet(self, context, subnet):
         """Create IP allocation pools for a given subnet
 
@@ -663,9 +689,16 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
                                      'end': pool['last_ip']}
                                     for pool in subnet['allocation_pools']],
                'gateway_ip': subnet['gateway_ip'],
-               'enable_dhcp': subnet['enable_dhcp']}
+               'enable_dhcp': subnet['enable_dhcp'],
+               'dns_nameservers': [dns['address']
+                                   for dns in subnet['dns_nameservers']],
+               'host_routes': [{'destination': route['destination'],
+                                'nexthop': route['nexthop']}
+                               for route in subnet['routes']],
+               }
         if subnet['gateway_ip']:
             res['gateway_ip'] = subnet['gateway_ip']
+
         return self._fields(res, fields)
 
     def _make_port_dict(self, port, fields=None):
@@ -755,8 +788,37 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
     def create_subnet_bulk(self, context, subnets):
         return self._create_bulk('subnet', context, subnets)
 
+    def _validate_subnet(self, s):
+        """a subroutine to validate a subnet spec"""
+        # check if the number of DNS nameserver exceeds the quota
+        if 'dns_nameservers' in s and \
+                s['dns_nameservers'] != attributes.ATTR_NOT_SPECIFIED:
+            if len(s['dns_nameservers']) > cfg.CONF.max_dns_nameservers:
+                raise q_exc.DNSNameServersExhausted(
+                    subnet_id=id,
+                    quota=cfg.CONF.max_dns_nameservers)
+            for dns in s['dns_nameservers']:
+                try:
+                    netaddr.IPAddress(dns)
+                except Exception:
+                    raise q_exc.InvalidInput(
+                        error_message=("error parsing dns address %s" % dns))
+
+        # check if the number of host routes exceeds the quota
+        if 'host_routes' in s and \
+                s['host_routes'] != attributes.ATTR_NOT_SPECIFIED:
+            if len(s['host_routes']) > cfg.CONF.max_subnet_host_routes:
+                raise q_exc.HostRoutesExhausted(
+                    subnet_id=id,
+                    quota=cfg.CONF.max_subnet_host_routes)
+            # check if the routes are all valid
+            for rt in s['host_routes']:
+                self._validate_host_route(rt)
+
     def create_subnet(self, context, subnet):
         s = subnet['subnet']
+        self._validate_subnet(s)
+
         net = netaddr.IPNetwork(s['cidr'])
         if s['gateway_ip'] == attributes.ATTR_NOT_SPECIFIED:
             s['gateway_ip'] = str(netaddr.IPAddress(net.first + 1))
@@ -771,10 +833,25 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
                                       network_id=s['network_id'],
                                       ip_version=s['ip_version'],
                                       cidr=s['cidr'],
-                                      gateway_ip=s['gateway_ip'],
-                                      enable_dhcp=s['enable_dhcp'])
-            context.session.add(subnet)
+                                      enable_dhcp=s['enable_dhcp'],
+                                      gateway_ip=s['gateway_ip'])
+
+            # perform allocate pools first, since it might raise an error
             pools = self._allocate_pools_for_subnet(context, s)
+
+            context.session.add(subnet)
+            if s['dns_nameservers'] != attributes.ATTR_NOT_SPECIFIED:
+                for addr in s['dns_nameservers']:
+                    ns = models_v2.DNSNameServer(address=addr,
+                                                 subnet_id=subnet.id)
+                    context.session.add(ns)
+
+            if s['host_routes'] != attributes.ATTR_NOT_SPECIFIED:
+                for rt in s['host_routes']:
+                    route = models_v2.Route(subnet_id=subnet.id,
+                                            destination=rt['destination'],
+                                            nexthop=rt['nexthop'])
+                    context.session.add(route)
             for pool in pools:
                 ip_pool = models_v2.IPAllocationPool(subnet=subnet,
                                                      first_ip=pool['start'],
@@ -785,11 +862,60 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
                     first_ip=pool['start'],
                     last_ip=pool['end'])
                 context.session.add(ip_range)
+
         return self._make_subnet_dict(subnet)
 
     def update_subnet(self, context, id, subnet):
+        """Update the subnet with new info. The change however will not be
+           realized until the client renew the dns lease or we support
+           gratuitous DHCP offers"""
+
         s = subnet['subnet']
+        self._validate_subnet(s)
+
         with context.session.begin():
+            if "dns_nameservers" in s:
+                old_dns_list = self._get_dns_by_subnet(context, id)
+
+                new_dns_addr_set = set(s["dns_nameservers"])
+                old_dns_addr_set = set([dns['address']
+                                        for dns in old_dns_list])
+
+                for dns_addr in old_dns_addr_set - new_dns_addr_set:
+                    for dns in old_dns_list:
+                        if dns['address'] == dns_addr:
+                            context.session.delete(dns)
+                for dns_addr in new_dns_addr_set - old_dns_addr_set:
+                    dns = models_v2.DNSNameServer(
+                        address=dns_addr,
+                        subnet_id=id)
+                    context.session.add(dns)
+                del s["dns_nameservers"]
+
+            def _combine(ht):
+                return ht['destination'] + "_" + ht['nexthop']
+
+            if "host_routes" in s:
+                old_route_list = self._get_route_by_subnet(context, id)
+
+                new_route_set = set([_combine(route)
+                                     for route in s['host_routes']])
+
+                old_route_set = set([_combine(route)
+                                     for route in old_route_list])
+
+                for route_str in old_route_set - new_route_set:
+                    for route in old_route_list:
+                        if _combine(route) == route_str:
+                            context.session.delete(route)
+                for route_str in new_route_set - old_route_set:
+                    route = models_v2.Route(
+                        destination=route_str.partition("_")[0],
+                        nexthop=route_str.partition("_")[2],
+                        subnet_id=id)
+                    context.session.add(route)
+                del s["host_routes"]
+
             subnet = self._get_subnet(context, id)
             subnet.update(s)
         return self._make_subnet_dict(subnet)
@@ -802,6 +928,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
             allocated = allocated_qry.filter_by(subnet_id=id).all()
             if allocated:
                 raise q_exc.SubnetInUse(subnet_id=id)
+
             context.session.delete(subnet)
 
     def get_subnet(self, context, id, fields=None, verbose=None):
index b727e488137cf921be26b142863c5db26edc8b9d..e93f99d148f14a258494ad6cb3f03aa0d659db63 100644 (file)
@@ -98,6 +98,25 @@ class Port(model_base.BASEV2, HasId, HasTenant):
     device_id = sa.Column(sa.String(255), nullable=False)
 
 
+class DNSNameServer(model_base.BASEV2):
+    """Internal representation of a DNS nameserver."""
+    address = sa.Column(sa.String(128), nullable=False, primary_key=True)
+    subnet_id = sa.Column(sa.String(36),
+                          sa.ForeignKey('subnets.id',
+                                        ondelete="CASCADE"),
+                          primary_key=True)
+
+
+class Route(model_base.BASEV2):
+    """Represents a route for a subnet or port."""
+    destination = sa.Column(sa.String(64), nullable=False, primary_key=True)
+    nexthop = sa.Column(sa.String(64), nullable=False, primary_key=True)
+    subnet_id = sa.Column(sa.String(36),
+                          sa.ForeignKey('subnets.id',
+                                        ondelete="CASCADE"),
+                          primary_key=True)
+
+
 class Subnet(model_base.BASEV2, HasId, HasTenant):
     """Represents a quantum subnet.
 
@@ -113,10 +132,12 @@ class Subnet(model_base.BASEV2, HasId, HasTenant):
                                         backref='subnet',
                                         lazy="dynamic")
     enable_dhcp = sa.Column(sa.Boolean())
-
-    #TODO(danwent):
-    # - dns_namservers
-    # - additional_routes
+    dns_nameservers = orm.relationship(DNSNameServer,
+                                       backref='subnet',
+                                       cascade='delete')
+    routes = orm.relationship(Route,
+                              backref='subnet',
+                              cascade='delete')
 
 
 class Network(model_base.BASEV2, HasId, HasTenant):
index 24c1895275b45ebdf43d8ea53785b02b12aa89b8..021e521586e44147b03cce361bbfdfbb519423dc 100644 (file)
@@ -73,6 +73,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
         # Update the plugin
         cfg.CONF.set_override('core_plugin', plugin)
         cfg.CONF.set_override('base_mac', "12:34:56:78:90:ab")
+        cfg.CONF.max_dns_nameservers = 2
+        cfg.CONF.max_subnet_host_routes = 2
         self.api = APIRouter()
 
         def _is_native_bulk_supported():
@@ -179,7 +181,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
                            'tenant_id': self._tenant_id}}
         for arg in ('allocation_pools',
                     'ip_version', 'tenant_id',
-                    'enable_dhcp'):
+                    'enable_dhcp', 'allocation_pools',
+                    'dns_nameservers', 'host_routes'):
             # Arg must be present and not null (but can be false)
             if arg in kwargs and kwargs[arg] is not None:
                 data['subnet'][arg] = kwargs[arg]
@@ -258,7 +261,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
         return self._create_bulk(fmt, number, 'port', base_data, **kwargs)
 
     def _make_subnet(self, fmt, network, gateway, cidr,
-                     allocation_pools=None, ip_version=4, enable_dhcp=True):
+                     allocation_pools=None, ip_version=4, enable_dhcp=True,
+                     dns_nameservers=None, host_routes=None):
         res = self._create_subnet(fmt,
                                   net_id=network['network']['id'],
                                   cidr=cidr,
@@ -266,7 +270,9 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
                                   tenant_id=network['network']['tenant_id'],
                                   allocation_pools=allocation_pools,
                                   ip_version=ip_version,
-                                  enable_dhcp=enable_dhcp)
+                                  enable_dhcp=enable_dhcp,
+                                  dns_nameservers=dns_nameservers,
+                                  host_routes=host_routes)
         # Things can go wrong - raise HTTP exc with res code only
         # so it can be caught by unit tests
         if res.status_int >= 400:
@@ -330,7 +336,9 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
                fmt='json',
                ip_version=4,
                allocation_pools=None,
-               enable_dhcp=True):
+               enable_dhcp=True,
+               dns_nameservers=None,
+               host_routes=None):
         # TODO(anyone) DRY this
         # NOTE(salvatore-orlando): we can pass the network object
         # to gen function anyway, and then avoid the repetition
@@ -342,7 +350,9 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
                                            cidr,
                                            allocation_pools,
                                            ip_version,
-                                           enable_dhcp)
+                                           enable_dhcp,
+                                           dns_nameservers,
+                                           host_routes)
                 yield subnet
                 self._delete('subnets', subnet['subnet']['id'])
         else:
@@ -352,7 +362,9 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
                                        cidr,
                                        allocation_pools,
                                        ip_version,
-                                       enable_dhcp)
+                                       enable_dhcp,
+                                       dns_nameservers,
+                                       host_routes)
             yield subnet
             self._delete('subnets', subnet['subnet']['id'])
 
@@ -1717,3 +1729,177 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
             subnet_req = self.new_create_request('subnets', data)
             res = subnet_req.get_response(self.api)
             self.assertEquals(res.status_int, 422)
+
+    def test_create_subnet_with_one_dns(self):
+        gateway_ip = '10.0.0.1'
+        cidr = '10.0.0.0/24'
+        allocation_pools = [{'start': '10.0.0.2',
+                             'end': '10.0.0.100'}]
+        dns_nameservers = ['1.2.3.4']
+        self._test_create_subnet(gateway_ip=gateway_ip,
+                                 cidr=cidr,
+                                 allocation_pools=allocation_pools,
+                                 dns_nameservers=dns_nameservers)
+
+    def test_create_subnet_with_two_dns(self):
+        gateway_ip = '10.0.0.1'
+        cidr = '10.0.0.0/24'
+        allocation_pools = [{'start': '10.0.0.2',
+                             'end': '10.0.0.100'}]
+        dns_nameservers = ['1.2.3.4', '4.3.2.1']
+        self._test_create_subnet(gateway_ip=gateway_ip,
+                                 cidr=cidr,
+                                 allocation_pools=allocation_pools,
+                                 dns_nameservers=dns_nameservers)
+
+    def test_create_subnet_with_too_many_dns(self):
+        with self.network() as network:
+            dns_list = ['1.1.1.1', '2.2.2.2', '3.3.3.3']
+            data = {'subnet': {'network_id': network['network']['id'],
+                               'cidr': '10.0.2.0/24',
+                               'ip_version': 4,
+                               'tenant_id': network['network']['tenant_id'],
+                               'gateway_ip': '10.0.0.1',
+                               'dns_nameservers': dns_list}}
+
+            subnet_req = self.new_create_request('subnets', data)
+            res = subnet_req.get_response(self.api)
+            self.assertEquals(res.status_int, 400)
+
+    def test_create_subnet_with_one_host_route(self):
+        gateway_ip = '10.0.0.1'
+        cidr = '10.0.0.0/24'
+        allocation_pools = [{'start': '10.0.0.2',
+                             'end': '10.0.0.100'}]
+        host_routes = [{'destination': '135.207.0.0/16',
+                       'nexthop': '1.2.3.4'}]
+        self._test_create_subnet(gateway_ip=gateway_ip,
+                                 cidr=cidr,
+                                 allocation_pools=allocation_pools,
+                                 host_routes=host_routes)
+
+    def test_create_subnet_with_two_host_routes(self):
+        gateway_ip = '10.0.0.1'
+        cidr = '10.0.0.0/24'
+        allocation_pools = [{'start': '10.0.0.2',
+                             'end': '10.0.0.100'}]
+        host_routes = [{'destination': '135.207.0.0/16',
+                       'nexthop': '1.2.3.4'},
+                       {'destination': '12.0.0.0/8',
+                       'nexthop': '4.3.2.1'}]
+
+        self._test_create_subnet(gateway_ip=gateway_ip,
+                                 cidr=cidr,
+                                 allocation_pools=allocation_pools,
+                                 host_routes=host_routes)
+
+    def test_create_subnet_with_too_many_routes(self):
+        with self.network() as network:
+            host_routes = [{'destination': '135.207.0.0/16',
+                            'nexthop': '1.2.3.4'},
+                           {'destination': '12.0.0.0/8',
+                            'nexthop': '4.3.2.1'},
+                           {'destination': '141.212.0.0/16',
+                            'nexthop': '2.2.2.2'}]
+
+            data = {'subnet': {'network_id': network['network']['id'],
+                               'cidr': '10.0.2.0/24',
+                               'ip_version': 4,
+                               'tenant_id': network['network']['tenant_id'],
+                               'gateway_ip': '10.0.0.1',
+                               'host_routes': host_routes}}
+
+            subnet_req = self.new_create_request('subnets', data)
+            res = subnet_req.get_response(self.api)
+            self.assertEquals(res.status_int, 400)
+
+    def test_update_subnet_dns(self):
+        with self.subnet() as subnet:
+            data = {'subnet': {'dns_nameservers': ['11.0.0.1']}}
+            req = self.new_update_request('subnets', data,
+                                          subnet['subnet']['id'])
+            res = self.deserialize('json', req.get_response(self.api))
+            self.assertEqual(res['subnet']['dns_nameservers'],
+                             data['subnet']['dns_nameservers'])
+
+    def test_update_subnet_dns_with_too_many_entries(self):
+        with self.subnet() as subnet:
+            dns_list = ['1.1.1.1', '2.2.2.2', '3.3.3.3']
+            data = {'subnet': {'dns_nameservers': dns_list}}
+            req = self.new_update_request('subnets', data,
+                                          subnet['subnet']['id'])
+            res = req.get_response(self.api)
+            self.assertEquals(res.status_int, 400)
+
+    def test_update_subnet_route(self):
+        with self.subnet() as subnet:
+            data = {'subnet': {'host_routes':
+                    [{'destination': '12.0.0.0/8', 'nexthop': '1.2.3.4'}]}}
+            req = self.new_update_request('subnets', data,
+                                          subnet['subnet']['id'])
+            res = self.deserialize('json', req.get_response(self.api))
+            self.assertEqual(res['subnet']['host_routes'],
+                             data['subnet']['host_routes'])
+
+    def test_update_subnet_route_with_too_many_entries(self):
+        with self.subnet() as subnet:
+            data = {'subnet': {'host_routes': [
+                    {'destination': '12.0.0.0/8', 'nexthop': '1.2.3.4'},
+                    {'destination': '13.0.0.0/8', 'nexthop': '1.2.3.5'},
+                    {'destination': '14.0.0.0/8', 'nexthop': '1.2.3.6'}]}}
+            req = self.new_update_request('subnets', data,
+                                          subnet['subnet']['id'])
+            res = req.get_response(self.api)
+            self.assertEquals(res.status_int, 400)
+
+    def test_delete_subnet_with_dns(self):
+        gateway_ip = '10.0.0.1'
+        cidr = '10.0.0.0/24'
+        fmt = 'json'
+        dns_nameservers = ['1.2.3.4']
+        # Create new network
+        res = self._create_network(fmt=fmt, name='net',
+                                   admin_status_up=True)
+        network = self.deserialize(fmt, res)
+        subnet = self._make_subnet(fmt, network, gateway_ip,
+                                   cidr, ip_version=4,
+                                   dns_nameservers=dns_nameservers)
+        req = self.new_delete_request('subnets', subnet['subnet']['id'])
+        res = req.get_response(self.api)
+        self.assertEquals(res.status_int, 204)
+
+    def test_delete_subnet_with_route(self):
+        gateway_ip = '10.0.0.1'
+        cidr = '10.0.0.0/24'
+        fmt = 'json'
+        host_routes = [{'destination': '135.207.0.0/16',
+                        'nexthop': '1.2.3.4'}]
+        # Create new network
+        res = self._create_network(fmt=fmt, name='net',
+                                   admin_status_up=True)
+        network = self.deserialize(fmt, res)
+        subnet = self._make_subnet(fmt, network, gateway_ip,
+                                   cidr, ip_version=4,
+                                   host_routes=host_routes)
+        req = self.new_delete_request('subnets', subnet['subnet']['id'])
+        res = req.get_response(self.api)
+        self.assertEquals(res.status_int, 204)
+
+    def test_delete_subnet_with_dns_and_route(self):
+        gateway_ip = '10.0.0.1'
+        cidr = '10.0.0.0/24'
+        fmt = 'json'
+        dns_nameservers = ['1.2.3.4']
+        host_routes = [{'destination': '135.207.0.0/16',
+                        'nexthop': '1.2.3.4'}]
+        # Create new network
+        res = self._create_network(fmt=fmt, name='net',
+                                   admin_status_up=True)
+        network = self.deserialize(fmt, res)
+        subnet = self._make_subnet(fmt, network, gateway_ip,
+                                   cidr, ip_version=4,
+                                   dns_nameservers=dns_nameservers,
+                                   host_routes=host_routes)
+        req = self.new_delete_request('subnets', subnet['subnet']['id'])
+        res = req.get_response(self.api)
+        self.assertEquals(res.status_int, 204)