The is part of the blueprint vif-plugging-improvements.
The patch adds an extension to Quantum that enables the plugin to
return VIF details.
At the moment it supports openvswitch and linuxbridge.
Change-Id: Ib9b4d34e668e2ddc61c152c2c4cd4a01f2d0de40
"extension:router:add_router_interface": "rule:admin_or_owner",
"extension:router:remove_router_interface": "rule:admin_or_owner",
+ "extension:port_binding:view": "rule:admin_only",
+ "extension:port_binding:set": "rule:admin_only",
+
"subnets:private:read": "rule:admin_or_owner",
"subnets:private:write": "rule:admin_or_owner",
"subnets:shared:read": "rule:regular_user",
--- /dev/null
+# Copyright (c) 2012 OpenStack, LLC.
+#
+# 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.
+
+from quantum.api.v2 import attributes
+
+# The service will return the vif type for the specific port.
+VIF_TYPE = 'binding:vif_type'
+# In some cases different implementations may be run on different hosts.
+# The host on which the port will be allocated.
+HOST_ID = 'binding:host_id'
+# The profile will be a dictionary that enables the application running
+# on the specific host to pass and receive vif port specific information to
+# the plugin.
+PROFILE = 'binding:profile'
+
+VIF_TYPE_OVS = 'ovs'
+VIF_TYPE_BRIDGE = 'bridge'
+VIF_TYPE_802_QBG = '802.1qbg'
+VIF_TYPE_802_QBH = '802.1qbh'
+VIF_TYPE_OTHER = 'other'
+
+EXTENDED_ATTRIBUTES_2_0 = {
+ 'ports': {
+ VIF_TYPE: {'allow_post': False, 'allow_put': False,
+ 'default': attributes.ATTR_NOT_SPECIFIED,
+ 'is_visible': True},
+ HOST_ID: {'allow_post': True, 'allow_put': True,
+ 'default': attributes.ATTR_NOT_SPECIFIED,
+ 'is_visible': True},
+ PROFILE: {'allow_post': True, 'allow_put': True,
+ 'default': attributes.ATTR_NOT_SPECIFIED,
+ 'is_visible': True},
+ }
+}
+
+
+class Portbindings(object):
+ """Extension class supporting port bindings.
+
+ This class is used by quantum's extension framework to make
+ metadata about the port bindings available to external applications.
+
+ With admin rights one will be able to update and read the values.
+ """
+
+ @classmethod
+ def get_name(cls):
+ return "Port Binding"
+
+ @classmethod
+ def get_alias(cls):
+ return "binding"
+
+ @classmethod
+ def get_description(cls):
+ return "Expose port bindings of a virtual port to external application"
+
+ @classmethod
+ def get_namespace(cls):
+ return "http://docs.openstack.org/ext/binding/api/v1.0"
+
+ @classmethod
+ def get_updated(cls):
+ return "2012-11-14T10:00:00-00:00"
+
+ def get_extended_resources(self, version):
+ if version == "2.0":
+ return EXTENDED_ATTRIBUTES_2_0
+ else:
+ return {}
from quantum.db import dhcp_rpc_base
from quantum.db import l3_db
from quantum.db import l3_rpc_base
+from quantum.extensions import portbindings
from quantum.extensions import providernet as provider
from quantum.openstack.common import cfg
from quantum.openstack.common import log as logging
handled, by adding support for extended attributes to the
QuantumDbPluginV2 base class. When that occurs, this class should
be updated to take advantage of it.
+
+ The port binding extension enables an external application relay
+ information to and from the plugin.
"""
# This attribute specifies whether the plugin supports or not
# is qualified by class
__native_bulk_support = True
- supported_extension_aliases = ["provider", "router"]
+ supported_extension_aliases = ["provider", "router", "binding"]
+
+ 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):
db.initialize()
self._add_network(entry)
LOG.debug("network VLAN ranges: %s" % self.network_vlan_ranges)
+ def _check_view_auth(self, context, resource, action):
+ return policy.check(context, action, resource)
+
+ def _enforce_set_auth(self, context, resource, action):
+ policy.enforce(context, action, resource)
+
def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max):
self._add_network(physical_network)
self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max))
# REVISIT(rkukura) Use core mechanism for attribute authorization
# when available.
- def _check_provider_view_auth(self, context, network):
- return policy.check(context,
- "extension:provider_network:view",
- network)
-
- def _enforce_provider_set_auth(self, context, network):
- return policy.enforce(context,
- "extension:provider_network:set",
- network)
-
def _extend_network_dict_provider(self, context, network):
- if self._check_provider_view_auth(context, network):
+ if self._check_view_auth(context, network, self.network_view):
binding = db.get_network_binding(context.session, network['id'])
if binding.vlan_id == constants.FLAT_VLAN_ID:
network[provider.NETWORK_TYPE] = constants.TYPE_FLAT
return (None, None, None)
# Authorize before exposing plugin details to client
- self._enforce_provider_set_auth(context, attrs)
+ self._enforce_set_auth(context, attrs, self.network_set)
if not network_type_set:
msg = _("provider:network_type required")
return
# Authorize before exposing plugin details to client
- self._enforce_provider_set_auth(context, attrs)
+ self._enforce_set_auth(context, attrs, self.network_set)
msg = _("plugin does not support updating provider attributes")
raise q_exc.InvalidInput(error_message=msg)
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
+ return port
+
+ def create_port(self, context, port):
+ port = super(LinuxBridgePluginV2, self).create_port(context, port)
+ return self._extend_port_dict_binding(context, port)
+
+ def get_port(self, context, id, fields=None):
+ port = super(LinuxBridgePluginV2, self).get_port(context, id, fields)
+ return self._fields(self._extend_port_dict_binding(context, port),
+ fields)
+
+ def get_ports(self, context, filters=None, fields=None):
+ ports = super(LinuxBridgePluginV2, self).get_ports(context, filters,
+ fields)
+ return [self._fields(self._extend_port_dict_binding(context, port),
+ fields) for port in ports]
+
def update_port(self, context, id, port):
original_port = super(LinuxBridgePluginV2, self).get_port(context,
id)
self.notifier.port_update(context, port,
binding.physical_network,
binding.vlan_id)
- return port
+ return self._extend_port_dict_binding(context, port)
def delete_port(self, context, id, l3_port_check=True):
from quantum.db import dhcp_rpc_base
from quantum.db import l3_db
from quantum.db import l3_rpc_base
+from quantum.extensions import portbindings
from quantum.extensions import providernet as provider
from quantum.openstack.common import cfg
from quantum.openstack.common import log as logging
class AgentNotifierApi(proxy.RpcProxy):
- '''Agent side of the linux bridge rpc API.
+ '''Agent side of the openvswitch rpc API.
API version history:
1.0 - Initial version.
handled, by adding support for extended attributes to the
QuantumDbPluginV2 base class. When that occurs, this class should
be updated to take advantage of it.
+
+ The port binding extension enables an external application relay
+ information to and from the plugin.
"""
# This attribute specifies whether the plugin supports or not
# bulk operations. Name mangling is used in order to ensure it
# is qualified by class
__native_bulk_support = True
- supported_extension_aliases = ["provider", "router"]
+ supported_extension_aliases = ["provider", "router", "binding"]
+
+ 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):
ovs_db_v2.initialize()
# TODO(rkukura) Use core mechanism for attribute authorization
# when available.
- def _check_provider_view_auth(self, context, network):
- return policy.check(context,
- "extension:provider_network:view",
- network)
+ def _check_view_auth(self, context, resource, action):
+ return policy.check(context, action, resource)
- def _enforce_provider_set_auth(self, context, network):
- return policy.enforce(context,
- "extension:provider_network:set",
- network)
+ def _enforce_set_auth(self, context, resource, action):
+ policy.enforce(context, action, resource)
def _extend_network_dict_provider(self, context, network):
- if self._check_provider_view_auth(context, network):
+ if self._check_view_auth(context, network, self.network_view):
binding = ovs_db_v2.get_network_binding(context.session,
network['id'])
network[provider.NETWORK_TYPE] = binding.network_type
return (None, None, None)
# Authorize before exposing plugin details to client
- self._enforce_provider_set_auth(context, attrs)
+ self._enforce_set_auth(context, attrs, self.network_set)
if not network_type_set:
msg = _("provider:network_type required")
return
# Authorize before exposing plugin details to client
- self._enforce_provider_set_auth(context, attrs)
+ self._enforce_set_auth(context, attrs, self.network_set)
msg = _("plugin does not support updating provider attributes")
raise q_exc.InvalidInput(error_message=msg)
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
+ return port
+
+ def create_port(self, context, port):
+ port = super(OVSQuantumPluginV2, self).create_port(context, port)
+ return self._extend_port_dict_binding(context, port)
+
+ def get_port(self, context, id, fields=None):
+ port = super(OVSQuantumPluginV2, self).get_port(context, id, fields)
+ return self._fields(self._extend_port_dict_binding(context, port),
+ fields)
+
+ def get_ports(self, context, filters=None, fields=None):
+ ports = super(OVSQuantumPluginV2, self).get_ports(context, filters,
+ fields)
+ return [self._fields(self._extend_port_dict_binding(context, port),
+ fields) for port in ports]
+
def update_port(self, context, id, port):
original_port = super(OVSQuantumPluginV2, self).get_port(context,
id)
binding.network_type,
binding.segmentation_id,
binding.physical_network)
- return port
+ return self._extend_port_dict_binding(context, port)
def delete_port(self, context, id, l3_port_check=True):
# See the License for the specific language governing permissions and
# limitations under the License.
+import contextlib
+
+from quantum import context
+from quantum.extensions import portbindings
+from quantum.manager import QuantumManager
+from quantum.openstack.common import cfg
from quantum.tests.unit import test_db_plugin as test_plugin
class TestLinuxBridgePortsV2(test_plugin.TestPortsV2,
LinuxBridgePluginV2TestCase):
- pass
+ def test_port_vif_details(self):
+ plugin = QuantumManager.get_plugin()
+ with self.port(name='name') as port:
+ port_id = port['port']['id']
+ self.assertEqual(port['port']['binding:vif_type'],
+ portbindings.VIF_TYPE_BRIDGE)
+ # 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)
+ self.assertTrue('status' in non_admin_port)
+ self.assertFalse('binding:vif_type' in non_admin_port)
+
+ def test_ports_vif_details(self):
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ plugin = QuantumManager.get_plugin()
+ with contextlib.nested(self.port(), self.port()) as (port1, port2):
+ ctx = context.get_admin_context()
+ ports = plugin.get_ports(ctx)
+ self.assertEqual(len(ports), 2)
+ for port in ports:
+ self.assertEqual(port['binding:vif_type'],
+ portbindings.VIF_TYPE_BRIDGE)
+ # 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 = plugin.get_ports(ctx)
+ self.assertEqual(len(ports), 2)
+ for non_admin_port in ports:
+ self.assertTrue('status' in non_admin_port)
+ self.assertFalse('binding:vif_type' in non_admin_port)
class TestLinuxBridgeNetworksV2(test_plugin.TestNetworksV2,
# See the License for the specific language governing permissions and
# limitations under the License.
+import contextlib
+
+from quantum import context
+from quantum.extensions import portbindings
+from quantum.manager import QuantumManager
+from quantum.openstack.common import cfg
from quantum.tests.unit import test_db_plugin as test_plugin
class TestOpenvswitchPortsV2(test_plugin.TestPortsV2,
OpenvswitchPluginV2TestCase):
- pass
+ def test_port_vif_details(self):
+ plugin = QuantumManager.get_plugin()
+ with self.port(name='name') as port:
+ port_id = port['port']['id']
+ self.assertEqual(port['port']['binding:vif_type'],
+ portbindings.VIF_TYPE_OVS)
+ # 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)
+ self.assertTrue('status' in non_admin_port)
+ self.assertFalse('binding:vif_type' in non_admin_port)
+
+ def test_ports_vif_details(self):
+ cfg.CONF.set_default('allow_overlapping_ips', True)
+ plugin = QuantumManager.get_plugin()
+ with contextlib.nested(self.port(), self.port()) as (port1, port2):
+ ctx = context.get_admin_context()
+ ports = plugin.get_ports(ctx)
+ self.assertEqual(len(ports), 2)
+ for port in ports:
+ self.assertEqual(port['binding:vif_type'],
+ portbindings.VIF_TYPE_OVS)
+ # 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 = plugin.get_ports(ctx)
+ self.assertEqual(len(ports), 2)
+ for non_admin_port in ports:
+ self.assertTrue('status' in non_admin_port)
+ self.assertFalse('binding:vif_type' in non_admin_port)
class TestOpenvswitchNetworksV2(test_plugin.TestNetworksV2,