Merge "Add portgroups to support LAG interfaces - objs"

This commit is contained in:
Jenkins
2016-02-06 00:56:17 +00:00
committed by Gerrit Code Review
6 changed files with 485 additions and 2 deletions
+1
View File
@@ -28,3 +28,4 @@ def register_all():
__import__('ironic.objects.conductor')
__import__('ironic.objects.node')
__import__('ironic.objects.port')
__import__('ironic.objects.portgroup')
+32 -1
View File
@@ -32,7 +32,9 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
# Version 1.2: Add create() and destroy()
# Version 1.3: Add list()
# Version 1.4: Add list_by_node_id()
VERSION = '1.4'
# Version 1.5: Add list_by_portgroup_id() and new fields
# local_link_connection, portgroup_id and pxe_enabled
VERSION = '1.5'
dbapi = dbapi.get_instance()
@@ -42,6 +44,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
'node_id': object_fields.IntegerField(nullable=True),
'address': object_fields.MACAddressField(nullable=True),
'extra': object_fields.FlexibleDictField(nullable=True),
'local_link_connection': object_fields.FlexibleDictField(
nullable=True),
'portgroup_id': object_fields.IntegerField(nullable=True),
'pxe_enabled': object_fields.BooleanField()
}
@staticmethod
@@ -184,6 +190,31 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
sort_dir=sort_dir)
return Port._from_db_object_list(db_ports, cls, context)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def list_by_portgroup_id(cls, context, portgroup_id, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Return a list of Port objects associated with a given portgroup ID.
:param context: Security context.
:param portgroup_id: the ID of the portgroup.
:param limit: maximum number of resources to return in a single result.
:param marker: pagination marker for large data sets.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:returns: a list of :class:`Port` object.
"""
db_ports = cls.dbapi.get_ports_by_portgroup_id(portgroup_id,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Port._from_db_object_list(db_ports, cls, context)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
+288
View File
@@ -0,0 +1,288 @@
# coding=utf-8
#
#
# 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 oslo_utils import strutils
from oslo_utils import uuidutils
from oslo_versionedobjects import base as object_base
from ironic.common import exception
from ironic.common import utils
from ironic.db import api as dbapi
from ironic.objects import base
from ironic.objects import fields as object_fields
@base.IronicObjectRegistry.register
class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': object_fields.IntegerField(),
'uuid': object_fields.UUIDField(nullable=True),
'name': object_fields.StringField(nullable=True),
'node_id': object_fields.IntegerField(nullable=True),
'address': object_fields.MACAddressField(nullable=True),
'extra': object_fields.FlexibleDictField(nullable=True),
}
@staticmethod
def _from_db_object(portgroup, db_portgroup):
"""Converts a database entity to a formal object."""
for field in portgroup.fields:
portgroup[field] = db_portgroup[field]
portgroup.obj_reset_changes()
return portgroup
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [Portgroup._from_db_object(cls(context), obj) for obj in
db_objects]
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def get(cls, context, portgroup_ident):
"""Find a portgroup based on its id, uuid, name or address.
:param portgroup_ident: The id, uuid, name or address of a portgroup.
:param context: Security context
:returns: A :class:`Portgroup` object.
:raises: InvalidIdentity
"""
if strutils.is_int_like(portgroup_ident):
return cls.get_by_id(context, portgroup_ident)
elif uuidutils.is_uuid_like(portgroup_ident):
return cls.get_by_uuid(context, portgroup_ident)
elif utils.is_valid_mac(portgroup_ident):
return cls.get_by_address(context, portgroup_ident)
elif utils.is_valid_logical_name(portgroup_ident):
return cls.get_by_name(context, portgroup_ident)
else:
raise exception.InvalidIdentity(identity=portgroup_ident)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def get_by_id(cls, context, portgroup_id):
"""Find a portgroup based on its integer id and return a Portgroup object.
:param portgroup id: The id of a portgroup.
:param context: Security context
:returns: A :class:`Portgroup` object.
:raises: PortgroupNotFound
"""
db_portgroup = cls.dbapi.get_portgroup_by_id(portgroup_id)
portgroup = Portgroup._from_db_object(cls(context), db_portgroup)
return portgroup
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def get_by_uuid(cls, context, uuid):
"""Find a portgroup based on uuid and return a :class:`Portgroup` object.
:param uuid: The uuid of a portgroup.
:param context: Security context
:returns: A :class:`Portgroup` object.
:raises: PortgroupNotFound
"""
db_portgroup = cls.dbapi.get_portgroup_by_uuid(uuid)
portgroup = Portgroup._from_db_object(cls(context), db_portgroup)
return portgroup
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def get_by_address(cls, context, address):
"""Find a portgroup based on address and return a :class:`Portgroup` object.
:param address: The MAC address of a portgroup.
:param context: Security context
:returns: A :class:`Portgroup` object.
:raises: PortgroupNotFound
"""
db_portgroup = cls.dbapi.get_portgroup_by_address(address)
portgroup = Portgroup._from_db_object(cls(context), db_portgroup)
return portgroup
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def get_by_name(cls, context, name):
"""Find a portgroup based on name and return a :class:`Portgroup` object.
:param name: The name of a portgroup.
:param context: Security context
:returns: A :class:`Portgroup` object.
:raises: PortgroupNotFound
"""
db_portgroup = cls.dbapi.get_portgroup_by_name(name)
portgroup = Portgroup._from_db_object(cls(context), db_portgroup)
return portgroup
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def list(cls, context, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of Portgroup objects.
:param context: Security context.
:param limit: Maximum number of resources to return in a single result.
:param marker: Pagination marker for large data sets.
:param sort_key: Column to sort results by.
:param sort_dir: Direction to sort. "asc" or "desc".
:returns: A list of :class:`Portgroup` object.
:raises: InvalidParameterValue
"""
db_portgroups = cls.dbapi.get_portgroup_list(limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Portgroup._from_db_object_list(db_portgroups, cls, context)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable_classmethod
@classmethod
def list_by_node_id(cls, context, node_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of Portgroup objects associated with a given node ID.
:param context: Security context.
:param node_id: The ID of the node.
:param limit: Maximum number of resources to return in a single result.
:param marker: Pagination marker for large data sets.
:param sort_key: Column to sort results by.
:param sort_dir: Direction to sort. "asc" or "desc".
:returns: A list of :class:`Portgroup` object.
:raises: InvalidParameterValue
"""
db_portgroups = cls.dbapi.get_portgroups_by_node_id(node_id,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Portgroup._from_db_object_list(db_portgroups, cls, context)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable
def create(self, context=None):
"""Create a Portgroup record in the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Portgroup(context)
:raises: DuplicateName, MACAlreadyExists, PortgroupAlreadyExists
"""
values = self.obj_get_changes()
db_portgroup = self.dbapi.create_portgroup(values)
self._from_db_object(self, db_portgroup)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable
def destroy(self, context=None):
"""Delete the Portgroup from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Portgroup(context)
:raises: PortgroupNotEmpty, PortgroupNotFound
"""
self.dbapi.destroy_portgroup(self.uuid)
self.obj_reset_changes()
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable
def save(self, context=None):
"""Save updates to this Portgroup.
Updates will be made column by column based on the result
of self.what_changed().
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Portgroup(context)
:raises: PortgroupNotFound, DuplicateName, MACAlreadyExists
"""
updates = self.obj_get_changes()
updated_portgroup = self.dbapi.update_portgroup(self.uuid, updates)
self._from_db_object(self, updated_portgroup)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
# methods can be used in the future to replace current explicit RPC calls.
# Implications of calling new remote procedures should be thought through.
# @object_base.remotable
def refresh(self, context=None):
"""Loads updates for this Portgroup.
Loads a portgroup with the same uuid from the database and
checks for updated attributes. Updates are applied from
the loaded portgroup column by column, if there are any updates.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Portgroup(context)
:raises: PortgroupNotFound
"""
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
self.obj_refresh(current)
+2 -1
View File
@@ -407,7 +407,8 @@ expected_object_fingerprints = {
'Node': '1.14-9ee8ab283b06398545880dfdedb49891',
'MyObj': '1.5-4f5efe8f0fcaf182bbe1c7fe3ba858db',
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
'Port': '1.4-f5aa3ff81d1459d6d7e6d9d9dceed351',
'Port': '1.5-a224755c3da5bc5cf1a14a11c0d00f3f',
'Portgroup': '1.0-1ac4db8fa31edd9e1637248ada4c25a1',
'Conductor': '1.0-5091f249719d4a465062a1b3dc7f860d'
}
+135
View File
@@ -0,0 +1,135 @@
# 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.
import datetime
import mock
from testtools.matchers import HasLength
from ironic.common import exception
from ironic import objects
from ironic.tests.unit.db import base
from ironic.tests.unit.db import utils
class TestPortgroupObject(base.DbTestCase):
def setUp(self):
super(TestPortgroupObject, self).setUp()
self.fake_portgroup = utils.get_test_portgroup()
def test_get_by_id(self):
portgroup_id = self.fake_portgroup['id']
with mock.patch.object(self.dbapi, 'get_portgroup_by_id',
autospec=True) as mock_get_portgroup:
mock_get_portgroup.return_value = self.fake_portgroup
portgroup = objects.Portgroup.get(self.context, portgroup_id)
mock_get_portgroup.assert_called_once_with(portgroup_id)
self.assertEqual(self.context, portgroup._context)
def test_get_by_uuid(self):
uuid = self.fake_portgroup['uuid']
with mock.patch.object(self.dbapi, 'get_portgroup_by_uuid',
autospec=True) as mock_get_portgroup:
mock_get_portgroup.return_value = self.fake_portgroup
portgroup = objects.Portgroup.get(self.context, uuid)
mock_get_portgroup.assert_called_once_with(uuid)
self.assertEqual(self.context, portgroup._context)
def test_get_by_address(self):
address = self.fake_portgroup['address']
with mock.patch.object(self.dbapi, 'get_portgroup_by_address',
autospec=True) as mock_get_portgroup:
mock_get_portgroup.return_value = self.fake_portgroup
portgroup = objects.Portgroup.get(self.context, address)
mock_get_portgroup.assert_called_once_with(address)
self.assertEqual(self.context, portgroup._context)
def test_get_by_name(self):
name = self.fake_portgroup['name']
with mock.patch.object(self.dbapi, 'get_portgroup_by_name',
autospec=True) as mock_get_portgroup:
mock_get_portgroup.return_value = self.fake_portgroup
portgroup = objects.Portgroup.get(self.context, name)
mock_get_portgroup.assert_called_once_with(name)
self.assertEqual(self.context, portgroup._context)
def test_get_bad_id_and_uuid_and_address_and_name(self):
self.assertRaises(exception.InvalidIdentity,
objects.Portgroup.get,
self.context,
'not:a_name_or_uuid')
def test_save(self):
uuid = self.fake_portgroup['uuid']
address = "b2:54:00:cf:2d:40"
test_time = datetime.datetime(2000, 1, 1, 0, 0)
with mock.patch.object(self.dbapi, 'get_portgroup_by_uuid',
autospec=True) as mock_get_portgroup:
mock_get_portgroup.return_value = self.fake_portgroup
with mock.patch.object(self.dbapi, 'update_portgroup',
autospec=True) as mock_update_portgroup:
mock_update_portgroup.return_value = (
utils.get_test_portgroup(address=address,
updated_at=test_time))
p = objects.Portgroup.get_by_uuid(self.context, uuid)
p.address = address
p.save()
mock_get_portgroup.assert_called_once_with(uuid)
mock_update_portgroup.assert_called_once_with(
uuid, {'address': "b2:54:00:cf:2d:40"})
self.assertEqual(self.context, p._context)
res_updated_at = (p.updated_at).replace(tzinfo=None)
self.assertEqual(test_time, res_updated_at)
def test_refresh(self):
uuid = self.fake_portgroup['uuid']
returns = [self.fake_portgroup,
utils.get_test_portgroup(address="c3:54:00:cf:2d:40")]
expected = [mock.call(uuid), mock.call(uuid)]
with mock.patch.object(self.dbapi, 'get_portgroup_by_uuid',
side_effect=returns,
autospec=True) as mock_get_portgroup:
p = objects.Portgroup.get_by_uuid(self.context, uuid)
self.assertEqual("52:54:00:cf:2d:31", p.address)
p.refresh()
self.assertEqual("c3:54:00:cf:2d:40", p.address)
self.assertEqual(expected, mock_get_portgroup.call_args_list)
self.assertEqual(self.context, p._context)
def test_list(self):
with mock.patch.object(self.dbapi, 'get_portgroup_list',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_portgroup]
portgroups = objects.Portgroup.list(self.context)
self.assertThat(portgroups, HasLength(1))
self.assertIsInstance(portgroups[0], objects.Portgroup)
self.assertEqual(self.context, portgroups[0]._context)
def test_list_by_node_id(self):
with mock.patch.object(self.dbapi, 'get_portgroups_by_node_id',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_portgroup]
node_id = self.fake_portgroup['node_id']
portgroups = objects.Portgroup.list_by_node_id(self.context,
node_id)
self.assertThat(portgroups, HasLength(1))
self.assertIsInstance(portgroups[0], objects.Portgroup)
self.assertEqual(self.context, portgroups[0]._context)
+27
View File
@@ -97,3 +97,30 @@ def create_test_chassis(ctxt, **kw):
chassis = get_test_chassis(ctxt, **kw)
chassis.create()
return chassis
def get_test_portgroup(ctxt, **kw):
"""Return a Portgroup object with appropriate attributes.
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_portgroup = db_utils.get_test_portgroup(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_portgroup['id']
portgroup = objects.Portgroup(ctxt)
for key in db_portgroup:
setattr(portgroup, key, db_portgroup[key])
return portgroup
def create_test_portgroup(ctxt, **kw):
"""Create and return a test portgroup object.
Create a portgroup in the DB and return a Portgroup object with appropriate
attributes.
"""
portgroup = get_test_portgroup(ctxt, **kw)
portgroup.create()
return portgroup