Create Port object.

1. Added the model of a Port.
2. Defined __all__ for ironic.objects to simplify
   accessing the models.
3. Made @objectify universal for all objects.

Partially implements blueprint: ironic-object-model

Change-Id: Iec88310811d0e17903b5182d08e293dd4d0967d0
This commit is contained in:
Roman Prykhodchenko
2013-06-07 17:59:14 +03:00
parent 1c1b4aa370
commit 461eba1c81
8 changed files with 249 additions and 38 deletions

View File

@@ -26,7 +26,7 @@ from ironic.common import exception
from ironic.common import utils
from ironic.db import api
from ironic.db.sqlalchemy import models
from ironic.objects import node
from ironic import objects
from ironic.openstack.common.db.sqlalchemy import session as db_session
from ironic.openstack.common import log
from ironic.openstack.common import uuidutils
@@ -84,19 +84,19 @@ class Connection(api.Connection):
def __init__(self):
pass
@node.objectify
@objects.objectify(objects.Node)
def get_nodes(self, columns):
pass
@node.objectify
@objects.objectify(objects.Node)
def get_associated_nodes(self):
pass
@node.objectify
@objects.objectify(objects.Node)
def get_unassociated_nodes(self):
pass
@node.objectify
@objects.objectify(objects.Node)
def reserve_nodes(self, tag, nodes):
# Ensure consistent sort order so we don't run into deadlocks.
nodes.sort()
@@ -150,14 +150,14 @@ class Connection(api.Connection):
if ref['reservation'] is not None:
raise exception.NodeLocked(node=node)
@node.objectify
@objects.objectify(objects.Node)
def create_node(self, values):
node = models.Node()
node.update(values)
node.save()
return node
@node.objectify
@objects.objectify(objects.Node)
def get_node(self, node):
query = model_query(models.Node)
query = add_uuid_filter(query, node)
@@ -169,7 +169,7 @@ class Connection(api.Connection):
return result
@node.objectify
@objects.objectify(objects.Node)
def get_node_by_instance(self, instance):
query = model_query(models.Node)
if uuidutils.is_uuid_like(instance):
@@ -194,7 +194,7 @@ class Connection(api.Connection):
if count != 1:
raise exception.NodeNotFound(node=node)
@node.objectify
@objects.objectify(objects.Node)
def update_node(self, node, values):
session = get_session()
with session.begin():
@@ -209,6 +209,7 @@ class Connection(api.Connection):
ref = query.one()
return ref
@objects.objectify(objects.Port)
def get_port(self, port):
query = model_query(models.Port)
query = add_port_filter(query, port)
@@ -220,9 +221,11 @@ class Connection(api.Connection):
return result
@objects.objectify(objects.Port)
def get_port_by_vif(self, vif):
pass
@objects.objectify(objects.Port)
def get_ports_by_node(self, node):
session = get_session()
@@ -238,12 +241,14 @@ class Connection(api.Connection):
return result
@objects.objectify(objects.Port)
def create_port(self, values):
port = models.Port()
port.update(values)
port.save()
return port
@objects.objectify(objects.Port)
def update_port(self, port, values):
session = get_session()
with session.begin():

View File

@@ -11,3 +11,28 @@
# 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 ironic.objects import node
from ironic.objects import port
def objectify(klass):
"""Decorator to convert database results into specified objects."""
def the_decorator(fn):
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
try:
return klass._from_db_object(klass(), result)
except TypeError:
# TODO(deva): handle lists of objects better
# once support for those lands and is imported.
return [klass._from_db_object(klass(), obj) for obj in result]
return wrapper
return the_decorator
Node = node.Node
Port = port.Port
__all__ = (Node,
Port,
objectify)

View File

@@ -19,19 +19,6 @@ from ironic.objects import base
from ironic.objects import utils
def objectify(fn):
"""Decorator to convert database results into Node objects."""
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
try:
return Node._from_db_object(Node(), result)
except TypeError:
# TODO(deva): handle lists of objects better
# once support for those lands and is imported.
return [Node._from_db_object(Node(), obj) for obj in result]
return wrapper
class Node(base.IronicObject):
dbapi = db_api.get_instance()

85
ironic/objects/port.py Normal file
View File

@@ -0,0 +1,85 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 ironic.db import api as dbapi
from ironic.objects import base
from ironic.objects import utils
class Port(base.IronicObject):
dbapi = dbapi.get_instance()
fields = {
'id': int,
'uuid': utils.str_or_none,
'node_id': utils.int_or_none,
'address': utils.str_or_none,
'extra': utils.str_or_none,
}
@staticmethod
def _from_db_object(port, db_port):
"""Converts a database entity to a formal object."""
for field in port.fields:
port[field] = db_port[field]
port.obj_reset_changes()
return port
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid=None):
"""Find a port based on uuid and return a Port object.
:param uuid: the uuid of a port.
:returns: a :class:`Port` object.
"""
db_port = cls.dbapi.get_port(uuid)
return Port._from_db_object(cls(), db_port)
@base.remotable
def save(self, context):
"""Save updates to this Port.
Updates will be made column by column based on the result
of self.what_changed(). If expected_task_state is provided,
it will be checked against the in-database copy of the port
before updates are made.
:param context: Security context
"""
updates = {}
changes = self.obj_what_changed()
for field in changes:
updates[field] = self[field]
self.dbapi.update_port(self.uuid, updates)
self.obj_reset_changes()
@base.remotable
def refresh(self, context):
"""Loads updates for this Port.
Loads a port with the same uuid from the database and
checks for updated attributes. Updates are applied from
the loaded port column by column, if there are any updates.
:param context: Security context
"""
current = self.__class__.get_by_uuid(context, uuid=self.uuid)
for field in self.fields:
if (hasattr(self, base.get_attrname(field)) and
self[field] != current[field]):
self[field] = current[field]

View File

@@ -16,7 +16,6 @@
# under the License.
"""Ironic test utilities."""
from ironic.db.sqlalchemy import models
from ironic.openstack.common import jsonutils as json
@@ -88,10 +87,14 @@ def get_test_node(**kw):
def get_test_port(**kw):
port = models.Port()
port.id = kw.get('id', 987)
port.uuid = kw.get('uuid', '4fc26c0b-03f2-4d2e-ae87-c02d7f33c234')
port.node_id = kw.get('node_id', 123)
port.address = kw.get('address', '52:54:00:cf:2d:31')
port = {
'id': kw.get('id', 987),
'uuid': kw.get('uuid', '1be26c0b-03f2-4d2e-ae87-c02d7f33c781'),
'node_id': kw.get('node_id', 123),
'address': kw.get('address', '52:54:00:cf:2d:31'),
'extra': kw.get('extra', '{}'),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at'),
}
return port

View File

@@ -378,8 +378,8 @@ class SSHDriverTestCase(db_base.DbTestCase):
id=7,
address='dd:ee:ff',
uuid='4fc26c0b-03f2-4d2e-ae87-c02d7f33c234')]
self.dbapi.create_port(ports[0])
self.dbapi.create_port(ports[1])
ports[0] = self.dbapi.create_port(ports[0])
ports[1] = self.dbapi.create_port(ports[1])
self.mox.StubOutWithMock(self.dbapi, 'get_ports_by_node')
self.dbapi.get_ports_by_node(self.node.get('id')).\

View File

@@ -17,7 +17,7 @@
from ironic.common import context
from ironic.db import api as db_api
from ironic.db.sqlalchemy import models
from ironic.objects import node
from ironic import objects
from ironic.tests.db import base
from ironic.tests.db import utils
@@ -37,7 +37,7 @@ class TestNodeObject(base.DbTestCase):
self.dbapi.get_node(uuid).AndReturn(self.fake_node)
self.mox.ReplayAll()
node.Node.get_by_uuid(ctxt, uuid)
objects.Node.get_by_uuid(ctxt, uuid)
self.mox.VerifyAll()
# TODO(deva): add tests for load-on-demand info, eg. ports,
# once Port objects are created
@@ -52,7 +52,7 @@ class TestNodeObject(base.DbTestCase):
self.dbapi.update_node(uuid, {'properties': "new property"})
self.mox.ReplayAll()
n = node.Node.get_by_uuid(ctxt, uuid)
n = objects.Node.get_by_uuid(ctxt, uuid)
n.properties = "new property"
n.save()
self.mox.VerifyAll()
@@ -68,7 +68,7 @@ class TestNodeObject(base.DbTestCase):
dict(self.fake_node, properties="second"))
self.mox.ReplayAll()
n = node.Node.get_by_uuid(ctxt, uuid)
n = objects.Node.get_by_uuid(ctxt, uuid)
self.assertEqual(n.properties, "first")
n.refresh()
self.assertEqual(n.properties, "second")
@@ -80,12 +80,12 @@ class TestNodeObject(base.DbTestCase):
n.update(self.fake_node)
return n
@node.objectify
@objects.objectify(objects.Node)
def _convert_db_node():
return _get_db_node()
self.assertIsInstance(_get_db_node(), models.Node)
self.assertIsInstance(_convert_db_node(), node.Node)
self.assertIsInstance(_convert_db_node(), objects.Node)
def test_objectify_many(self):
def _get_db_nodes():
@@ -96,11 +96,11 @@ class TestNodeObject(base.DbTestCase):
nodes.append(n)
return nodes
@node.objectify
@objects.objectify(objects.Node)
def _convert_db_nodes():
return _get_db_nodes()
for n in _get_db_nodes():
self.assertIsInstance(n, models.Node)
for n in _convert_db_nodes():
self.assertIsInstance(n, node.Node)
self.assertIsInstance(n, objects.Node)

View File

@@ -0,0 +1,106 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 ironic.common import context
from ironic.db import api as db_api
from ironic.db.sqlalchemy import models
from ironic import objects
from ironic.tests.db import base
from ironic.tests.db import utils
class TestPortObject(base.DbTestCase):
def setUp(self):
super(TestPortObject, self).setUp()
self.fake_port = utils.get_test_port()
self.dbapi = db_api.get_instance()
def test_load(self):
ctxt = context.get_admin_context()
uuid = self.fake_port['uuid']
self.mox.StubOutWithMock(self.dbapi, 'get_port')
self.dbapi.get_port(uuid).AndReturn(self.fake_port)
self.mox.ReplayAll()
objects.Port.get_by_uuid(ctxt, uuid)
self.mox.VerifyAll()
def test_save(self):
ctxt = context.get_admin_context()
uuid = self.fake_port['uuid']
self.mox.StubOutWithMock(self.dbapi, 'get_port')
self.mox.StubOutWithMock(self.dbapi, 'update_port')
self.dbapi.get_port(uuid).AndReturn(self.fake_port)
self.dbapi.update_port(uuid, {'address': "b2:54:00:cf:2d:40"})
self.mox.ReplayAll()
p = objects.Port.get_by_uuid(ctxt, uuid)
p.address = "b2:54:00:cf:2d:40"
p.save()
self.mox.VerifyAll()
def test_refresh(self):
ctxt = context.get_admin_context()
uuid = self.fake_port['uuid']
self.mox.StubOutWithMock(self.dbapi, 'get_port')
self.dbapi.get_port(uuid).AndReturn(self.fake_port)
self.dbapi.get_port(uuid).AndReturn(
utils.get_test_port(address="c3:54:00:cf:2d:40"))
self.mox.ReplayAll()
p = objects.Port.get_by_uuid(ctxt, uuid)
self.assertEqual(p.address, "52:54:00:cf:2d:31")
p.refresh()
self.assertEqual(p.address, "c3:54:00:cf:2d:40")
self.mox.VerifyAll()
def test_objectify(self):
def _get_db_port():
p = models.Port()
p.update(self.fake_port)
return p
@objects.objectify(objects.Port)
def _convert_db_port():
return _get_db_port()
self.assertIsInstance(_get_db_port(), models.Port)
self.assertIsInstance(_convert_db_port(), objects.Port)
def test_objectify_many(self):
def _get_db_ports():
nodes = []
for i in xrange(5):
n = models.Port()
n.update(self.fake_port)
nodes.append(n)
return nodes
@objects.objectify(objects.Port)
def _convert_db_nodes():
return _get_db_ports()
for p in _get_db_ports():
self.assertIsInstance(p, models.Port)
for p in _convert_db_nodes():
self.assertIsInstance(p, objects.Port)