Add RPCs to support volume connector operation

This patch adds the following two RPCs in order to support volume
connector operations.

- update_volume_connector()
  This function is called to update the information about a volume
  connector that is stored in the database.

- destroy_volume_connector()
  This function is called to remove a volume connector from the database.

Co-Authored-By: Tomoki Sekiyama <tomoki.sekiyama.qu@hitachi.com>
Co-Authored-By: Stephanie Miller <stephane@alum.mit.edu>
Co-Authored-By: Ruby Loo <ruby.loo@intel.com>
Change-Id: I3debe98ea78e159a81f53d0a3a3a49fe0c8663f6
Partial-Bug: 1526231
This commit is contained in:
Satoru Moriya 2016-02-09 21:54:42 +09:00 committed by Ruby Loo
parent bc7daf9779
commit 20fe26335e
5 changed files with 245 additions and 2 deletions

View File

@ -82,7 +82,7 @@ class ConductorManager(base_manager.BaseConductorManager):
"""Ironic Conductor manager main class."""
# NOTE(rloo): This must be in sync with rpcapi.ConductorAPI's.
RPC_API_VERSION = '1.34'
RPC_API_VERSION = '1.35'
target = messaging.Target(version=RPC_API_VERSION)
@ -1537,6 +1537,33 @@ class ConductorManager(base_manager.BaseConductorManager):
'%(node)s'),
{'portgroup': portgroup.uuid, 'node': task.node.uuid})
@METRICS.timer('ConductorManager.destroy_volume_connector')
@messaging.expected_exceptions(exception.NodeLocked,
exception.NodeNotFound,
exception.VolumeConnectorNotFound)
def destroy_volume_connector(self, context, connector):
"""Delete a volume connector.
:param context: request context
:param connector: volume connector object
:raises: NodeLocked if node is locked by another conductor
:raises: NodeNotFound if the node associated with the connector does
not exist
:raises: VolumeConnectorNotFound if the volume connector cannot be
found
"""
LOG.debug('RPC destroy_volume_connector called for volume connector '
'%(connector)s',
{'connector': connector.uuid})
with task_manager.acquire(context, connector.node_id,
purpose='volume connector deletion') as task:
connector.destroy()
LOG.info(_LI('Successfully deleted volume connector '
'%(connector)s. '
'The node associated with the connector was '
'%(node)s'),
{'connector': connector.uuid, 'node': task.node.uuid})
@METRICS.timer('ConductorManager.get_console_information')
@messaging.expected_exceptions(exception.NodeLocked,
exception.UnsupportedDriverExtension,
@ -1833,6 +1860,42 @@ class ConductorManager(base_manager.BaseConductorManager):
return portgroup_obj
@METRICS.timer('ConductorManager.update_volume_connector')
@messaging.expected_exceptions(
exception.InvalidParameterValue,
exception.NodeLocked,
exception.NodeNotFound,
exception.VolumeConnectorNotFound,
exception.VolumeConnectorTypeAndIdAlreadyExists)
def update_volume_connector(self, context, connector):
"""Update a volume connector.
:param context: request context
:param connector: a changed (but not saved) volume connector object
:returns: an updated volume connector object
:raises: InvalidParameterValue if the volume connector's UUID is being
changed
:raises: NodeLocked if the node is already locked
:raises: NodeNotFound if the node associated with the conductor does
not exist
:raises: VolumeConnectorNotFound if the volume connector cannot be
found
:raises: VolumeConnectorTypeAndIdAlreadyExists if another connector
already exists with the same values for type and connector_id
fields
"""
LOG.debug("RPC update_volume_connector called for connector "
"%(connector)s.",
{'connector': connector.uuid})
with task_manager.acquire(context, connector.node_id,
purpose='volume connector update'):
connector.save()
LOG.info(_LI("Successfully updated volume connector "
"%(connector)s."),
{'connector': connector.uuid})
return connector
@METRICS.timer('ConductorManager.get_driver_properties')
@messaging.expected_exceptions(exception.DriverNotFound)
def get_driver_properties(self, context, driver_name):

View File

@ -81,11 +81,12 @@ class ConductorAPI(object):
| 1.32 - Add do_node_clean
| 1.33 - Added update and destroy portgroup.
| 1.34 - Added heartbeat
| 1.35 - Added destroy_volume_connector and update_volume_connector
"""
# NOTE(rloo): This must be in sync with manager.ConductorManager's.
RPC_API_VERSION = '1.34'
RPC_API_VERSION = '1.35'
def __init__(self, topic=None):
super(ConductorAPI, self).__init__()
@ -735,3 +736,49 @@ class ConductorAPI(object):
cctxt = self.client.prepare(topic=self.topic, version='1.31')
return cctxt.call(context, 'object_backport_versions', objinst=objinst,
object_versions=object_versions)
def destroy_volume_connector(self, context, connector, topic=None):
"""Delete a volume connector.
Delete the volume connector. The conductor will lock the related node
during this operation.
:param context: request context
:param connector: volume connector object
:param topic: RPC topic. Defaults to self.topic.
:raises: NodeLocked if node is locked by another conductor
:raises: NodeNotFound if the node associated with the connector does
not exist
:raises: VolumeConnectorNotFound if the volume connector cannot be
found
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.35')
return cctxt.call(context, 'destroy_volume_connector',
connector=connector)
def update_volume_connector(self, context, connector, topic=None):
"""Update the volume connector's information.
Update the volume connector's information in the database and return
a volume connector object. The conductor will lock the related node
during this operation.
:param context: request context
:param connector: a changed (but not saved) volume connector object
:param topic: RPC topic. Defaults to self.topic.
:raises: InvalidParameterValue if the volume connector's UUID is being
changed
:raises: NodeLocked if node is locked by another conductor
:raises: NodeNotFound if the node associated with the connector does
not exist
:raises: VolumeConnectorNotFound if the volume connector cannot be
found
:raises: VolumeConnectorTypeAndIdAlreadyExists if another connector
already exists with the same values for type and connector_id
fields
:returns: updated volume connector object, including all fields.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.35')
return cctxt.call(context, 'update_volume_connector',
connector=connector)

View File

@ -5471,3 +5471,95 @@ class DoNodeAdoptionTestCase(
self.service.heartbeat(self.context, node.uuid, 'http://callback')
mock_spawn.assert_called_with(self.driver.deploy.heartbeat,
mock.ANY, 'http://callback')
@mgr_utils.mock_record_keepalive
class DestroyVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
tests_db_base.DbTestCase):
def test_destroy_volume_connector(self):
node = obj_utils.create_test_node(self.context, driver='fake')
volume_connector = obj_utils.create_test_volume_connector(
self.context, node_id=node.id)
self.service.destroy_volume_connector(self.context, volume_connector)
self.assertRaises(exception.VolumeConnectorNotFound,
volume_connector.refresh)
self.assertRaises(exception.VolumeConnectorNotFound,
self.dbapi.get_volume_connector_by_uuid,
volume_connector.uuid)
def test_destroy_volume_connector_node_locked(self):
node = obj_utils.create_test_node(self.context, driver='fake',
reservation='fake-reserv')
volume_connector = obj_utils.create_test_volume_connector(
self.context, node_id=node.id)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.destroy_volume_connector,
self.context, volume_connector)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeLocked, exc.exc_info[0])
@mgr_utils.mock_record_keepalive
class UpdateVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
tests_db_base.DbTestCase):
def test_update_volume_connector(self):
node = obj_utils.create_test_node(self.context, driver='fake')
volume_connector = obj_utils.create_test_volume_connector(
self.context, node_id=node.id, extra={'foo': 'bar'})
new_extra = {'foo': 'baz'}
volume_connector.extra = new_extra
res = self.service.update_volume_connector(self.context,
volume_connector)
self.assertEqual(new_extra, res.extra)
def test_update_volume_connector_node_locked(self):
node = obj_utils.create_test_node(self.context, driver='fake',
reservation='fake-reserv')
volume_connector = obj_utils.create_test_volume_connector(
self.context, node_id=node.id)
volume_connector.extra = {'foo': 'baz'}
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_volume_connector,
self.context, volume_connector)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NodeLocked, exc.exc_info[0])
def test_update_volume_connector_type(self):
node = obj_utils.create_test_node(self.context, driver='fake')
volume_connector = obj_utils.create_test_volume_connector(
self.context, node_id=node.id, extra={'vol_id': 'fake-id'})
new_type = 'wwnn'
volume_connector.type = new_type
res = self.service.update_volume_connector(self.context,
volume_connector)
self.assertEqual(new_type, res.type)
def test_update_volume_connector_uuid(self):
node = obj_utils.create_test_node(self.context, driver='fake')
volume_connector = obj_utils.create_test_volume_connector(
self.context, node_id=node.id)
volume_connector.uuid = uuidutils.generate_uuid()
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_volume_connector,
self.context, volume_connector)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
def test_update_volume_connector_duplicate(self):
node = obj_utils.create_test_node(self.context, driver='fake')
volume_connector1 = obj_utils.create_test_volume_connector(
self.context, node_id=node.id)
volume_connector2 = obj_utils.create_test_volume_connector(
self.context, node_id=node.id, uuid=uuidutils.generate_uuid(),
type='diff_type')
volume_connector2.type = volume_connector1.type
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.update_volume_connector,
self.context, volume_connector2)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.VolumeConnectorTypeAndIdAlreadyExists,
exc.exc_info[0])

View File

@ -400,3 +400,17 @@ class RPCAPITestCase(base.DbTestCase):
node_id='fake-node',
callback_url='http://ramdisk.url:port',
version='1.34')
def test_destroy_volume_connector(self):
fake_volume_connector = dbutils.get_test_volume_connector()
self._test_rpcapi('destroy_volume_connector',
'call',
version='1.35',
connector=fake_volume_connector)
def test_update_volume_connector(self):
fake_volume_connector = dbutils.get_test_volume_connector()
self._test_rpcapi('update_volume_connector',
'call',
version='1.35',
connector=fake_volume_connector)

View File

@ -161,3 +161,30 @@ def create_test_portgroup(ctxt, **kw):
portgroup = get_test_portgroup(ctxt, **kw)
portgroup.create()
return portgroup
def get_test_volume_connector(ctxt, **kw):
"""Return a VolumeConnector 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_volume_connector = db_utils.get_test_volume_connector(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_volume_connector['id']
volume_connector = objects.VolumeConnector(ctxt)
for key in db_volume_connector:
setattr(volume_connector, key, db_volume_connector[key])
return volume_connector
def create_test_volume_connector(ctxt, **kw):
"""Create and return a test volume connector object.
Create a volume connector in the DB and return a VolumeConnector object
with appropriate attributes.
"""
volume_connector = get_test_volume_connector(ctxt, **kw)
volume_connector.create()
return volume_connector