Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
Allow disabling of default route via DHCP
Browse files Browse the repository at this point in the history
By default the route to the default gw is always injected via DHCP.
This patch adds an attribute "inject_default_route" to the l2_policy
which allows controlling this behavior. The default behavior is still
maintained.

The apic_mapping driver is updated to support both setting and unsetting
this attribute.

The resource_mapping driver currently only supports setting this attribute.

Implements blueprint inject-default-route

Change-Id: Ifdf6d51ae3a115f3d643b74e12d5693be3d15307
  • Loading branch information
snaiksat committed Dec 8, 2015
1 parent f3fc14c commit e5497c4
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 18 deletions.
5 changes: 5 additions & 0 deletions gbpservice/neutron/db/grouppolicy/group_policy_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class L2Policy(gquota.GBPQuotaBase, model_base.BASEV2, models_v2.HasId,
l3_policy_id = sa.Column(sa.String(36),
sa.ForeignKey('gp_l3_policies.id'),
nullable=True)
inject_default_route = sa.Column(sa.Boolean, default=True,
server_default=sa.sql.true())
shared = sa.Column(sa.Boolean)


Expand Down Expand Up @@ -853,6 +855,7 @@ def _make_l2_policy_dict(self, l2p, fields=None):
'name': l2p['name'],
'description': l2p['description'],
'l3_policy_id': l2p['l3_policy_id'],
'inject_default_route': l2p.get('inject_default_route', True),
'shared': l2p.get('shared', False), }
res['policy_target_groups'] = [
ptg['id'] for ptg in l2p['policy_target_groups']]
Expand Down Expand Up @@ -1261,6 +1264,8 @@ def create_l2_policy(self, context, l2_policy):
tenant_id=tenant_id, name=l2p['name'],
description=l2p['description'],
l3_policy_id=l2p['l3_policy_id'],
inject_default_route=l2p.get(
'inject_default_route', True),
shared=l2p.get('shared', False))
context.session.add(l2p_db)
return self._make_l2_policy_dict(l2p_db)
Expand Down
2 changes: 2 additions & 0 deletions gbpservice/neutron/db/grouppolicy/group_policy_mapping_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ def create_l2_policy(self, context, l2_policy):
description=l2p['description'],
l3_policy_id=l2p['l3_policy_id'],
network_id=l2p['network_id'],
inject_default_route=l2p.get(
'inject_default_route', True),
shared=l2p.get('shared', False))
context.session.add(l2p_db)
return self._make_l2_policy_dict(l2p_db)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2015 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_inject_default_route_attr_to_l2p
Revision ID: 7ef98f287d6
Create Date: 2015-11-29 02:00:06.217136
"""

# revision identifiers, used by Alembic.
revision = '7ef98f287d6'
down_revision = '478d86c6c648'

from alembic import op
import sqlalchemy as sa


def upgrade():
op.add_column('gp_l2_policies',
sa.Column('inject_default_route', sa.Boolean,
server_default=sa.sql.true()))


def downgrade():
pass
Original file line number Diff line number Diff line change
@@ -1 +1 @@
478d86c6c648
7ef98f287d6
4 changes: 4 additions & 0 deletions gbpservice/neutron/extensions/group_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ def _validate_gbp_resource_name(data, valid_values=None):
'validate': {'type:uuid_or_none': None},
'default': None, 'is_visible': True,
'required': True},
'inject_default_route': {'allow_post': True, 'allow_put': True,
'default': True, 'is_visible': True,
'convert_to': attr.convert_to_boolean,
'required': False},
attr.SHARED: {'allow_post': True, 'allow_put': True,
'default': False, 'convert_to': attr.convert_to_boolean,
'is_visible': True, 'required_by_policy': True,
Expand Down
5 changes: 5 additions & 0 deletions gbpservice/neutron/services/grouppolicy/common/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ class L3PolicyUpdateOfL2PolicyNotSupported(GroupPolicyBadRequest):
message = _("Updating L3 policy of L2 policy is not supported.")


class UnsettingInjectDefaultRouteOfL2PolicyNotSupported(GroupPolicyBadRequest):
message = _("Unsetting inject_default_route attribute of L2 policy is not "
"supported.")


class L3PolicyMultipleRoutersNotSupported(GroupPolicyBadRequest):
message = _("L3 policy does not support multiple routers.")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ def is_port_promiscuous(port):
context, port['id'], l3_policy, pt=pt,
owned_addresses=own_addr, host=details['host']))
self._add_network_details(context, port, details, pt=pt,
owned=own_addr)
owned=own_addr, inject_default_route=
l2p['inject_default_route'])
self._add_vrf_details(context, details)
if self._is_pt_chain_head(context, pt, ptg, owned_ips=own_addr):
# is a relevant proxy_gateway, push all the addresses from this
Expand Down Expand Up @@ -502,7 +503,7 @@ def _get_ip_mapping_details(self, context, port_id, l3_policy, pt=None,
return fips, ipms, host_snat_ips

def _add_network_details(self, context, port, details, pt=None,
owned=None):
owned=None, inject_default_route=True):
details['allowed_address_pairs'] = port['allowed_address_pairs']
if pt:
# Set the correct address ownership for this port
Expand All @@ -529,23 +530,34 @@ def _add_network_details(self, context, port, details, pt=None,
if not subnet['dns_nameservers']:
# Use DHCP namespace port IP
subnet['dns_nameservers'] = dhcp_ips
# Ser Default route if needed
metadata = default = False
# Set Default & Metadata routes if needed
default_route = metadata_route = {}
if subnet['ip_version'] == 4:
for route in subnet['host_routes']:
if route['destination'] == '0.0.0.0/0':
default = True
default_route = route
if route['destination'] == dhcp.METADATA_DEFAULT_CIDR:
metadata = True
# Set missing routes
if not default:
subnet['host_routes'].append(
{'destination': '0.0.0.0/0',
'nexthop': subnet['gateway_ip']})
if not metadata and dhcp_ips and not self.enable_metadata_opt:
subnet['host_routes'].append(
{'destination': dhcp.METADATA_DEFAULT_CIDR,
'nexthop': dhcp_ips[0]})
metadata_route = route
if not inject_default_route:
# In this case we do not want to send the default route
# and the metadata route. We also do not want to send
# the gateway_ip for the subnet.
if default_route:
subnet['host_routes'].remove(default_route)
if metadata_route:
subnet['host_routes'].remove(metadata_route)
del subnet['gateway_ip']
else:
# Set missing routes
if not default_route:
subnet['host_routes'].append(
{'destination': '0.0.0.0/0',
'nexthop': subnet['gateway_ip']})
if not metadata_route and dhcp_ips and (
not self.enable_metadata_opt):
subnet['host_routes'].append(
{'destination': dhcp.METADATA_DEFAULT_CIDR,
'nexthop': dhcp_ips[0]})
subnet['dhcp_server_ips'] = dhcp_ips

def _add_vrf_details(self, context, details):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,8 @@ def create_l2_policy_precommit(self, context):
self._reject_cross_tenant_l2p_l3p(context)
self._reject_non_shared_net_on_shared_l2p(context)
self._reject_invalid_network_access(context)
if not context.current['inject_default_route']:
raise exc.UnsettingInjectDefaultRouteOfL2PolicyNotSupported()

@log.log
def create_l2_policy_postcommit(self, context):
Expand All @@ -704,6 +706,9 @@ def create_l2_policy_postcommit(self, context):

@log.log
def update_l2_policy_precommit(self, context):
if (context.current['inject_default_route'] !=
context.original['inject_default_route']):
raise exc.UnsettingInjectDefaultRouteOfL2PolicyNotSupported()
if (context.current['l3_policy_id'] !=
context.original['l3_policy_id']):
raise exc.L3PolicyUpdateOfL2PolicyNotSupported()
Expand Down
5 changes: 3 additions & 2 deletions gbpservice/neutron/tests/unit/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,15 @@ def get_update_policy_target_group_attrs():

@gbp_attributes
def get_create_l2_policy_default_attrs():
return {'name': '', 'description': '', 'shared': False}
return {'name': '', 'description': '', 'shared': False,
'inject_default_route': True}


@gbp_attributes
def get_create_l2_policy_attrs():
return {'name': 'l2p1', 'tenant_id': _uuid(),
'description': 'test L2 policy', 'l3_policy_id': _uuid(),
'shared': False}
'inject_default_route': True, 'shared': False}


@gbp_attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,151 @@ def test_enhanced_subnet_options(self):
self.assertEqual([dhcp['fixed_ips'][0]['ip_address']],
details['subnets'][0]['dhcp_server_ips'])

def test_update_l2p_inject_default_route_false(self):
self.driver.enable_metadata_opt = False
l3p = self.create_l3_policy(name='myl3',
ip_pool='192.168.0.0/16')['l3_policy']
l2p = self.create_l2_policy(name='myl2',
l3_policy_id=l3p['id'])['l2_policy']
ptg = self.create_policy_target_group(
name="ptg1", l2_policy_id=l2p['id'])['policy_target_group']
pt1 = self.create_policy_target(
policy_target_group_id=ptg['id'])['policy_target']
self._bind_port_to_host(pt1['port_id'], 'h1')
sub = self._get_object('subnets', ptg['subnets'][0],
self.api)

# Add one more host_route to the subnet
more_host_routes = [{'destination': '172.16.0.0/24',
'nexthop': '10.0.2.2'}]
data = {'subnet': {'host_routes': more_host_routes}}
req = self.new_update_request('subnets', data, sub['subnet']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(sorted(res['subnet']['host_routes']),
sorted(more_host_routes))

with self.port(subnet=sub, device_owner='network:dhcp',
tenant_id='onetenant') as dhcp:
dhcp = dhcp['port']
details = self.driver.get_gbp_details(
context.get_admin_context(),
device='tap%s' % pt1['port_id'], host='h1')

self.assertEqual(1, len(details['subnets']))
# Verify that DNS nameservers are correctly set
self.assertEqual([dhcp['fixed_ips'][0]['ip_address']],
details['subnets'][0]['dns_nameservers'])
# Verify Default route via GW
self.assertTrue({'destination': '0.0.0.0/0',
'nexthop': '192.168.0.1'} in
details['subnets'][0]['host_routes'])

# Verify Metadata route via DHCP
self.assertTrue(
{'destination': '169.254.169.254/16',
'nexthop': dhcp['fixed_ips'][0]['ip_address']} in
details['subnets'][0]['host_routes'])

# Verify additional host_routes are also added:
# GW + Metadata + 1 additional route = 3
self.assertEqual(3, len(details['subnets'][0]['host_routes']))
self.assertEqual([dhcp['fixed_ips'][0]['ip_address']],
details['subnets'][0]['dhcp_server_ips'])

# Verify gateway_ip is set
self.assertTrue('gateway_ip' in details['subnets'][0])

data = {'l2_policy': {'inject_default_route': False}}
res = self.new_update_request('l2_policies', data, l2p['id'],
self.fmt).get_response(self.ext_api)
pt2 = self.create_policy_target(
policy_target_group_id=ptg['id'])['policy_target']
self._bind_port_to_host(pt2['port_id'], 'h1')
with self.port(subnet=sub, tenant_id='onetenant'):
details = self.driver.get_gbp_details(
context.get_admin_context(),
device='tap%s' % pt2['port_id'], host='h1')

self.assertEqual(1, len(details['subnets']))
# Verify Default route via GW is not present
self.assertFalse({'destination': '0.0.0.0/0',
'nexthop': '192.168.0.1'} in
details['subnets'][0]['host_routes'])

# Verify Metadata route via DHCP is not present
self.assertFalse(
{'destination': '169.254.169.254/16',
'nexthop': dhcp['fixed_ips'][0]['ip_address']} in
details['subnets'][0]['host_routes'])

# Verify only extra route is present
self.assertEqual(1, len(details['subnets'][0]['host_routes']))
self.assertTrue(
{'destination': '172.16.0.0/24',
'nexthop': '10.0.2.2'} in
details['subnets'][0]['host_routes'])
self.assertEqual([dhcp['fixed_ips'][0]['ip_address']],
details['subnets'][0]['dhcp_server_ips'])
# Verify gateway_ip is not set
self.assertFalse('gateway_ip' in details['subnets'][0])

def test_create_l2p_inject_default_route_false(self):
self.driver.enable_metadata_opt = False
l3p = self.create_l3_policy(name='myl3',
ip_pool='192.168.0.0/16')['l3_policy']
l2p = self.create_l2_policy(name='myl2',
l3_policy_id=l3p['id'],
inject_default_route=False)['l2_policy']
ptg = self.create_policy_target_group(
name="ptg1", l2_policy_id=l2p['id'])['policy_target_group']
pt1 = self.create_policy_target(
policy_target_group_id=ptg['id'])['policy_target']
self._bind_port_to_host(pt1['port_id'], 'h1')
sub = self._get_object('subnets', ptg['subnets'][0],
self.api)

# Add one more host_route to the subnet
more_host_routes = [{'destination': '172.16.0.0/24',
'nexthop': '10.0.2.2'}]
data = {'subnet': {'host_routes': more_host_routes}}
req = self.new_update_request('subnets', data, sub['subnet']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(sorted(res['subnet']['host_routes']),
sorted(more_host_routes))

with self.port(subnet=sub, device_owner='network:dhcp',
tenant_id='onetenant') as dhcp:
dhcp = dhcp['port']
details = self.driver.get_gbp_details(
context.get_admin_context(),
device='tap%s' % pt1['port_id'], host='h1')

self.assertEqual(1, len(details['subnets']))
# Verify that DNS nameservers are correctly set
self.assertEqual([dhcp['fixed_ips'][0]['ip_address']],
details['subnets'][0]['dns_nameservers'])
# Verify Default route via GW is not present
self.assertFalse({'destination': '0.0.0.0/0',
'nexthop': '192.168.0.1'} in
details['subnets'][0]['host_routes'])

# Verify Metadata route via DHCP is not present
self.assertFalse(
{'destination': '169.254.169.254/16',
'nexthop': dhcp['fixed_ips'][0]['ip_address']} in
details['subnets'][0]['host_routes'])

# Verify only extra route is present
self.assertEqual(1, len(details['subnets'][0]['host_routes']))
self.assertTrue(
{'destination': '172.16.0.0/24',
'nexthop': '10.0.2.2'} in
details['subnets'][0]['host_routes'])
self.assertEqual([dhcp['fixed_ips'][0]['ip_address']],
details['subnets'][0]['dhcp_server_ips'])
# Verify gateway_ip is not set
self.assertFalse('gateway_ip' in details['subnets'][0])

def test_get_gbp_details_error(self):
details = self.driver.get_gbp_details(
context.get_admin_context(), device='tap%s' % 'randomid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,12 @@ def test_create_l2p_using_different_tenant_network_rejected(self):
self.assertEqual('InvalidNetworkAccess',
res['NeutronError']['type'])

def test_l2_policy_create_unset_inject_default_route_reject(self):
res = self.create_l2_policy(inject_default_route='False',
expected_res_status=400)
self.assertEqual('UnsettingInjectDefaultRouteOfL2PolicyNotSupported',
res['NeutronError']['type'])

def test_shared_l2_policy_create_negative(self):
l3p = self.create_l3_policy(shared=True)
for shared in [True, False]:
Expand Down Expand Up @@ -1341,6 +1347,16 @@ def test_l3p_update_rejected(self, proxy_ip_pool=None):
self.assertEqual('L3PolicyUpdateOfL2PolicyNotSupported',
data['NeutronError']['type'])

def test_l2_policy_update_inject_default_route_reject(self):
l2p = self.create_l2_policy()
l2p_id = l2p['l2_policy']['id']

data = {'l2_policy': {'inject_default_route': False}}
req = self.new_update_request('l2_policies', data, l2p_id)
data = self.deserialize(self.fmt, req.get_response(self.ext_api))
self.assertEqual('UnsettingInjectDefaultRouteOfL2PolicyNotSupported',
data['NeutronError']['type'])


class TestL3Policy(ResourceMappingTestCase):

Expand Down

0 comments on commit e5497c4

Please sign in to comment.