Export portinfo thru portbinding ext in NEC plugin

blueprint nec-port-binding

* Add host-id support in port-binding extension.
* Expose portinfo thourgh binding:profile attr in a port.
  portinfo is a mapping between neutron port id and OpenFlow switch
  physical information (datapath_id and port_no)

It changes the following in portinfo db model
* Add cascade on delete to delete an associated portinfo
  when deleting the port.
* Use joined query for portinfo model to retrieve an associated
  portinfo when querying a port.

Change-Id: Id88d93dc0770a1290714436324b1b53c0b023eeb
This commit is contained in:
Akihiro MOTOKI 2013-08-21 15:43:19 +09:00
parent 795155c743
commit 99310d5174
8 changed files with 605 additions and 97 deletions

View File

@ -0,0 +1,65 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 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.
#
"""NEC Port Binding
Revision ID: 2a3bae1ceb8
Revises: 46a0efbd8f0
Create Date: 2013-08-22 11:09:19.955386
"""
# revision identifiers, used by Alembic.
revision = '2a3bae1ceb8'
down_revision = '46a0efbd8f0'
# Change to ['*'] if this migration applies to all plugins
migration_for_plugins = [
'neutron.plugins.nec.nec_plugin.NECPluginV2'
]
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.create_table(
'portbindingports',
sa.Column('port_id', sa.String(length=36), nullable=False),
sa.Column('host', sa.String(length=255), nullable=False),
sa.ForeignKeyConstraint(['port_id'], ['ports.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('port_id')
)
op.create_foreign_key(
'portinfos_ibfk_1',
source='portinfos', referent='ports',
local_cols=['id'], remote_cols=['id'],
ondelete='CASCADE')
def downgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.drop_constraint('portinfos_ibfk_1', 'portinfos', 'foreignkey')
op.drop_table('portbindingports')

View File

@ -33,3 +33,14 @@ class OFCConsistencyBroken(qexc.NeutronException):
class PortInfoNotFound(qexc.NotFound): class PortInfoNotFound(qexc.NotFound):
message = _("PortInfo %(id)s could not be found") message = _("PortInfo %(id)s could not be found")
class ProfilePortInfoInvalidDataPathId(qexc.InvalidInput):
message = _('Invalid input for operation: '
'portinfo:datapath_id should be a hex string '
'with at most 8 bytes')
class ProfilePortInfoInvalidPortNo(qexc.InvalidInput):
message = _('Invalid input for operation: '
'portinfo:port_no should be [0:65535]')

View File

@ -16,6 +16,7 @@
# @author: Ryota MIBU # @author: Ryota MIBU
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm
from neutron.db import model_base from neutron.db import model_base
from neutron.db import models_v2 from neutron.db import models_v2
@ -74,9 +75,17 @@ class OFCFilter(model_base.BASEV2, models_v2.HasId, HasNeutronId):
"""Represents a Filter on OpenFlow Network/Controller.""" """Represents a Filter on OpenFlow Network/Controller."""
class PortInfo(model_base.BASEV2, models_v2.HasId): class PortInfo(model_base.BASEV2):
"""Represents a Virtual Interface.""" """Represents a Virtual Interface."""
id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
datapath_id = sa.Column(sa.String(36), nullable=False) datapath_id = sa.Column(sa.String(36), nullable=False)
port_no = sa.Column(sa.Integer, nullable=False) port_no = sa.Column(sa.Integer, nullable=False)
vlan_id = sa.Column(sa.Integer, nullable=False) vlan_id = sa.Column(sa.Integer, nullable=False)
mac = sa.Column(sa.String(32), nullable=False) mac = sa.Column(sa.String(32), nullable=False)
port = orm.relationship(
models_v2.Port,
backref=orm.backref("portinfo",
lazy='joined', uselist=False,
cascade='delete'))

View File

@ -19,6 +19,7 @@
from neutron.agent import securitygroups_rpc as sg_rpc from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.api.v2 import attributes as attrs
from neutron.common import constants as const from neutron.common import constants as const
from neutron.common import exceptions as q_exc from neutron.common import exceptions as q_exc
from neutron.common import rpc as q_rpc from neutron.common import rpc as q_rpc
@ -31,6 +32,7 @@ from neutron.db import extraroute_db
from neutron.db import l3_gwmode_db from neutron.db import l3_gwmode_db
from neutron.db import l3_rpc_base from neutron.db import l3_rpc_base
from neutron.db import portbindings_base from neutron.db import portbindings_base
from neutron.db import portbindings_db
from neutron.db import quota_db # noqa from neutron.db import quota_db # noqa
from neutron.db import securitygroups_rpc_base as sg_db_rpc from neutron.db import securitygroups_rpc_base as sg_db_rpc
from neutron.extensions import portbindings from neutron.extensions import portbindings
@ -55,7 +57,7 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
agentschedulers_db.L3AgentSchedulerDbMixin, agentschedulers_db.L3AgentSchedulerDbMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin, agentschedulers_db.DhcpAgentSchedulerDbMixin,
packet_filter.PacketFilterMixin, packet_filter.PacketFilterMixin,
portbindings_base.PortBindingBaseMixin): portbindings_db.PortBindingMixin):
"""NECPluginV2 controls an OpenFlow Controller. """NECPluginV2 controls an OpenFlow Controller.
The Neutron NECPluginV2 maps L2 logical networks to L2 virtualized networks The Neutron NECPluginV2 maps L2 logical networks to L2 virtualized networks
@ -342,6 +344,111 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
'security-group' in self.supported_extension_aliases}} 'security-group' in self.supported_extension_aliases}}
return binding return binding
def _extend_port_dict_binding_portinfo(self, port_res, portinfo):
if portinfo:
port_res[portbindings.PROFILE] = {
'portinfo:datapath_id': portinfo['datapath_id'],
'portinfo:port_no': portinfo['port_no'],
}
elif portbindings.PROFILE in port_res:
del port_res[portbindings.PROFILE]
def _validate_portinfo(self, profile):
key_specs = {
'portinfo:datapath_id': {'type:string': None, 'required': True},
'portinfo:port_no': {'type:non_negative': None, 'required': True,
'convert_to': attrs.convert_to_int}
}
msg = attrs._validate_dict_or_empty(profile, key_specs=key_specs)
if msg:
raise q_exc.InvalidInput(error_message=msg)
datapath_id = profile.get('portinfo:datapath_id')
port_no = profile.get('portinfo:port_no')
try:
dpid = int(datapath_id, 16)
except ValueError:
raise nexc.ProfilePortInfoInvalidDataPathId()
if dpid > 0xffffffffffffffffL:
raise nexc.ProfilePortInfoInvalidDataPathId()
# Make sure dpid is a hex string beginning with 0x.
dpid = hex(dpid)
if int(port_no) > 65535:
raise nexc.ProfilePortInfoInvalidPortNo()
return {'datapath_id': dpid, 'port_no': port_no}
def _process_portbindings_portinfo_create(self, context, port_data, port):
"""Add portinfo according to bindings:profile in create_port().
:param context: neutron api request context
:param port_data: port attributes passed in PUT request
:param port: port attributes to be returned
"""
profile = port_data.get(portbindings.PROFILE)
# If portbindings.PROFILE is None, unspecified or an empty dict
# it is regarded that portbinding.PROFILE is not set.
profile_set = attrs.is_attr_set(profile) and profile
if profile_set:
portinfo = self._validate_portinfo(profile)
portinfo['mac'] = port['mac_address']
ndb.add_portinfo(context.session, port['id'], **portinfo)
else:
portinfo = None
self._extend_port_dict_binding_portinfo(port, portinfo)
def _process_portbindings_portinfo_update(self, context, port_data, port):
"""Update portinfo according to bindings:profile in update_port().
:param context: neutron api request context
:param port_data: port attributes passed in PUT request
:param port: port attributes to be returned
:returns: 'ADD', 'MOD', 'DEL' or None
"""
if portbindings.PROFILE not in port_data:
return
profile = port_data.get(portbindings.PROFILE)
# If binding:profile is None or an empty dict,
# it means binding:.profile needs to be cleared.
# TODO(amotoki): Allow Make None in binding:profile in
# the API layer. See LP bug #1220011.
profile_set = attrs.is_attr_set(profile) and profile
cur_portinfo = ndb.get_portinfo(context.session, port['id'])
if profile_set:
portinfo = self._validate_portinfo(profile)
portinfo_changed = 'ADD'
if cur_portinfo:
if (portinfo['datapath_id'] == cur_portinfo.datapath_id and
portinfo['port_no'] == cur_portinfo.port_no):
return
ndb.del_portinfo(context.session, port['id'])
portinfo_changed = 'MOD'
portinfo['mac'] = port['mac_address']
ndb.add_portinfo(context.session, port['id'], **portinfo)
elif cur_portinfo:
portinfo_changed = 'DEL'
portinfo = None
ndb.del_portinfo(context.session, port['id'])
self._extend_port_dict_binding_portinfo(port, portinfo)
return portinfo_changed
def extend_port_dict_binding(self, port_res, port_db):
super(NECPluginV2, self).extend_port_dict_binding(port_res, port_db)
self._extend_port_dict_binding_portinfo(port_res, port_db.portinfo)
def _process_portbindings_create(self, context, port_data, port):
super(NECPluginV2, self)._process_portbindings_create_and_update(
context, port_data, port)
self._process_portbindings_portinfo_create(context, port_data, port)
def _process_portbindings_update(self, context, port_data, port):
super(NECPluginV2, self)._process_portbindings_create_and_update(
context, port_data, port)
portinfo_changed = self._process_portbindings_portinfo_update(
context, port_data, port)
return portinfo_changed
def create_port(self, context, port): def create_port(self, context, port):
"""Create a new port entry on DB, then try to activate it.""" """Create a new port entry on DB, then try to activate it."""
LOG.debug(_("NECPluginV2.create_port() called, port=%s ."), port) LOG.debug(_("NECPluginV2.create_port() called, port=%s ."), port)
@ -353,15 +460,50 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self._ensure_default_security_group_on_port(context, port) self._ensure_default_security_group_on_port(context, port)
sgids = self._get_security_groups_on_port(context, port) sgids = self._get_security_groups_on_port(context, port)
port = super(NECPluginV2, self).create_port(context, port) port = super(NECPluginV2, self).create_port(context, port)
self._process_portbindings_create_and_update(context, self._process_portbindings_create(context, port_data, port)
port_data,
port)
self._process_port_create_security_group( self._process_port_create_security_group(
context, port, sgids) context, port, sgids)
self.notify_security_groups_member_updated(context, port) self.notify_security_groups_member_updated(context, port)
return self.activate_port_if_ready(context, port) return self.activate_port_if_ready(context, port)
def _update_ofc_port_if_required(self, context, old_port, new_port,
portinfo_changed):
def get_ofport_exist(port):
return (port['admin_state_up'] and
bool(port.get(portbindings.PROFILE)))
# Determine it is required to update OFC port
need_add = False
need_del = False
need_packet_filter_update = False
old_ofport_exist = get_ofport_exist(old_port)
new_ofport_exist = get_ofport_exist(new_port)
if old_port['admin_state_up'] != new_port['admin_state_up']:
if new_port['admin_state_up']:
need_add |= new_ofport_exist
else:
need_del |= old_ofport_exist
if portinfo_changed:
if portinfo_changed in ['DEL', 'MOD']:
need_del |= old_ofport_exist
if portinfo_changed in ['ADD', 'MOD']:
need_add |= new_ofport_exist
need_packet_filter_update |= True
# Update OFC port if required
if need_del:
self.deactivate_port(context, new_port)
if need_packet_filter_update:
self.deactivate_packet_filters_by_port(context, id)
if need_add:
if need_packet_filter_update:
self.activate_packet_filters_by_port(context, id)
self.activate_port_if_ready(context, new_port)
def update_port(self, context, id, port): def update_port(self, context, id, port):
"""Update port, and handle packetfilters associated with the port. """Update port, and handle packetfilters associated with the port.
@ -375,9 +517,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
old_port = super(NECPluginV2, self).get_port(context, id) old_port = super(NECPluginV2, self).get_port(context, id)
new_port = super(NECPluginV2, self).update_port(context, id, port) new_port = super(NECPluginV2, self).update_port(context, id, port)
self._process_portbindings_create_and_update(context, portinfo_changed = self._process_portbindings_update(
port['port'], context, port['port'], new_port)
new_port)
need_port_update_notify = self.update_security_group_on_port( need_port_update_notify = self.update_security_group_on_port(
context, id, port, old_port, new_port) context, id, port, old_port, new_port)
@ -386,13 +527,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
if need_port_update_notify: if need_port_update_notify:
self.notifier.port_update(context, new_port) self.notifier.port_update(context, new_port)
changed = (old_port['admin_state_up'] != new_port['admin_state_up']) self._update_ofc_port_if_required(context, old_port, new_port,
if changed: portinfo_changed)
if new_port['admin_state_up']:
new_port = self.activate_port_if_ready(context, new_port)
else:
new_port = self.deactivate_port(context, new_port)
return new_port return new_port
def delete_port(self, context, id, l3_port_check=True): def delete_port(self, context, id, l3_port_check=True):
@ -510,10 +646,10 @@ class NECPluginV2RPCCallbacks(object):
"port_added message (port_id=%s)."), id) "port_added message (port_id=%s)."), id)
continue continue
ndb.del_portinfo(session, id) ndb.del_portinfo(session, id)
ndb.add_portinfo(session, id, datapath_id, p['port_no'],
mac=p.get('mac', ''))
port = self._get_port(rpc_context, id) port = self._get_port(rpc_context, id)
if port: if port:
ndb.add_portinfo(session, id, datapath_id, p['port_no'],
mac=p.get('mac', ''))
# NOTE: Make sure that packet filters on this port exist while # NOTE: Make sure that packet filters on this port exist while
# the port is active to avoid unexpected packet transfer. # the port is active to avoid unexpected packet transfer.
if portinfo: if portinfo:

View File

@ -15,26 +15,24 @@
# under the License. # under the License.
# @author: Ryota MIBU # @author: Ryota MIBU
import contextlib
import random import random
from neutron.common import constants as q_const from neutron.common import constants as q_const
from neutron.db import api as db_api
from neutron.openstack.common import uuidutils from neutron.openstack.common import uuidutils
from neutron.plugins.nec.common import exceptions as nexc from neutron.plugins.nec.common import exceptions as nexc
from neutron.plugins.nec.db import api as ndb from neutron.plugins.nec.db import api as ndb
from neutron.plugins.nec.db import models as nmodels # noqa from neutron.plugins.nec.db import models as nmodels # noqa
from neutron.tests import base from neutron.tests.unit.nec import test_nec_plugin
class NECPluginV2DBTestBase(base.BaseTestCase): class NECPluginV2DBTestBase(test_nec_plugin.NecPluginV2TestCase):
"""Class conisting of NECPluginV2 DB unit tests.""" """Class conisting of NECPluginV2 DB unit tests."""
def setUp(self): def setUp(self):
"""Setup for tests.""" """Setup for tests."""
super(NECPluginV2DBTestBase, self).setUp() super(NECPluginV2DBTestBase, self).setUp()
ndb.initialize() self.session = self.context.session
self.session = db_api.get_session()
self.addCleanup(ndb.clear_db)
def get_ofc_item_random_params(self): def get_ofc_item_random_params(self):
"""create random parameters for ofc_item test.""" """create random parameters for ofc_item test."""
@ -43,15 +41,18 @@ class NECPluginV2DBTestBase(base.BaseTestCase):
none = uuidutils.generate_uuid() none = uuidutils.generate_uuid()
return ofc_id, neutron_id, none return ofc_id, neutron_id, none
def get_portinfo_random_params(self): @contextlib.contextmanager
"""create random parameters for portinfo test.""" def portinfo_random_params(self):
port_id = uuidutils.generate_uuid() with self.port() as port:
datapath_id = hex(random.randint(0, 0xffffffff)) params = {'port_id': port['port']['id'],
port_no = random.randint(1, 100) 'datapath_id': hex(random.randint(0, 0xffffffff)),
vlan_id = random.randint(q_const.MIN_VLAN_TAG, q_const.MAX_VLAN_TAG) 'port_no': random.randint(1, 100),
mac = ':'.join(["%02x" % random.randint(0, 0xff) for x in range(6)]) 'vlan_id': random.randint(q_const.MIN_VLAN_TAG,
none = uuidutils.generate_uuid() q_const.MAX_VLAN_TAG),
return port_id, datapath_id, port_no, vlan_id, mac, none 'mac': ':'.join(["%02x" % random.randint(0, 0xff)
for x in range(6)])
}
yield params
class NECPluginV2DBTest(NECPluginV2DBTestBase): class NECPluginV2DBTest(NECPluginV2DBTestBase):
@ -122,46 +123,51 @@ class NECPluginV2DBTest(NECPluginV2DBTestBase):
'ofc_tenant', o) 'ofc_tenant', o)
self.assertEqual(None, tenant_none) self.assertEqual(None, tenant_none)
def _compare_portinfo(self, portinfo, expected):
self.assertEqual(portinfo.id, expected['port_id'])
self.assertEqual(portinfo.datapath_id, expected['datapath_id'])
self.assertEqual(portinfo.port_no, expected['port_no'])
self.assertEqual(portinfo.vlan_id, expected['vlan_id'])
self.assertEqual(portinfo.mac, expected['mac'])
def _add_portinfo(self, session, params):
return ndb.add_portinfo(session, params['port_id'],
params['datapath_id'], params['port_no'],
params['vlan_id'], params['mac'])
def testd_add_portinfo(self): def testd_add_portinfo(self):
"""test add portinfo.""" """test add portinfo."""
i, d, p, v, m, n = self.get_portinfo_random_params() with self.portinfo_random_params() as params:
portinfo = ndb.add_portinfo(self.session, i, d, p, v, m) portinfo = self._add_portinfo(self.session, params)
self.assertEqual(portinfo.id, i) self._compare_portinfo(portinfo, params)
self.assertEqual(portinfo.datapath_id, d)
self.assertEqual(portinfo.port_no, p)
self.assertEqual(portinfo.vlan_id, v)
self.assertEqual(portinfo.mac, m)
exception_raised = False exception_raised = False
try: try:
ndb.add_portinfo(self.session, i, d, p, v, m) self._add_portinfo(self.session, params)
except nexc.NECDBException: except nexc.NECDBException:
exception_raised = True exception_raised = True
self.assertTrue(exception_raised) self.assertTrue(exception_raised)
def teste_get_portinfo(self): def teste_get_portinfo(self):
"""test get portinfo.""" """test get portinfo."""
i, d, p, v, m, n = self.get_portinfo_random_params() with self.portinfo_random_params() as params:
ndb.add_portinfo(self.session, i, d, p, v, m) self._add_portinfo(self.session, params)
portinfo = ndb.get_portinfo(self.session, i) portinfo = ndb.get_portinfo(self.session, params['port_id'])
self.assertEqual(portinfo.id, i) self._compare_portinfo(portinfo, params)
self.assertEqual(portinfo.datapath_id, d)
self.assertEqual(portinfo.port_no, p)
self.assertEqual(portinfo.vlan_id, v)
self.assertEqual(portinfo.mac, m)
portinfo_none = ndb.get_portinfo(self.session, n) nonexist_id = uuidutils.generate_uuid()
self.assertEqual(None, portinfo_none) portinfo_none = ndb.get_portinfo(self.session, nonexist_id)
self.assertEqual(None, portinfo_none)
def testf_del_portinfo(self): def testf_del_portinfo(self):
"""test delete portinfo.""" """test delete portinfo."""
i, d, p, v, m, n = self.get_portinfo_random_params() with self.portinfo_random_params() as params:
ndb.add_portinfo(self.session, i, d, p, v, m) self._add_portinfo(self.session, params)
portinfo = ndb.get_portinfo(self.session, i) portinfo = ndb.get_portinfo(self.session, params['port_id'])
self.assertEqual(portinfo.id, i) self.assertEqual(portinfo.id, params['port_id'])
ndb.del_portinfo(self.session, i) ndb.del_portinfo(self.session, params['port_id'])
portinfo_none = ndb.get_portinfo(self.session, i) portinfo_none = ndb.get_portinfo(self.session, params['port_id'])
self.assertEqual(None, portinfo_none) self.assertEqual(None, portinfo_none)
class NECPluginV2DBOldMappingTest(NECPluginV2DBTestBase): class NECPluginV2DBOldMappingTest(NECPluginV2DBTestBase):

View File

@ -24,15 +24,12 @@ from neutron.common.test_lib import test_config
from neutron.common import topics from neutron.common import topics
from neutron import context from neutron import context
from neutron.db import db_base_plugin_v2 from neutron.db import db_base_plugin_v2
from neutron.extensions import portbindings
from neutron import manager from neutron import manager
from neutron.plugins.nec.common import exceptions as nexc from neutron.plugins.nec.common import exceptions as nexc
from neutron.plugins.nec.db import api as ndb from neutron.plugins.nec.db import api as ndb
from neutron.plugins.nec import nec_plugin from neutron.plugins.nec import nec_plugin
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit.nec import fake_ofc_manager from neutron.tests.unit.nec import fake_ofc_manager
from neutron.tests.unit import test_db_plugin as test_plugin from neutron.tests.unit import test_db_plugin as test_plugin
from neutron.tests.unit import test_security_groups_rpc as test_sg_rpc
PLUGIN_NAME = 'neutron.plugins.nec.nec_plugin.NECPluginV2' PLUGIN_NAME = 'neutron.plugins.nec.nec_plugin.NECPluginV2'
@ -101,31 +98,13 @@ class TestNecV2HTTPResponse(test_plugin.TestV2HTTPResponse,
class TestNecPortsV2(test_plugin.TestPortsV2, NecPluginV2TestCase): class TestNecPortsV2(test_plugin.TestPortsV2, NecPluginV2TestCase):
pass
VIF_TYPE = portbindings.VIF_TYPE_OVS
HAS_PORT_FILTER = True
class TestNecNetworksV2(test_plugin.TestNetworksV2, NecPluginV2TestCase): class TestNecNetworksV2(test_plugin.TestNetworksV2, NecPluginV2TestCase):
pass pass
class TestNecPortBinding(test_bindings.PortBindingsTestCase,
NecPluginV2TestCase):
VIF_TYPE = portbindings.VIF_TYPE_OVS
HAS_PORT_FILTER = True
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_HYBRID_DRIVER
def setUp(self):
test_sg_rpc.set_firewall_driver(self.FIREWALL_DRIVER)
super(TestNecPortBinding, self).setUp()
class TestNecPortBindingNoSG(TestNecPortBinding):
HAS_PORT_FILTER = False
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_NOOP_DRIVER
class TestNecPortsV2Callback(NecPluginV2TestCase): class TestNecPortsV2Callback(NecPluginV2TestCase):
def _get_portinfo(self, port_id): def _get_portinfo(self, port_id):
@ -177,10 +156,10 @@ class TestNecPortsV2Callback(NecPluginV2TestCase):
self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) self.assertEqual(self.ofc.delete_ofc_port.call_count, 1)
self.assertIsNone(self._get_portinfo(port_id)) self.assertIsNone(self._get_portinfo(port_id))
# The port is expected to delete when exiting with-clause. # The port and portinfo is expected to delete when exiting with-clause.
self.assertEqual(self.ofc.delete_ofc_port.call_count, 1)
self.assertIsNone(self._get_portinfo(port_id))
if not portinfo_delete_first: if not portinfo_delete_first:
self.assertEqual(self.ofc.delete_ofc_port.call_count, 1)
self.assertIsNotNone(self._get_portinfo(port_id))
self.rpcapi_update_ports(removed=[port_id]) self.rpcapi_update_ports(removed=[port_id])
# Ensure port deletion is called once. # Ensure port deletion is called once.
@ -198,8 +177,8 @@ class TestNecPortsV2Callback(NecPluginV2TestCase):
def test_portinfo_added_unknown_port(self): def test_portinfo_added_unknown_port(self):
portinfo = {'id': 'dummy-p1', 'port_no': 123} portinfo = {'id': 'dummy-p1', 'port_no': 123}
self.rpcapi_update_ports(added=[portinfo]) self.rpcapi_update_ports(added=[portinfo])
self.assertIsNotNone(ndb.get_portinfo(self.context.session, self.assertIsNone(ndb.get_portinfo(self.context.session,
'dummy-p1')) 'dummy-p1'))
self.assertEqual(self.ofc.exists_ofc_port.call_count, 0) self.assertEqual(self.ofc.exists_ofc_port.call_count, 0)
self.assertEqual(self.ofc.create_ofc_port.call_count, 0) self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
@ -234,8 +213,7 @@ class TestNecPortsV2Callback(NecPluginV2TestCase):
# No OFC operations are expected. # No OFC operations are expected.
self.assertEqual(self.ofc.create_ofc_port.call_count, 1) self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) self.assertEqual(self.ofc.delete_ofc_port.call_count, 1)
self.assertEqual(ndb.get_portinfo(self.context.session, self.assertIsNone(ndb.get_portinfo(self.context.session, port_id))
port_id).port_no, 456)
def test_portinfo_change(self): def test_portinfo_change(self):
self._test_portinfo_change() self._test_portinfo_change()

View File

@ -15,6 +15,8 @@
# under the License. # under the License.
# @author: Ryota MIBU # @author: Ryota MIBU
import mock
from neutron import context from neutron import context
from neutron.openstack.common import uuidutils from neutron.openstack.common import uuidutils
from neutron.plugins.nec.common import config from neutron.plugins.nec.common import config
@ -24,6 +26,19 @@ from neutron.plugins.nec import ofc_manager
from neutron.tests import base from neutron.tests import base
class FakePortInfo(object):
def __init__(self, id, datapath_id, port_no=0,
vlan_id=65535, mac='00:11:22:33:44:55'):
self.data = {'id': id, 'datapath_id': datapath_id,
'port_no': port_no, 'vlan_id': vlan_id, 'mac': mac}
def __getattr__(self, name):
if name in self.fields:
return self[name]
else:
raise AttributeError(name)
class OFCManagerTestBase(base.BaseTestCase): class OFCManagerTestBase(base.BaseTestCase):
"""Class conisting of OFCManager unit tests.""" """Class conisting of OFCManager unit tests."""
@ -35,6 +50,7 @@ class OFCManagerTestBase(base.BaseTestCase):
self.addCleanup(ndb.clear_db) self.addCleanup(ndb.clear_db)
self.ofc = ofc_manager.OFCManager() self.ofc = ofc_manager.OFCManager()
self.ctx = context.get_admin_context() self.ctx = context.get_admin_context()
self.addCleanup(mock.patch.stopall)
def get_random_params(self): def get_random_params(self):
"""create random parameters for portinfo test.""" """create random parameters for portinfo test."""
@ -98,44 +114,51 @@ class OFCManagerTest(OFCManagerTestBase):
self.ofc.delete_ofc_network(self.ctx, n, {'tenant_id': t}) self.ofc.delete_ofc_network(self.ctx, n, {'tenant_id': t})
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_network', n)) self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_network', n))
def _mock_get_portinfo(self, port_id, datapath_id='0xabc', port_no=1):
get_portinfo = mock.patch.object(ndb, 'get_portinfo').start()
fake_portinfo = FakePortInfo(id=port_id, datapath_id=datapath_id,
port_no=port_no)
get_portinfo.return_value = fake_portinfo
return get_portinfo
def testg_create_ofc_port(self): def testg_create_ofc_port(self):
"""test create ofc_port.""" """test create ofc_port."""
t, n, p, f, none = self.get_random_params() t, n, p, f, none = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t) self.ofc.create_ofc_tenant(self.ctx, t)
self.ofc.create_ofc_network(self.ctx, t, n) self.ofc.create_ofc_network(self.ctx, t, n)
ndb.add_portinfo(self.ctx.session, p, "0xabc", 1, 65535,
"00:11:22:33:44:55")
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p)) self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
get_portinfo = self._mock_get_portinfo(p)
port = {'tenant_id': t, 'network_id': n} port = {'tenant_id': t, 'network_id': n}
self.ofc.create_ofc_port(self.ctx, p, port) self.ofc.create_ofc_port(self.ctx, p, port)
self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p)) self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
port = ndb.get_ofc_item(self.ctx.session, 'ofc_port', p) port = ndb.get_ofc_item(self.ctx.session, 'ofc_port', p)
self.assertEqual(port.ofc_id, "ofc-" + p[:-4]) self.assertEqual(port.ofc_id, "ofc-" + p[:-4])
get_portinfo.assert_called_once_with(mock.ANY, p)
def testh_exists_ofc_port(self): def testh_exists_ofc_port(self):
"""test exists_ofc_port.""" """test exists_ofc_port."""
t, n, p, f, none = self.get_random_params() t, n, p, f, none = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t) self.ofc.create_ofc_tenant(self.ctx, t)
self.ofc.create_ofc_network(self.ctx, t, n) self.ofc.create_ofc_network(self.ctx, t, n)
ndb.add_portinfo(self.ctx.session, p, "0xabc", 2, 65535,
"00:12:22:33:44:55")
self.assertFalse(self.ofc.exists_ofc_port(self.ctx, p)) self.assertFalse(self.ofc.exists_ofc_port(self.ctx, p))
get_portinfo = self._mock_get_portinfo(p)
port = {'tenant_id': t, 'network_id': n} port = {'tenant_id': t, 'network_id': n}
self.ofc.create_ofc_port(self.ctx, p, port) self.ofc.create_ofc_port(self.ctx, p, port)
self.assertTrue(self.ofc.exists_ofc_port(self.ctx, p)) self.assertTrue(self.ofc.exists_ofc_port(self.ctx, p))
get_portinfo.assert_called_once_with(mock.ANY, p)
def testi_delete_ofc_port(self): def testi_delete_ofc_port(self):
"""test delete ofc_port.""" """test delete ofc_port."""
t, n, p, f, none = self.get_random_params() t, n, p, f, none = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t) self.ofc.create_ofc_tenant(self.ctx, t)
self.ofc.create_ofc_network(self.ctx, t, n) self.ofc.create_ofc_network(self.ctx, t, n)
ndb.add_portinfo(self.ctx.session, p, "0xabc", 3, 65535, get_portinfo = self._mock_get_portinfo(p)
"00:13:22:33:44:55")
port = {'tenant_id': t, 'network_id': n} port = {'tenant_id': t, 'network_id': n}
self.ofc.create_ofc_port(self.ctx, p, port) self.ofc.create_ofc_port(self.ctx, p, port)
self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p)) self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
self.ofc.delete_ofc_port(self.ctx, p, port) self.ofc.delete_ofc_port(self.ctx, p, port)
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p)) self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
get_portinfo.assert_called_once_with(mock.ANY, p)
def testj_create_ofc_packet_filter(self): def testj_create_ofc_packet_filter(self):
"""test create ofc_filter.""" """test create ofc_filter."""

View File

@ -0,0 +1,280 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 NEC Corporation
# All rights reserved.
#
# 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.
#
# @author: Akihiro Motoki, NEC Corporation
from testtools import matchers
from webob import exc
from neutron.common import exceptions as q_exc
from neutron import context
from neutron.extensions import portbindings
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit.nec import test_nec_plugin
from neutron.tests.unit import test_security_groups_rpc as test_sg_rpc
class TestNecPortBinding(test_bindings.PortBindingsTestCase,
test_nec_plugin.NecPluginV2TestCase):
VIF_TYPE = portbindings.VIF_TYPE_OVS
HAS_PORT_FILTER = True
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_HYBRID_DRIVER
def setUp(self):
test_sg_rpc.set_firewall_driver(self.FIREWALL_DRIVER)
super(TestNecPortBinding, self).setUp()
class TestNecPortBindingNoSG(TestNecPortBinding):
HAS_PORT_FILTER = False
FIREWALL_DRIVER = test_sg_rpc.FIREWALL_NOOP_DRIVER
class TestNecPortBindingHost(
test_bindings.PortBindingsHostTestCaseMixin,
test_nec_plugin.NecPluginV2TestCase):
pass
class TestNecPortBindingPortInfo(test_nec_plugin.NecPluginV2TestCase):
def _get_portinfo(self, datapath_id=None, port_no=None, prefix=None):
if datapath_id is None:
datapath_id = '0xabc'
if port_no is None:
port_no = 1
if prefix is None:
prefix = 'portinfo:'
return {prefix + 'datapath_id': datapath_id,
prefix + 'port_no': port_no}
def _check_response_portbinding_profile(self, port, datapath_id=None,
port_no=None):
expected = self._get_portinfo(datapath_id, port_no, prefix='')
profile = port[portbindings.PROFILE]
self.assertEqual(len(profile), 2)
self.assertEqual(profile['portinfo:datapath_id'],
expected['datapath_id'])
self.assertEqual(profile['portinfo:port_no'],
expected['port_no'])
def _check_response_portbinding_no_profile(self, port):
self.assertIn('status', port)
self.assertNotIn(portbindings.PROFILE, port)
def _get_non_admin_context(self):
return context.Context(user_id=None,
tenant_id=self._tenant_id,
is_admin=False,
read_deleted="no")
def test_port_create_portinfo(self):
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
with self.port(arg_list=(portbindings.PROFILE,),
**profile_arg) as port:
port_id = port['port']['id']
# Check a response of create_port
self._check_response_portbinding_profile(port['port'])
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
# Check a response of get_port
ctx = context.get_admin_context()
port = self._show('ports', port_id, neutron_context=ctx)['port']
self._check_response_portbinding_profile(port)
# By default user is admin - now test non admin user
ctx = self._get_non_admin_context()
non_admin_port = self._show(
'ports', port_id, neutron_context=ctx)['port']
self._check_response_portbinding_no_profile(non_admin_port)
# port-update with non admin user should fail
self._update('ports', port_id,
{'port': profile_arg},
expected_code=404,
neutron_context=ctx)
def test_port_update_portinfo(self):
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
with self.port() as port:
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
port_id = port['port']['id']
# Check a response of create_port
self._check_response_portbinding_no_profile(port['port'])
# Check a response of update_port
ctx = context.get_admin_context()
port = self._update('ports', port_id, {'port': profile_arg},
neutron_context=ctx)['port']
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
self._check_response_portbinding_profile(port)
port = self._show('ports', port_id, neutron_context=ctx)['port']
self._check_response_portbinding_profile(port)
def test_port_update_portinfo_detail(self):
with self.port() as port:
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
port_id = port['port']['id']
ctx = context.get_admin_context()
# add portinfo
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
port = self._update('ports', port_id, {'port': profile_arg},
neutron_context=ctx)['port']
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
# portinfo unchanged
port = self._update('ports', port_id, {'port': profile_arg},
neutron_context=ctx)['port']
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
# modify portinfo
profile_arg = {portbindings.PROFILE:
self._get_portinfo(datapath_id='0x1234567890',
port_no=99)}
port = self._update('ports', port_id, {'port': profile_arg},
neutron_context=ctx)['port']
self.assertEqual(self.ofc.create_ofc_port.call_count, 2)
self.assertEqual(self.ofc.delete_ofc_port.call_count, 1)
# delete portinfo
profile_arg = {portbindings.PROFILE: {}}
port = self._update('ports', port_id, {'port': profile_arg},
neutron_context=ctx)['port']
self.assertEqual(self.ofc.create_ofc_port.call_count, 2)
self.assertEqual(self.ofc.delete_ofc_port.call_count, 2)
def test_port_create_portinfo_with_empty_dict(self):
profile_arg = {portbindings.PROFILE: {}}
with self.port(arg_list=(portbindings.PROFILE,),
**profile_arg) as port:
port_id = port['port']['id']
# Check a response of create_port
self._check_response_portbinding_no_profile(port['port'])
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
# add portinfo
ctx = context.get_admin_context()
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
port = self._update('ports', port_id, {'port': profile_arg},
neutron_context=ctx)['port']
self._check_response_portbinding_profile(port)
self.assertEqual(self.ofc.create_ofc_port.call_count, 1)
self.assertEqual(self.ofc.delete_ofc_port.call_count, 0)
def test_port_create_portinfo_non_admin(self):
with self.network(set_context=True, tenant_id='test') as net1:
with self.subnet(network=net1) as subnet1:
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
try:
with self.port(subnet=subnet1,
expected_res_status=403,
arg_list=(portbindings.PROFILE,),
set_context=True, tenant_id='test',
**profile_arg):
pass
except exc.HTTPClientError:
pass
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
def test_port_update_portinfo_non_admin(self):
profile_arg = {portbindings.PROFILE: self._get_portinfo()}
with self.network() as net1:
with self.subnet(network=net1) as subnet1:
with self.port(subnet=subnet1) as port:
# By default user is admin - now test non admin user
# Note that 404 is returned when prohibit by policy.
# See comment for PolicyNotAuthorized except clause
# in update() in neutron.api.v2.base.Controller.
port_id = port['port']['id']
ctx = self._get_non_admin_context()
port = self._update('ports', port_id,
{'port': profile_arg},
expected_code=404,
neutron_context=ctx)
self.assertEqual(self.ofc.create_ofc_port.call_count, 0)
def test_port_create_portinfo_validation_called(self):
# Check validate_portinfo is called.
profile_arg = {portbindings.PROFILE:
{'portinfo:datapath_id': '0xabc',
'portinfo:port_no': 0xffff + 1}}
try:
with self.port(arg_list=(portbindings.PROFILE,),
expected_res_status=400,
**profile_arg):
pass
except exc.HTTPClientError:
pass
class TestNecPortBindingValidatePortInfo(test_nec_plugin.NecPluginV2TestCase):
def test_validate_portinfo_ok(self):
profile = {'portinfo:datapath_id': '0x1234567890abcdef',
'portinfo:port_no': 123}
portinfo = self.plugin._validate_portinfo(profile)
self.assertEqual(portinfo['datapath_id'], '0x1234567890abcdef')
self.assertEqual(portinfo['port_no'], 123)
def test_validate_portinfo_ok_without_0x(self):
profile = {'portinfo:datapath_id': '1234567890abcdef',
'portinfo:port_no': 123}
portinfo = self.plugin._validate_portinfo(profile)
self.assertEqual(portinfo['datapath_id'], '0x1234567890abcdef')
self.assertEqual(portinfo['port_no'], 123)
def _test_validate_exception(self, profile, expected_msg):
e = self.assertRaises(q_exc.InvalidInput,
self.plugin._validate_portinfo, profile)
self.assertThat(str(e), matchers.StartsWith(expected_msg))
def test_validate_portinfo_dict_validation(self):
expected_msg = ("Invalid input for operation: "
"Validation of dictionary's keys failed.")
profile = {'portinfo:port_no': 123}
self._test_validate_exception(profile, expected_msg)
profile = {'portinfo:datapath_id': '0xabcdef'}
self._test_validate_exception(profile, expected_msg)
def test_validate_portinfo_negative_port_number(self):
profile = {'portinfo:datapath_id': '0x1234567890abcdef',
'portinfo:port_no': -1}
expected_msg = ("Invalid input for operation: "
"'-1' should be non-negative.")
self._test_validate_exception(profile, expected_msg)
def test_validate_portinfo_invalid_datapath_id(self):
expected_msg = ("Invalid input for operation: "
"portinfo:datapath_id should be a hex string")
# non hexidecimal datapath_id
profile = {'portinfo:datapath_id': 'INVALID',
'portinfo:port_no': 123}
self._test_validate_exception(profile, expected_msg)
# Too big datapath_id
profile = {'portinfo:datapath_id': '0x10000000000000000',
'portinfo:port_no': 123}
self._test_validate_exception(profile, expected_msg)
def test_validate_portinfo_too_big_port_number(self):
profile = {'portinfo:datapath_id': '0x1234567890abcdef',
'portinfo:port_no': 65536}
expected_msg = ("Invalid input for operation: "
"portinfo:port_no should be [0:65535]")
self._test_validate_exception(profile, expected_msg)