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
This commit is contained in:
Gary Kotton 2012-11-10 05:56:07 +00:00
parent 4aaf0fe474
commit 64f2a38bc9
6 changed files with 246 additions and 31 deletions

View File

@ -15,6 +15,9 @@
"extension:router:add_router_interface": "rule:admin_or_owner", "extension:router:add_router_interface": "rule:admin_or_owner",
"extension:router:remove_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:read": "rule:admin_or_owner",
"subnets:private:write": "rule:admin_or_owner", "subnets:private:write": "rule:admin_or_owner",
"subnets:shared:read": "rule:regular_user", "subnets:shared:read": "rule:regular_user",

View File

@ -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 {}

View File

@ -25,6 +25,7 @@ from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base from quantum.db import dhcp_rpc_base
from quantum.db import l3_db from quantum.db import l3_db
from quantum.db import l3_rpc_base from quantum.db import l3_rpc_base
from quantum.extensions import portbindings
from quantum.extensions import providernet as provider from quantum.extensions import providernet as provider
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
from quantum.openstack.common import log as logging 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 handled, by adding support for extended attributes to the
QuantumDbPluginV2 base class. When that occurs, this class should QuantumDbPluginV2 base class. When that occurs, this class should
be updated to take advantage of it. 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 # This attribute specifies whether the plugin supports or not
@ -150,7 +154,12 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
# is qualified by class # is qualified by class
__native_bulk_support = True __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): def __init__(self):
db.initialize() db.initialize()
@ -197,6 +206,12 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
self._add_network(entry) self._add_network(entry)
LOG.debug("network VLAN ranges: %s" % self.network_vlan_ranges) 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): def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max):
self._add_network(physical_network) self._add_network(physical_network)
self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max)) 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 # REVISIT(rkukura) Use core mechanism for attribute authorization
# when available. # 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): 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']) binding = db.get_network_binding(context.session, network['id'])
if binding.vlan_id == constants.FLAT_VLAN_ID: if binding.vlan_id == constants.FLAT_VLAN_ID:
network[provider.NETWORK_TYPE] = constants.TYPE_FLAT network[provider.NETWORK_TYPE] = constants.TYPE_FLAT
@ -248,7 +253,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
return (None, None, None) return (None, None, None)
# Authorize before exposing plugin details to client # 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: if not network_type_set:
msg = _("provider:network_type required") msg = _("provider:network_type required")
@ -312,7 +317,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
return return
# Authorize before exposing plugin details to client # 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") msg = _("plugin does not support updating provider attributes")
raise q_exc.InvalidInput(error_message=msg) 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] 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): def update_port(self, context, id, port):
original_port = super(LinuxBridgePluginV2, self).get_port(context, original_port = super(LinuxBridgePluginV2, self).get_port(context,
id) id)
@ -402,7 +427,7 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
self.notifier.port_update(context, port, self.notifier.port_update(context, port,
binding.physical_network, binding.physical_network,
binding.vlan_id) binding.vlan_id)
return port return self._extend_port_dict_binding(context, port)
def delete_port(self, context, id, l3_port_check=True): def delete_port(self, context, id, l3_port_check=True):

View File

@ -31,6 +31,7 @@ from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base from quantum.db import dhcp_rpc_base
from quantum.db import l3_db from quantum.db import l3_db
from quantum.db import l3_rpc_base from quantum.db import l3_rpc_base
from quantum.extensions import portbindings
from quantum.extensions import providernet as provider from quantum.extensions import providernet as provider
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
from quantum.openstack.common import log as logging from quantum.openstack.common import log as logging
@ -124,7 +125,7 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
class AgentNotifierApi(proxy.RpcProxy): class AgentNotifierApi(proxy.RpcProxy):
'''Agent side of the linux bridge rpc API. '''Agent side of the openvswitch rpc API.
API version history: API version history:
1.0 - Initial version. 1.0 - Initial version.
@ -184,13 +185,21 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
handled, by adding support for extended attributes to the handled, by adding support for extended attributes to the
QuantumDbPluginV2 base class. When that occurs, this class should QuantumDbPluginV2 base class. When that occurs, this class should
be updated to take advantage of it. 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 # This attribute specifies whether the plugin supports or not
# bulk operations. Name mangling is used in order to ensure it # bulk operations. Name mangling is used in order to ensure it
# is qualified by class # is qualified by class
__native_bulk_support = True __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): def __init__(self, configfile=None):
ovs_db_v2.initialize() ovs_db_v2.initialize()
@ -271,18 +280,14 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
# TODO(rkukura) Use core mechanism for attribute authorization # TODO(rkukura) Use core mechanism for attribute authorization
# when available. # when available.
def _check_provider_view_auth(self, context, network): def _check_view_auth(self, context, resource, action):
return policy.check(context, return policy.check(context, action, resource)
"extension:provider_network:view",
network)
def _enforce_provider_set_auth(self, context, network): def _enforce_set_auth(self, context, resource, action):
return policy.enforce(context, policy.enforce(context, action, resource)
"extension:provider_network:set",
network)
def _extend_network_dict_provider(self, context, 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 = ovs_db_v2.get_network_binding(context.session, binding = ovs_db_v2.get_network_binding(context.session,
network['id']) network['id'])
network[provider.NETWORK_TYPE] = binding.network_type network[provider.NETWORK_TYPE] = binding.network_type
@ -313,7 +318,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
return (None, None, None) return (None, None, None)
# Authorize before exposing plugin details to client # 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: if not network_type_set:
msg = _("provider:network_type required") msg = _("provider:network_type required")
@ -390,7 +395,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
return return
# Authorize before exposing plugin details to client # 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") msg = _("plugin does not support updating provider attributes")
raise q_exc.InvalidInput(error_message=msg) 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] 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): def update_port(self, context, id, port):
original_port = super(OVSQuantumPluginV2, self).get_port(context, original_port = super(OVSQuantumPluginV2, self).get_port(context,
id) id)
@ -491,7 +516,7 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
binding.network_type, binding.network_type,
binding.segmentation_id, binding.segmentation_id,
binding.physical_network) binding.physical_network)
return port return self._extend_port_dict_binding(context, port)
def delete_port(self, context, id, l3_port_check=True): def delete_port(self, context, id, l3_port_check=True):

View File

@ -13,6 +13,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # 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 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, class TestLinuxBridgePortsV2(test_plugin.TestPortsV2,
LinuxBridgePluginV2TestCase): 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, class TestLinuxBridgeNetworksV2(test_plugin.TestNetworksV2,

View File

@ -13,6 +13,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # 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 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, class TestOpenvswitchPortsV2(test_plugin.TestPortsV2,
OpenvswitchPluginV2TestCase): 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, class TestOpenvswitchNetworksV2(test_plugin.TestNetworksV2,