"extension:provider_network:view": [["rule:admin_only"]],
"extension:provider_network:set": [["rule:admin_only"]],
+ "extension:router:view": [["rule:regular_user"]],
+ "extension:router:set": [["rule:admin_only"]],
+
"networks:private:read": [["rule:admin_or_owner"]],
"networks:private:write": [["rule:admin_or_owner"]],
"networks:shared:read": [["rule:regular_user"]],
from quantum.db import models_v2
from quantum.extensions import l3
from quantum.openstack.common import cfg
+from quantum import policy
LOG = logging.getLogger(__name__)
gw_port = orm.relationship(models_v2.Port)
+class ExternalNetwork(model_base.BASEV2):
+ network_id = sa.Column(sa.String(36),
+ sa.ForeignKey('networks.id', ondelete="CASCADE"),
+ primary_key=True)
+
+
class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
"""Represents a floating IP, which may or many not be
allocated to a tenant, and may or may not be associated with
network_id = info.get('network_id', None) if info else None
if network_id:
- #FIXME(danwent): confirm net-id is valid external network
self._get_network(context, network_id)
+ if not self._network_is_external(context, network_id):
+ msg = "Network %s is not a valid external network" % network_id
+ raise q_exc.BadRequest(resource='router', msg=msg)
# figure out if we need to delete existing port
if gw_port and gw_port['network_id'] != network_id:
tenant_id = self._get_tenant_id_for_create(context, fip)
fip_id = utils.str_uuid()
- #TODO(danwent): validate that network_id is valid floatingip-network
+ f_net_id = fip['floating_network_id']
+ if not self._network_is_external(context, f_net_id):
+ msg = "Network %s is not a valid external network" % f_net_id
+ raise q_exc.BadRequest(resource='floatingip', msg=msg)
# This external port is never exposed to the tenant.
# it is used purely for internal system and admin use when
# managing floating IPs.
external_port = self.create_port(context, {
'port':
- {'network_id': fip['floating_network_id'],
+ {'network_id': f_net_id,
'mac_address': attributes.ATTR_NOT_SPECIFIED,
'fixed_ips': attributes.ATTR_NOT_SPECIFIED,
'admin_state_up': True,
# should never happen
raise Exception('Multiple floating IPs found for port %s'
% port_id)
+
+ def _check_l3_view_auth(self, context, network):
+ return policy.check(context,
+ "extension:router:view",
+ network)
+
+ def _enforce_l3_set_auth(self, context, network):
+ return policy.enforce(context,
+ "extension:router:set",
+ network)
+
+ def _network_is_external(self, context, net_id):
+ try:
+ context.session.query(ExternalNetwork).filter_by(
+ network_id=net_id).one()
+ return True
+ except exc.NoResultFound:
+ return False
+
+ def _extend_network_dict_l3(self, context, network):
+ if self._check_l3_view_auth(context, network):
+ network['router:external'] = self._network_is_external(
+ context, network['id'])
+
+ def _process_l3_create(self, context, net_data, net_id):
+ external = net_data.get('router:external')
+ external_set = attributes.is_attr_set(external)
+
+ if not external_set:
+ return
+
+ self._enforce_l3_set_auth(context, net_data)
+
+ if external:
+ # expects to be called within a plugin's session
+ context.session.add(ExternalNetwork(network_id=net_id))
+
+ def _process_l3_update(self, context, net_data, net_id):
+
+ new_value = net_data.get('router:external')
+ if not attributes.is_attr_set(new_value):
+ return
+
+ self._enforce_l3_set_auth(context, net_data)
+ existing_value = self._network_is_external(context, net_id)
+
+ if existing_value == new_value:
+ return
+
+ if new_value:
+ context.session.add(ExternalNetwork(network_id=net_id))
+ else:
+ # must make sure we do not have any external gateway ports
+ # (and thus, possible floating IPs) on this network before
+ # allow it to be update to external=False
+ try:
+ context.session.query(models_v2.Port).filter_by(
+ device_owner=DEVICE_OWNER_ROUTER_GW,
+ network_id=net_id).first()
+ raise ExternalNetworkInUse(net_id=net_id)
+ except exc.NoResultFound:
+ pass # expected
+
+ context.session.query(ExternalNetwork).filter_by(
+ network_id=net_id).delete()
+
+ def _filter_nets_l3(self, context, nets, filters):
+ vals = filters.get('router:external', [])
+ if not vals:
+ return nets
+
+ ext_nets = set([en['network_id'] for en in
+ context.session.query(ExternalNetwork).all()])
+ if vals[0]:
+ return [n for n in nets if n['id'] in ext_nets]
+ else:
+ return [n for n in nets if n['id'] not in ext_nets]
wants to extend this map.
"""
for ext in self.extensions.itervalues():
+ if not hasattr(ext, 'get_extended_resources'):
+ continue
try:
extended_attrs = ext.get_extended_resources(version)
for resource, resource_attrs in extended_attrs.iteritems():
else:
attr_map[resource] = resource_attrs
except AttributeError:
- # Extensions aren't required to have extended
- # attributes
- pass
+ LOG.exception("Error fetching extended attributes for "
+ "extension '%s'" % ext.get_name())
def _check_extension(self, extension):
"""Checks for required methods in extension objects."""
" cannot be deleted directly via the port API.")
+class ExternalNetworkInUse(qexception.InUse):
+ message = _("External network %(net_id)s cannot be updated to be made "
+ "non-external, since it has existing gateway ports")
+
+
def _validate_uuid_or_none(data, valid_values=None):
if data is None:
return None
},
}
+EXTENDED_ATTRIBUTES_2_0 = {
+ 'networks': {'router:external': {'allow_post': True,
+ 'allow_put': True,
+ 'default': attr.ATTR_NOT_SPECIFIED,
+ 'is_visible': True,
+ 'convert_to': attr.convert_to_boolean,
+ 'validate': {'type:boolean': None}}}}
+
l3_quota_opts = [
cfg.IntOpt('quota_router',
default=10,
@classmethod
def get_name(cls):
- return "Quantum Router"
+ return "Quantum L3 Router"
@classmethod
def get_alias(cls):
- return "os-quantum-router"
+ return "router"
@classmethod
def get_description(cls):
@classmethod
def get_namespace(cls):
- return "http://docs.openstack.org/ext/os-quantum-router/api/v1.0"
+ return "http://docs.openstack.org/ext/quantum/router/api/v1.0"
@classmethod
def get_updated(cls):
return exts
+ def get_extended_resources(self, version):
+ if version == "2.0":
+ return EXTENDED_ATTRIBUTES_2_0
+ else:
+ return {}
+
class RouterPluginBase(object):
# is qualified by class
__native_bulk_support = True
- supported_extension_aliases = ["provider", "os-quantum-router"]
+ supported_extension_aliases = ["provider", "router"]
def __init__(self):
db.initialize()
"extension:provider_network:set",
network)
- def _extend_network_dict(self, context, network):
+ def _extend_network_dict_provider(self, context, network):
if self._check_provider_view_auth(context, network):
binding = db.get_network_binding(context.session, network['id'])
network[provider.PHYSICAL_NETWORK] = binding.physical_network
network)
db.add_network_binding(session, net['id'],
physical_network, vlan_id)
- self._extend_network_dict(context, net)
+ self._process_l3_create(context, network['network'], net['id'])
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
# note - exception will rollback entire transaction
return net
with session.begin(subtransactions=True):
net = super(LinuxBridgePluginV2, self).update_network(context, id,
network)
- self._extend_network_dict(context, net)
+ self._process_l3_update(context, network['network'], id)
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
return net
def delete_network(self, context, id):
def get_network(self, context, id, fields=None):
net = super(LinuxBridgePluginV2, self).get_network(context, id, None)
- self._extend_network_dict(context, net)
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
return self._fields(net, fields)
def get_networks(self, context, filters=None, fields=None):
nets = super(LinuxBridgePluginV2, self).get_networks(context, filters,
None)
for net in nets:
- self._extend_network_dict(context, net)
- # TODO(rkukura): Filter on extended attributes.
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
+
+ # TODO(rkukura): Filter on extended provider attributes.
+ nets = self._filter_nets_l3(context, nets, filters)
+
return [self._fields(net, fields) for net in nets]
def update_port(self, context, id, port):
# bulk operations. Name mangling is used in order to ensure it
# is qualified by class
__native_bulk_support = True
- supported_extension_aliases = ["provider", "os-quantum-router"]
+ supported_extension_aliases = ["provider", "router"]
def __init__(self, configfile=None):
ovs_db_v2.initialize()
"extension:provider_network:set",
network)
- def _extend_network_dict(self, context, network):
+ def _extend_network_dict_provider(self, context, network):
if self._check_provider_view_auth(context, network):
binding = ovs_db_v2.get_network_binding(context.session,
network['id'])
network)
ovs_db_v2.add_network_binding(session, net['id'], network_type,
physical_network, segmentation_id)
- self._extend_network_dict(context, net)
+
+ self._process_l3_create(context, network['network'], net['id'])
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
# note - exception will rollback entire transaction
LOG.debug("Created network: %s" % net['id'])
return net
with session.begin(subtransactions=True):
net = super(OVSQuantumPluginV2, self).update_network(context, id,
network)
- self._extend_network_dict(context, net)
+ self._process_l3_update(context, network['network'], id)
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
return net
def delete_network(self, context, id):
session = context.session
with session.begin(subtransactions=True):
binding = ovs_db_v2.get_network_binding(session, id)
- result = super(OVSQuantumPluginV2, self).delete_network(context,
- id)
+ super(OVSQuantumPluginV2, self).delete_network(context, id)
if binding.network_type == constants.TYPE_GRE:
ovs_db_v2.release_tunnel(session, binding.segmentation_id,
self.tunnel_id_ranges)
# the network record, so explicit removal is not necessary
if self.agent_rpc:
self.notifier.network_delete(self.rpc_context, id)
- return result
def get_network(self, context, id, fields=None):
net = super(OVSQuantumPluginV2, self).get_network(context, id, None)
- self._extend_network_dict(context, net)
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
return self._fields(net, fields)
def get_networks(self, context, filters=None, fields=None):
nets = super(OVSQuantumPluginV2, self).get_networks(context, filters,
None)
for net in nets:
- self._extend_network_dict(context, net)
- # TODO(rkukura): Filter on extended attributes.
+ self._extend_network_dict_provider(context, net)
+ self._extend_network_dict_l3(context, net)
+
+ # TODO(rkukura): Filter on extended provider attributes.
+ nets = self._filter_nets_l3(context, nets, filters)
+
return [self._fields(net, fields) for net in nets]
def update_port(self, context, id, port):
self.assertEqual(res.status_int, expected_code)
return self.deserialize('json', res)
- def _list(self, resource):
- req = self.new_list_request(resource)
+ def _list(self, resource, fmt='json', query_params=None):
+ req = self.new_list_request(resource, fmt, query_params)
res = req.get_response(self._api_for_resource(resource))
self.assertEqual(res.status_int, webob.exc.HTTPOk.code)
return self.deserialize('json', res)
self._plugin_patcher = mock.patch(plugin, autospec=True)
self.plugin = self._plugin_patcher.start()
- # Instantiate mock plugin and enable the os-quantum-router extension
+ # Instantiate mock plugin and enable the 'router' extension
manager.QuantumManager.get_plugin().supported_extension_aliases = (
- ["os-quantum-router"])
+ ["router"])
ext_mgr = L3TestExtensionManager()
self.ext_mdw = test_extensions.setup_extensions_middleware(ext_mgr)
# This plugin class is just for testing
class TestL3NatPlugin(db_base_plugin_v2.QuantumDbPluginV2,
l3_db.L3_NAT_db_mixin):
- supported_extension_aliases = ["os-quantum-router"]
+ supported_extension_aliases = ["router"]
+
+ def create_network(self, context, network):
+ session = context.session
+ with session.begin(subtransactions=True):
+ net = super(TestL3NatPlugin, self).create_network(context,
+ network)
+ self._process_l3_create(context, network['network'], net['id'])
+ self._extend_network_dict_l3(context, net)
+ return net
+
+ def update_network(self, context, id, network):
+
+ session = context.session
+ with session.begin(subtransactions=True):
+ net = super(TestL3NatPlugin, self).update_network(context, id,
+ network)
+ self._process_l3_update(context, network['network'], id)
+ self._extend_network_dict_l3(context, net)
+ return net
+
+ def delete_network(self, context, id):
+ session = context.session
+ with session.begin(subtransactions=True):
+ net = super(TestL3NatPlugin, self).delete_network(context, id)
+
+ def get_network(self, context, id, fields=None):
+ net = super(TestL3NatPlugin, self).get_network(context, id, None)
+ self._extend_network_dict_l3(context, net)
+ return self._fields(net, fields)
+
+ def get_networks(self, context, filters=None, fields=None):
+ nets = super(TestL3NatPlugin, self).get_networks(context, filters,
+ None)
+ for net in nets:
+ self._extend_network_dict_l3(context, net)
+ nets = self._filter_nets_l3(context, nets, filters)
+
+ return [self._fields(net, fields) for net in nets]
def delete_port(self, context, id, l3_port_check=True):
if l3_port_check:
def test_router_add_gateway(self):
with self.router() as r:
with self.subnet() as s:
+ self._set_net_external(s['subnet']['network_id'])
self._add_external_gateway_to_router(
r['router']['id'],
s['subnet']['network_id'])
r['router']['id'],
"foobar", expected_code=exc.HTTPNotFound.code)
+ def test_router_add_gateway_net_not_external(self):
+ with self.router() as r:
+ with self.subnet() as s:
+ # intentionally do not set net as external
+ self._add_external_gateway_to_router(
+ r['router']['id'],
+ s['subnet']['network_id'],
+ expected_code=exc.HTTPBadRequest.code)
+
def test_router_add_gateway_no_subnet(self):
with self.router() as r:
with self.network() as n:
+ self._set_net_external(n['network']['id'])
self._add_external_gateway_to_router(
r['router']['id'],
n['network']['id'], expected_code=exc.HTTPBadRequest.code)
# remove extra port created
self._delete('ports', p2['port']['id'])
+ def _set_net_external(self, net_id):
+ self._update('networks', net_id,
+ {'network': {'router:external': True}})
+
def _create_floatingip(self, fmt, network_id, port_id=None,
fixed_ip=None):
data = {'floatingip': {'floating_network_id': network_id,
@contextlib.contextmanager
def floatingip_with_assoc(self, port_id=None, fmt='json'):
with self.subnet() as public_sub:
+ self._set_net_external(public_sub['subnet']['network_id'])
with self.port() as private_port:
with self.router() as r:
sid = private_port['port']['fixed_ips'][0]['subnet_id']
@contextlib.contextmanager
def floatingip_no_assoc(self, private_sub, fmt='json'):
with self.subnet() as public_sub:
+ self._set_net_external(public_sub['subnet']['network_id'])
with self.router() as r:
self._add_external_gateway_to_router(
r['router']['id'],
def test_create_floatingip_no_ext_gateway_return_404(self):
with self.subnet() as public_sub:
+ self._set_net_external(public_sub['subnet']['network_id'])
with self.port() as private_port:
with self.router() as r:
res = self._create_floatingip(
# this should be some kind of error
self.assertEqual(res.status_int, exc.HTTPNotFound.code)
+ def test_create_floating_non_ext_network_returns_400(self):
+ with self.subnet() as public_sub:
+ # normally we would set the network of public_sub to be
+ # external, but the point of this test is to handle when
+ # that is not the case
+ with self.router() as r:
+ res = self._create_floatingip(
+ 'json',
+ public_sub['subnet']['network_id'])
+ self.assertEqual(res.status_int, exc.HTTPBadRequest.code)
+
def test_create_floatingip_no_public_subnet_returns_400(self):
with self.network() as public_network:
with self.port() as private_port:
res = self._create_floatingip('json', utils.str_uuid(),
utils.str_uuid(), 'iamnotnanip')
self.assertEqual(res.status_int, 422)
+
+ def test_list_nets_external(self):
+ with self.network() as n1:
+ self._set_net_external(n1['network']['id'])
+ with self.network() as n2:
+ body = self._list('networks')
+ self.assertEquals(len(body['networks']), 2)
+
+ body = self._list('networks',
+ query_params="router:external=True")
+ self.assertEquals(len(body['networks']), 1)
+
+ body = self._list('networks',
+ query_params="router:external=False")
+ self.assertEquals(len(body['networks']), 1)