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:
parent
4aaf0fe474
commit
64f2a38bc9
@ -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",
|
||||
|
82
quantum/extensions/portbindings.py
Normal file
82
quantum/extensions/portbindings.py
Normal 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 {}
|
@ -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):
|
||||
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user