--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2014 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.
+#
+
+"""ml2 binding:profile
+
+Revision ID: 157a5d299379
+Revises: 50d5ba354c23
+Create Date: 2014-02-13 23:48:25.147279
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '157a5d299379'
+down_revision = '50d5ba354c23'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+ 'neutron.plugins.ml2.plugin.Ml2Plugin'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+
+
+def upgrade(active_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.add_column('ml2_port_bindings',
+ sa.Column('profile', sa.String(length=4095),
+ nullable=False, server_default=''))
+
+
+def downgrade(active_plugins=None, options=None):
+ if not migration.should_run(active_plugins, migration_for_plugins):
+ return
+
+ op.drop_column('ml2_port_bindings', 'profile')
except exc.NoResultFound:
record = models.PortBinding(
port_id=port_id,
- host='',
vif_type=portbindings.VIF_TYPE_UNBOUND)
session.add(record)
return record
"""
binding = context._binding
LOG.debug(_("Attempting to bind port %(port)s on host %(host)s "
- "for vnic_type %(vnic_type)s"),
+ "for vnic_type %(vnic_type)s with profile %(profile)s"),
{'port': context._port['id'],
'host': binding.host,
- 'vnic_type': binding.vnic_type})
+ 'vnic_type': binding.vnic_type,
+ 'profile': binding.profile})
for driver in self.ordered_mech_drivers:
try:
driver.obj.bind_port(context)
binding.driver = driver.name
LOG.debug(_("Bound port: %(port)s, host: %(host)s, "
"vnic_type: %(vnic_type)s, "
+ "profile: %(profile)s"
"driver: %(driver)s, vif_type: %(vif_type)s, "
"vif_details: %(vif_details)s, "
"segment: %(segment)s"),
{'port': context._port['id'],
'host': binding.host,
'vnic_type': binding.vnic_type,
+ 'profile': binding.profile,
'driver': binding.driver,
'vif_type': binding.vif_type,
'vif_details': binding.vif_details,
from neutron.db import models_v2
from neutron.extensions import portbindings
+BINDING_PROFILE_LEN = 4095
+
class NetworkSegment(model_base.BASEV2, models_v2.HasId):
"""Represent persistent state of a network segment.
port_id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
- host = sa.Column(sa.String(255), nullable=False)
+ host = sa.Column(sa.String(255), nullable=False, default='')
vnic_type = sa.Column(sa.String(64), nullable=False,
default=portbindings.VNIC_NORMAL)
+ profile = sa.Column(sa.String(BINDING_PROFILE_LEN), nullable=False,
+ default='')
vif_type = sa.Column(sa.String(64), nullable=False)
vif_details = sa.Column(sa.String(4095), nullable=False, default='')
driver = sa.Column(sa.String(64))
binding = mech_context._binding
port = mech_context.current
self._update_port_dict_binding(port, binding)
+
host = attrs and attrs.get(portbindings.HOST_ID)
host_set = attributes.is_attr_set(host)
+
vnic_type = attrs and attrs.get(portbindings.VNIC_TYPE)
vnic_type_set = attributes.is_attr_set(vnic_type)
+ # CLI can't send {}, so treat None as {}
+ profile = attrs and attrs.get(portbindings.PROFILE)
+ profile_set = profile is not attributes.ATTR_NOT_SPECIFIED
+ if profile_set and not profile:
+ profile = {}
+
if binding.vif_type != portbindings.VIF_TYPE_UNBOUND:
- if (not host_set and not vnic_type_set and binding.segment and
+ if (not host_set and not vnic_type_set and not profile_set and
+ binding.segment and
self.mechanism_manager.validate_port_binding(mech_context)):
return False
self.mechanism_manager.unbind_port(mech_context)
self._update_port_dict_binding(port, binding)
# Return True only if an agent notification is needed.
- # This will happen if a new host or vnic_type was specified that
- # differs from the current one. Note that host_set is True
+ # This will happen if a new host, vnic_type, or profile was specified
+ # that differs from the current one. Note that host_set is True
# even if the host is an empty string
ret_value = ((host_set and binding.get('host') != host) or
- (vnic_type_set and binding.get('vnic_type') != vnic_type))
+ (vnic_type_set and
+ binding.get('vnic_type') != vnic_type) or
+ (profile_set and self._get_profile(binding) != profile))
+
if host_set:
binding.host = host
port[portbindings.HOST_ID] = host
binding.vnic_type = vnic_type
port[portbindings.VNIC_TYPE] = vnic_type
+ if profile_set:
+ binding.profile = jsonutils.dumps(profile)
+ if len(binding.profile) > models.BINDING_PROFILE_LEN:
+ msg = _("binding:profile value too large")
+ raise exc.InvalidInput(error_message=msg)
+ port[portbindings.PROFILE] = profile
+
+ # To try to [re]bind if host is non-empty.
if binding.host:
self.mechanism_manager.bind_port(mech_context)
self._update_port_dict_binding(port, binding)
def _update_port_dict_binding(self, port, binding):
port[portbindings.HOST_ID] = binding.host
port[portbindings.VNIC_TYPE] = binding.vnic_type
+ port[portbindings.PROFILE] = self._get_profile(binding)
port[portbindings.VIF_TYPE] = binding.vif_type
port[portbindings.VIF_DETAILS] = self._get_vif_details(binding)
'port': binding.port_id})
return {}
+ def _get_profile(self, binding):
+ if binding.profile:
+ try:
+ return jsonutils.loads(binding.profile)
+ except Exception:
+ LOG.error(_("Serialized profile DB value '%(value)s' for "
+ "port %(port)s is invalid"),
+ {'value': binding.profile,
+ 'port': binding.port_id})
+ return {}
+
def _delete_port_binding(self, mech_context):
binding = mech_context._binding
port = mech_context.current
for non_admin_port in ports:
self._check_response_no_portbindings(non_admin_port)
- def _check_default_port_binding_profile(self, port):
+ def _check_port_binding_profile(self, port, profile=None):
# For plugins which does not use binding:profile attr
# we just check an operation for the port succeed.
self.assertIn('id', port)
profile_arg = {portbindings.PROFILE: profile}
with self.port(arg_list=(portbindings.PROFILE,),
**profile_arg) as port:
- self._check_default_port_binding_profile(port['port'])
+ port_id = port['port']['id']
+ self._check_port_binding_profile(port['port'], profile)
+ port = self._show('ports', port_id)
+ self._check_port_binding_profile(port['port'], profile)
def test_create_port_binding_profile_none(self):
self._test_create_port_binding_profile(None)
profile_arg = {portbindings.PROFILE: profile}
with self.port() as port:
# print "(1) %s" % port
- self._check_default_port_binding_profile(port['port'])
+ self._check_port_binding_profile(port['port'])
port_id = port['port']['id']
ctx = context.get_admin_context()
port = self._update('ports', port_id, {'port': profile_arg},
neutron_context=ctx)['port']
- self._check_default_port_binding_profile(port)
+ self._check_port_binding_profile(port, profile)
+ port = self._show('ports', port_id)['port']
+ self._check_port_binding_profile(port, profile)
def test_update_port_binding_profile_none(self):
self._test_update_port_binding_profile(None)
import mock
import testtools
+import webob
from neutron.common import exceptions as exc
from neutron import context
test_sg_rpc.set_firewall_driver(self.FIREWALL_DRIVER)
super(TestMl2PortBinding, self).setUp()
+ def _check_port_binding_profile(self, port, profile=None):
+ self.assertIn('id', port)
+ self.assertIn(portbindings.PROFILE, port)
+ value = port[portbindings.PROFILE]
+ self.assertEqual(profile or {}, value)
+
+ def test_create_port_binding_profile(self):
+ self._test_create_port_binding_profile({'a': 1, 'b': 2})
+
+ def test_update_port_binding_profile(self):
+ self._test_update_port_binding_profile({'c': 3})
+
+ def test_create_port_binding_profile_too_big(self):
+ s = 'x' * 5000
+ profile_arg = {portbindings.PROFILE: {'d': s}}
+ try:
+ with self.port(expected_res_status=400,
+ arg_list=(portbindings.PROFILE,),
+ **profile_arg):
+ pass
+ except webob.exc.HTTPClientError:
+ pass
+
+ def test_remove_port_binding_profile(self):
+ profile = {'e': 5}
+ profile_arg = {portbindings.PROFILE: profile}
+ with self.port(arg_list=(portbindings.PROFILE,),
+ **profile_arg) as port:
+ self._check_port_binding_profile(port['port'], profile)
+ port_id = port['port']['id']
+ profile_arg = {portbindings.PROFILE: None}
+ port = self._update('ports', port_id,
+ {'port': profile_arg})['port']
+ self._check_port_binding_profile(port)
+ port = self._show('ports', port_id)['port']
+ self._check_port_binding_profile(port)
+
class TestMl2PortBindingNoSG(TestMl2PortBinding):
HAS_PORT_FILTER = False