From 64f2a38bc9a0551bb761c32058ab9c7929c7c0de Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Sat, 10 Nov 2012 05:56:07 +0000 Subject: [PATCH] Add VIF binding extensions 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 --- etc/policy.json | 3 + quantum/extensions/portbindings.py | 82 +++++++++++++++++++ .../plugins/linuxbridge/lb_quantum_plugin.py | 55 +++++++++---- .../plugins/openvswitch/ovs_quantum_plugin.py | 53 ++++++++---- .../linuxbridge/test_linuxbridge_plugin.py | 42 +++++++++- .../openvswitch/test_openvswitch_plugin.py | 42 +++++++++- 6 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 quantum/extensions/portbindings.py diff --git a/etc/policy.json b/etc/policy.json index d5641de42..2961b3882 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -15,6 +15,9 @@ "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", diff --git a/quantum/extensions/portbindings.py b/quantum/extensions/portbindings.py new file mode 100644 index 000000000..e3276c86f --- /dev/null +++ b/quantum/extensions/portbindings.py @@ -0,0 +1,82 @@ +# 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 {} diff --git a/quantum/plugins/linuxbridge/lb_quantum_plugin.py b/quantum/plugins/linuxbridge/lb_quantum_plugin.py index 711a059c2..0a407752e 100644 --- a/quantum/plugins/linuxbridge/lb_quantum_plugin.py +++ b/quantum/plugins/linuxbridge/lb_quantum_plugin.py @@ -25,6 +25,7 @@ from quantum.db import db_base_plugin_v2 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 @@ -143,6 +144,9 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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 @@ -150,7 +154,12 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, # 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() @@ -197,6 +206,12 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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)) @@ -208,18 +223,8 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, # 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 @@ -248,7 +253,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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") @@ -312,7 +317,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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) @@ -392,6 +397,26 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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) @@ -402,7 +427,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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): diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index eea17377c..195bac2f8 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -31,6 +31,7 @@ from quantum.db import db_base_plugin_v2 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 @@ -124,7 +125,7 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin, 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. @@ -184,13 +185,21 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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() @@ -271,18 +280,14 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, # 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 @@ -313,7 +318,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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") @@ -390,7 +395,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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) @@ -480,6 +485,26 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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) @@ -491,7 +516,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, 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): diff --git a/quantum/tests/unit/linuxbridge/test_linuxbridge_plugin.py b/quantum/tests/unit/linuxbridge/test_linuxbridge_plugin.py index 3a2c0b7c2..dd510e7a3 100644 --- a/quantum/tests/unit/linuxbridge/test_linuxbridge_plugin.py +++ b/quantum/tests/unit/linuxbridge/test_linuxbridge_plugin.py @@ -13,6 +13,12 @@ # 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 @@ -37,7 +43,41 @@ class TestLinuxBridgeV2HTTPResponse(test_plugin.TestV2HTTPResponse, 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, diff --git a/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py b/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py index 6727d9221..49a109181 100644 --- a/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py +++ b/quantum/tests/unit/openvswitch/test_openvswitch_plugin.py @@ -13,6 +13,12 @@ # 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 @@ -37,7 +43,41 @@ class TestOpenvswitchV2HTTPResponse(test_plugin.TestV2HTTPResponse, 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, -- 2.45.2