"extension:port_binding:view": "rule:admin_only",
"extension:port_binding:set": "rule:admin_only",
+ "get_port:binding:host_id": "rule:admin_only",
+ "get_port:binding:vif_type": "rule:admin_only",
+ "get_port:binding:profile": "rule:admin_only",
+ "get_port:binding:capabilities": "rule:admin_only",
+ "create_port:binding:host_id": "rule:admin_only",
+ "update_port:binding:host_id": "rule:admin_only",
"subnets:private:read": "rule:admin_or_owner",
"subnets:private:write": "rule:admin_or_owner",
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 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 portbindings db
+
+Revision ID: 176a85fc7d79
+Revises: grizzly
+Create Date: 2013-03-21 14:59:53.052600
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '176a85fc7d79'
+down_revision = 'grizzly'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+ 'quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPluginV2',
+ 'quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2',
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from quantum.db import migration
+
+
+def upgrade(active_plugin=None, options=None):
+ if not migration.should_run(active_plugin, migration_for_plugins):
+ return
+
+ op.create_table(
+ 'portbindingports',
+ sa.Column('port_id', sa.String(length=36), nullable=False),
+ sa.Column('host', sa.String(length=255), nullable=False),
+ sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'),
+ sa.PrimaryKeyConstraint('port_id')
+ )
+
+
+def downgrade(active_plugin=None, options=None):
+ if not migration.should_run(active_plugin, migration_for_plugins):
+ return
+ op.drop_table('portbindingports')
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# 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.
+# @author: Yong Sheng Gong, IBM, Corp.
+
+import sqlalchemy as sa
+from sqlalchemy import orm
+
+from quantum.api.v2 import attributes
+from quantum.db import db_base_plugin_v2
+from quantum.db import model_base
+from quantum.db import models_v2
+from quantum.extensions import portbindings
+from quantum.openstack.common import log as logging
+from quantum import policy
+
+
+LOG = logging.getLogger(__name__)
+
+
+class PortBindingPort(model_base.BASEV2):
+ port_id = sa.Column(sa.String(36),
+ sa.ForeignKey('ports.id', ondelete="CASCADE"),
+ primary_key=True)
+ host = sa.Column(sa.String(255), nullable=False)
+ port = orm.relationship(
+ models_v2.Port,
+ backref=orm.backref("portbinding",
+ lazy='joined', uselist=False,
+ cascade='delete'))
+
+
+class PortBindingMixin(object):
+ extra_binding_dict = None
+
+ def _port_model_hook(self, context, original_model, query):
+ query = query.outerjoin(PortBindingPort,
+ (original_model.id ==
+ PortBindingPort.port_id))
+ return query
+
+ def _port_result_filter_hook(self, query, filters):
+ values = filters and filters.get(portbindings.HOST_ID, [])
+ if not values:
+ return query
+ if len(values) == 1:
+ query = query.filter(PortBindingPort.host == values[0])
+ else:
+ query = query.filter(PortBindingPort.host.in_(values))
+ return query
+
+ db_base_plugin_v2.QuantumDbPluginV2.register_model_query_hook(
+ models_v2.Port,
+ "portbindings_port",
+ _port_model_hook,
+ None,
+ _port_result_filter_hook)
+
+ def _check_portbindings_view_auth(self, context, port):
+ #TODO(salv-orlando): Remove this as part of bp/make-authz-orthogonal
+ keys_to_delete = []
+ for key in port:
+ if key.startswith('binding'):
+ policy_rule = "get_port:%s" % key
+ if not policy.check(context, policy_rule, port):
+ keys_to_delete.append(key)
+ for key in keys_to_delete:
+ del port[key]
+ return port
+
+ def _process_portbindings_create_and_update(self, context, port_data,
+ port):
+ host = port_data.get(portbindings.HOST_ID)
+ host_set = attributes.is_attr_set(host)
+ if not host_set:
+ _extend_port_dict_binding_host(self, port, None)
+ return
+ with context.session.begin(subtransactions=True):
+ bind_port = context.session.query(
+ PortBindingPort).filter_by(port_id=port['id']).first()
+ if not bind_port:
+ context.session.add(PortBindingPort(port_id=port['id'],
+ host=host))
+ else:
+ bind_port.host = host
+ _extend_port_dict_binding_host(self, port, host)
+
+ def get_port_host(self, context, port_id):
+ with context.session.begin(subtransactions=True):
+ bind_port = context.session.query(
+ PortBindingPort).filter_by(port_id=port_id).first()
+ return bind_port and bind_port.host or None
+
+
+def _extend_port_dict_binding_host(plugin, port_res, host):
+ port_res[portbindings.HOST_ID] = host
+ if plugin.extra_binding_dict:
+ port_res.update(plugin.extra_binding_dict)
+ return port_res
+
+
+def _extend_port_dict_binding(plugin, port_res, port_db):
+ if not isinstance(plugin, PortBindingMixin):
+ return
+ host = (port_db.portbinding and port_db.portbinding.host or None)
+ return _extend_port_dict_binding_host(
+ plugin, port_res, host)
+
+ # Register dict extend functions for ports
+db_base_plugin_v2.QuantumDbPluginV2.register_dict_extend_funcs(
+ attributes.PORTS, [_extend_port_dict_binding])
'is_visible': True},
HOST_ID: {'allow_post': True, 'allow_put': True,
'default': attributes.ATTR_NOT_SPECIFIED,
- 'is_visible': True},
+ 'is_visible': True,
+ 'enforce_policy': True},
PROFILE: {'allow_post': True, 'allow_put': True,
'default': attributes.ATTR_NOT_SPECIFIED,
'validate': {'type:dict': None},
from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db
from quantum.db import l3_rpc_base
+from quantum.db import portbindings_db
from quantum.db import quota_db # noqa
from quantum.db import securitygroups_rpc_base as sg_db_rpc
from quantum.extensions import portbindings
class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
- agentschedulers_db.AgentSchedulerDbMixin):
+ agentschedulers_db.AgentSchedulerDbMixin,
+ portbindings_db.PortBindingMixin):
"""Implement the Quantum abstractions using Linux bridging.
A new VLAN is created for each network. An agent is relied upon
network_view = "extension:provider_network:view"
network_set = "extension:provider_network:set"
- binding_view = "extension:port_binding:view"
- binding_set = "extension:port_binding:set"
def __init__(self):
+ self.extra_binding_dict = {
+ portbindings.VIF_TYPE: portbindings.VIF_TYPE_BRIDGE,
+ portbindings.CAPABILITIES: {
+ portbindings.CAP_PORT_FILTER:
+ 'security-group' in self.supported_extension_aliases}}
db.initialize()
self._parse_network_vlan_ranges()
db.sync_network_states(self.network_vlan_ranges)
return [self._fields(net, fields) for net in nets]
- def _extend_port_dict_binding(self, context, port):
- if self._check_view_auth(context, port, self.binding_view):
- port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE
- port[portbindings.CAPABILITIES] = {
- portbindings.CAP_PORT_FILTER:
- 'security-group' in self.supported_extension_aliases}
- return port
-
def get_port(self, context, id, fields=None):
with context.session.begin(subtransactions=True):
port = super(LinuxBridgePluginV2, self).get_port(context,
id,
fields)
- self._extend_port_dict_binding(context, port),
- return self._fields(port, fields)
+ return self._check_portbindings_view_auth(context, port)
def get_ports(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None, page_reverse=False):
ports = super(LinuxBridgePluginV2,
self).get_ports(context, filters, fields, sorts,
limit, marker, page_reverse)
- #TODO(nati) filter by security group
for port in ports:
- self._extend_port_dict_binding(context, port)
- res_ports.append(self._fields(port, fields))
+ self._check_portbindings_view_auth(context, port)
+ res_ports.append(port)
return res_ports
def create_port(self, context, port):
session = context.session
+ port_data = port['port']
with session.begin(subtransactions=True):
self._ensure_default_security_group_on_port(context, port)
sgids = self._get_security_groups_on_port(context, port)
port = super(LinuxBridgePluginV2,
self).create_port(context, port)
+ self._process_portbindings_create_and_update(context,
+ port_data,
+ port)
self._process_port_create_security_group(
context, port, sgids)
self.notify_security_groups_member_updated(context, port)
- return self._extend_port_dict_binding(context, port)
+ return self._check_portbindings_view_auth(context, port)
def update_port(self, context, id, port):
original_port = self.get_port(context, id)
with session.begin(subtransactions=True):
updated_port = super(LinuxBridgePluginV2, self).update_port(
context, id, port)
+ self._process_portbindings_create_and_update(context,
+ port['port'],
+ updated_port)
need_port_update_notify = self.update_security_group_on_port(
context, id, port, original_port, updated_port)
if need_port_update_notify:
self._notify_port_updated(context, updated_port)
- return self._extend_port_dict_binding(context, updated_port)
+ return self._check_portbindings_view_auth(context, updated_port)
def delete_port(self, context, id, l3_port_check=True):
from quantum.db import dhcp_rpc_base
from quantum.db import extraroute_db
from quantum.db import l3_rpc_base
+from quantum.db import portbindings_db
from quantum.db import quota_db # noqa
from quantum.db import securitygroups_rpc_base as sg_db_rpc
from quantum.extensions import portbindings
class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
extraroute_db.ExtraRoute_db_mixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
- agentschedulers_db.AgentSchedulerDbMixin):
+ agentschedulers_db.AgentSchedulerDbMixin,
+ portbindings_db.PortBindingMixin):
"""Implement the Quantum abstractions using Open vSwitch.
network_view = "extension:provider_network:view"
network_set = "extension:provider_network:set"
- binding_view = "extension:port_binding:view"
- binding_set = "extension:port_binding:set"
def __init__(self, configfile=None):
+ self.extra_binding_dict = {
+ portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
+ portbindings.CAPABILITIES: {
+ portbindings.CAP_PORT_FILTER:
+ 'security-group' in self.supported_extension_aliases}}
ovs_db_v2.initialize()
self._parse_network_vlan_ranges()
ovs_db_v2.sync_vlan_allocations(self.network_vlan_ranges)
return [self._fields(net, fields) for net in nets]
- def _extend_port_dict_binding(self, context, port):
- if self._check_view_auth(context, port, self.binding_view):
- port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS
- port[portbindings.CAPABILITIES] = {
- portbindings.CAP_PORT_FILTER:
- 'security-group' in self.supported_extension_aliases}
- return port
-
def create_port(self, context, port):
# Set port status as 'DOWN'. This will be updated by agent
port['port']['status'] = q_const.PORT_STATUS_DOWN
+ port_data = port['port']
session = context.session
with session.begin(subtransactions=True):
self._ensure_default_security_group_on_port(context, port)
sgids = self._get_security_groups_on_port(context, port)
port = super(OVSQuantumPluginV2, self).create_port(context, port)
+ self._process_portbindings_create_and_update(context,
+ port_data, port)
self._process_port_create_security_group(context, port, sgids)
self.notify_security_groups_member_updated(context, port)
- return self._extend_port_dict_binding(context, port)
+ return self._check_portbindings_view_auth(context, port)
def get_port(self, context, id, fields=None):
with context.session.begin(subtransactions=True):
port = super(OVSQuantumPluginV2, self).get_port(context,
- id, fields)
- self._extend_port_dict_binding(context, port)
- return self._fields(port, fields)
+ id,
+ fields)
+ return self._check_portbindings_view_auth(context, port)
def get_ports(self, context, filters=None, fields=None,
- sorts=None, limit=None, marker=None,
- page_reverse=False):
+ sorts=None, limit=None, marker=None, page_reverse=False):
+ res_ports = []
with context.session.begin(subtransactions=True):
- ports = super(OVSQuantumPluginV2, self).get_ports(
- context, filters, fields, sorts, limit, marker,
- page_reverse)
- #TODO(nati) filter by security group
+ ports = super(OVSQuantumPluginV2,
+ self).get_ports(context, filters, fields, sorts,
+ limit, marker, page_reverse)
for port in ports:
- self._extend_port_dict_binding(context, port)
- return [self._fields(port, fields) for port in ports]
+ self._check_portbindings_view_auth(context, port)
+ res_ports.append(port)
+ return res_ports
def update_port(self, context, id, port):
session = context.session
context, id, port)
need_port_update_notify = self.update_security_group_on_port(
context, id, port, original_port, updated_port)
+ self._process_portbindings_create_and_update(context,
+ port['port'],
+ updated_port)
need_port_update_notify |= self.is_security_group_member_updated(
context, original_port, updated_port)
if original_port['admin_state_up'] != updated_port['admin_state_up']:
binding.network_type,
binding.segmentation_id,
binding.physical_network)
- return self._extend_port_dict_binding(context, updated_port)
+ return self._check_portbindings_view_auth(context, updated_port)
def delete_port(self, context, id, l3_port_check=True):
import contextlib
from oslo.config import cfg
+from webob import exc
from quantum import context
from quantum.extensions import portbindings
self.assertFalse(portbindings.CAPABILITIES in port)
def test_port_vif_details(self):
- plugin = QuantumManager.get_plugin()
with self.port(name='name') as port:
port_id = port['port']['id']
# Check a response of create_port
self._check_response_portbindings(port['port'])
# Check a response of get_port
ctx = context.get_admin_context()
- port = plugin.get_port(ctx, port_id)
+ port = self._show('ports', port_id, quantum_context=ctx)['port']
self._check_response_portbindings(port)
# By default user is admin - now test non admin user
ctx = context.Context(user_id=None,
tenant_id=self._tenant_id,
is_admin=False,
read_deleted="no")
- non_admin_port = plugin.get_port(ctx, port_id)
+ non_admin_port = self._show(
+ 'ports', port_id, quantum_context=ctx)['port']
self._check_response_no_portbindings(non_admin_port)
def test_ports_vif_details(self):
tenant_id=self._tenant_id,
is_admin=False,
read_deleted="no")
- ports = plugin.get_ports(ctx)
+ ports = self._list('ports', quantum_context=ctx)['ports']
self.assertEqual(len(ports), 2)
for non_admin_port in ports:
self._check_response_no_portbindings(non_admin_port)
+
+
+class PortBindingsHostTestCaseMixin(object):
+ fmt = 'json'
+ hostname = 'testhost'
+
+ def _check_response_portbindings_host(self, port):
+ self.assertEqual(port[portbindings.HOST_ID], self.hostname)
+
+ def _check_response_no_portbindings_host(self, port):
+ self.assertIn('status', port)
+ self.assertNotIn(portbindings.HOST_ID, port)
+
+ def test_port_vif_non_admin(self):
+ with self.network(set_context=True,
+ tenant_id='test') as net1:
+ with self.subnet(network=net1) as subnet1:
+ host_arg = {portbindings.HOST_ID: self.hostname}
+ try:
+ with self.port(subnet=subnet1,
+ expected_res_status=403,
+ arg_list=(portbindings.HOST_ID,),
+ set_context=True,
+ tenant_id='test',
+ **host_arg):
+ pass
+ except exc.HTTPClientError:
+ pass
+
+ def test_port_vif_host(self):
+ host_arg = {portbindings.HOST_ID: self.hostname}
+ with self.port(name='name', arg_list=(portbindings.HOST_ID,),
+ **host_arg) as port:
+ port_id = port['port']['id']
+ # Check a response of create_port
+ self._check_response_portbindings_host(port['port'])
+ # Check a response of get_port
+ ctx = context.get_admin_context()
+ port = self._show('ports', port_id, quantum_context=ctx)['port']
+ self._check_response_portbindings_host(port)
+ # By default user is admin - now test non admin user
+ ctx = context.Context(user_id=None,
+ tenant_id=self._tenant_id,
+ is_admin=False,
+ read_deleted="no")
+ non_admin_port = self._show(
+ 'ports', port_id, quantum_context=ctx)['port']
+ self._check_response_no_portbindings_host(non_admin_port)
+
+ def test_ports_vif_host(self):
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ host_arg = {portbindings.HOST_ID: self.hostname}
+ with contextlib.nested(
+ self.port(name='name1',
+ arg_list=(portbindings.HOST_ID,),
+ **host_arg),
+ self.port(name='name2')):
+ ctx = context.get_admin_context()
+ ports = self._list('ports', quantum_context=ctx)['ports']
+ self.assertEqual(2, len(ports))
+ for port in ports:
+ if port['name'] == 'name1':
+ self._check_response_portbindings_host(port)
+ else:
+ self.assertFalse(port[portbindings.HOST_ID])
+ # By default user is admin - now test non admin user
+ ctx = context.Context(user_id=None,
+ tenant_id=self._tenant_id,
+ is_admin=False,
+ read_deleted="no")
+ ports = self._list('ports', quantum_context=ctx)['ports']
+ self.assertEqual(2, len(ports))
+ for non_admin_port in ports:
+ self._check_response_no_portbindings_host(non_admin_port)
+
+ def test_ports_vif_host_update(self):
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ host_arg = {portbindings.HOST_ID: self.hostname}
+ with contextlib.nested(
+ self.port(name='name1',
+ arg_list=(portbindings.HOST_ID,),
+ **host_arg),
+ self.port(name='name2')) as (port1, port2):
+ data = {'port': {portbindings.HOST_ID: 'testhosttemp'}}
+ req = self.new_update_request('ports', data, port1['port']['id'])
+ req.get_response(self.api)
+ req = self.new_update_request('ports', data, port2['port']['id'])
+ ctx = context.get_admin_context()
+ req.get_response(self.api)
+ ports = self._list('ports', quantum_context=ctx)['ports']
+ self.assertEqual(2, len(ports))
+ for port in ports:
+ self.assertEqual('testhosttemp', port[portbindings.HOST_ID])
+
+ def test_ports_vif_host_list(self):
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ host_arg = {portbindings.HOST_ID: self.hostname}
+ with contextlib.nested(
+ self.port(name='name1',
+ arg_list=(portbindings.HOST_ID,),
+ **host_arg),
+ self.port(name='name2'),
+ self.port(name='name3',
+ arg_list=(portbindings.HOST_ID,),
+ **host_arg),) as (port1, _port2, port3):
+ self._test_list_resources(
+ 'port', (port1, port3),
+ query_params='%s=%s' % (portbindings.HOST_ID, self.hostname))
class TestLinuxBridgePortBindingNoSG(TestLinuxBridgePortBinding):
HAS_PORT_FILTER = False
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_NOOP_DRIVER
+
+
+class TestOpenvswitchPortBindingHost(
+ LinuxBridgePluginV2TestCase,
+ test_bindings.PortBindingsHostTestCaseMixin):
+ pass
class TestOpenvswitchPortBindingNoSG(TestOpenvswitchPortBinding):
HAS_PORT_FILTER = False
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_NOOP_DRIVER
+
+
+class TestOpenvswitchPortBindingHost(
+ OpenvswitchPluginV2TestCase,
+ test_bindings.PortBindingsHostTestCaseMixin):
+ pass