Browse Source

Add RPCs to support volume target operations

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

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

- destroy_volume_target()
  This function is called to remove a volume target from the database.

Co-Authored-By: Stephane Miller <stephane@alum.mit.edu>
Co-Authored-By: Ruby Loo <ruby.loo@intel.com>
Change-Id: I2925c081da4bb0a77edd5c579f92aaa0f5999b78
Partial-Bug: 1526231
tags/7.0.0
Satoru Moriya 3 years ago
parent
commit
aab505a698

+ 57
- 1
ironic/conductor/manager.py View File

@@ -82,7 +82,7 @@ class ConductorManager(base_manager.BaseConductorManager):
82 82
     """Ironic Conductor manager main class."""
83 83
 
84 84
     # NOTE(rloo): This must be in sync with rpcapi.ConductorAPI's.
85
-    RPC_API_VERSION = '1.36'
85
+    RPC_API_VERSION = '1.37'
86 86
 
87 87
     target = messaging.Target(version=RPC_API_VERSION)
88 88
 
@@ -1576,6 +1576,30 @@ class ConductorManager(base_manager.BaseConductorManager):
1576 1576
                          '%(node)s'),
1577 1577
                      {'connector': connector.uuid, 'node': task.node.uuid})
1578 1578
 
1579
+    @METRICS.timer('ConductorManager.destroy_volume_target')
1580
+    @messaging.expected_exceptions(exception.NodeLocked,
1581
+                                   exception.NodeNotFound,
1582
+                                   exception.VolumeTargetNotFound)
1583
+    def destroy_volume_target(self, context, target):
1584
+        """Delete a volume target.
1585
+
1586
+        :param context: request context
1587
+        :param target: volume target object
1588
+        :raises: NodeLocked if node is locked by another conductor
1589
+        :raises: NodeNotFound if the node associated with the target does
1590
+                 not exist
1591
+        :raises: VolumeTargetNotFound if the volume target cannot be found
1592
+        """
1593
+        LOG.debug('RPC destroy_volume_target called for volume target '
1594
+                  '%(target)s',
1595
+                  {'target': target.uuid})
1596
+        with task_manager.acquire(context, target.node_id,
1597
+                                  purpose='volume target deletion') as task:
1598
+            target.destroy()
1599
+            LOG.info(_LI('Successfully deleted volume target %(target)s. '
1600
+                         'The node associated with the target was %(node)s'),
1601
+                     {'target': target.uuid, 'node': task.node.uuid})
1602
+
1579 1603
     @METRICS.timer('ConductorManager.get_console_information')
1580 1604
     @messaging.expected_exceptions(exception.NodeLocked,
1581 1605
                                    exception.UnsupportedDriverExtension,
@@ -1948,6 +1972,38 @@ class ConductorManager(base_manager.BaseConductorManager):
1948 1972
                      {'connector': connector.uuid})
1949 1973
             return connector
1950 1974
 
1975
+    @METRICS.timer('ConductorManager.update_volume_target')
1976
+    @messaging.expected_exceptions(
1977
+        exception.InvalidParameterValue,
1978
+        exception.NodeLocked,
1979
+        exception.NodeNotFound,
1980
+        exception.VolumeTargetNotFound,
1981
+        exception.VolumeTargetBootIndexAlreadyExists)
1982
+    def update_volume_target(self, context, target):
1983
+        """Update a volume target.
1984
+
1985
+        :param context: request context
1986
+        :param target: a changed (but not saved) volume target object
1987
+        :returns: an updated volume target object
1988
+        :raises: InvalidParameterValue if the volume target's UUID is being
1989
+                 changed
1990
+        :raises: NodeLocked if the node is already locked
1991
+        :raises: NodeNotFound if the node associated with the volume target
1992
+                 does not exist
1993
+        :raises: VolumeTargetNotFound if the volume target cannot be found
1994
+        :raises: VolumeTargetBootIndexAlreadyExists if a volume target already
1995
+                 exists with the same node ID and boot index values
1996
+        """
1997
+        LOG.debug("RPC update_volume_target called for target %(target)s.",
1998
+                  {'target': target.uuid})
1999
+
2000
+        with task_manager.acquire(context, target.node_id,
2001
+                                  purpose='volume target update'):
2002
+            target.save()
2003
+            LOG.info(_LI("Successfully updated volume target %(target)s."),
2004
+                     {'target': target.uuid})
2005
+            return target
2006
+
1951 2007
     @METRICS.timer('ConductorManager.get_driver_properties')
1952 2008
     @messaging.expected_exceptions(exception.DriverNotFound)
1953 2009
     def get_driver_properties(self, context, driver_name):

+ 42
- 1
ironic/conductor/rpcapi.py View File

@@ -83,11 +83,12 @@ class ConductorAPI(object):
83 83
     |    1.34 - Added heartbeat
84 84
     |    1.35 - Added destroy_volume_connector and update_volume_connector
85 85
     |    1.36 - Added create_node
86
+    |    1.37 - Added destroy_volume_target and update_volume_target
86 87
 
87 88
     """
88 89
 
89 90
     # NOTE(rloo): This must be in sync with manager.ConductorManager's.
90
-    RPC_API_VERSION = '1.36'
91
+    RPC_API_VERSION = '1.37'
91 92
 
92 93
     def __init__(self, topic=None):
93 94
         super(ConductorAPI, self).__init__()
@@ -798,3 +799,43 @@ class ConductorAPI(object):
798 799
         cctxt = self.client.prepare(topic=topic or self.topic, version='1.35')
799 800
         return cctxt.call(context, 'update_volume_connector',
800 801
                           connector=connector)
802
+
803
+    def destroy_volume_target(self, context, target, topic=None):
804
+        """Delete a volume target.
805
+
806
+        :param context: request context
807
+        :param target: volume target object
808
+        :param topic: RPC topic. Defaults to self.topic.
809
+        :raises: NodeLocked if node is locked by another conductor
810
+        :raises: NodeNotFound if the node associated with the target does
811
+                 not exist
812
+        :raises: VolumeTargetNotFound if the volume target cannot be found
813
+        """
814
+        cctxt = self.client.prepare(topic=topic or self.topic, version='1.37')
815
+        return cctxt.call(context, 'destroy_volume_target',
816
+                          target=target)
817
+
818
+    def update_volume_target(self, context, target, topic=None):
819
+        """Update the volume target's information.
820
+
821
+        Update the volume target's information in the database and return a
822
+        volume target object. The conductor will lock the related node during
823
+        this operation.
824
+
825
+        :param context: request context
826
+        :param target: a changed (but not saved) volume target object
827
+        :param topic: RPC topic. Defaults to self.topic.
828
+        :raises: InvalidParameterValue if the volume target's UUID is being
829
+                 changed
830
+        :raises: NodeLocked if the node is already locked
831
+        :raises: NodeNotFound if the node associated with the volume target
832
+                 does not exist
833
+        :raises: VolumeTargetNotFound if the volume target cannot be found
834
+        :raises: VolumeTargetBootIndexAlreadyExists if a volume target already
835
+                 exists with the same node ID and boot index values
836
+        :returns: updated volume target object, including all fields
837
+
838
+        """
839
+        cctxt = self.client.prepare(topic=topic or self.topic, version='1.37')
840
+        return cctxt.call(context, 'update_volume_target',
841
+                          target=target)

+ 134
- 0
ironic/tests/unit/conductor/test_manager.py View File

@@ -5811,3 +5811,137 @@ class UpdateVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
5811 5811
         # Compare true exception hidden by @messaging.expected_exceptions
5812 5812
         self.assertEqual(exception.VolumeConnectorTypeAndIdAlreadyExists,
5813 5813
                          exc.exc_info[0])
5814
+
5815
+
5816
+@mgr_utils.mock_record_keepalive
5817
+class DestroyVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
5818
+                                  tests_db_base.DbTestCase):
5819
+    def test_destroy_volume_target(self):
5820
+        node = obj_utils.create_test_node(self.context, driver='fake')
5821
+
5822
+        volume_target = obj_utils.create_test_volume_target(self.context,
5823
+                                                            node_id=node.id)
5824
+        self.service.destroy_volume_target(self.context, volume_target)
5825
+        self.assertRaises(exception.VolumeTargetNotFound,
5826
+                          volume_target.refresh)
5827
+        self.assertRaises(exception.VolumeTargetNotFound,
5828
+                          self.dbapi.get_volume_target_by_uuid,
5829
+                          volume_target.uuid)
5830
+
5831
+    def test_destroy_volume_target_node_locked(self):
5832
+        node = obj_utils.create_test_node(self.context, driver='fake',
5833
+                                          reservation='fake-reserv')
5834
+
5835
+        volume_target = obj_utils.create_test_volume_target(self.context,
5836
+                                                            node_id=node.id)
5837
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
5838
+                                self.service.destroy_volume_target,
5839
+                                self.context, volume_target)
5840
+        # Compare true exception hidden by @messaging.expected_exceptions
5841
+        self.assertEqual(exception.NodeLocked, exc.exc_info[0])
5842
+
5843
+    def test_destroy_volume_target_node_gone(self):
5844
+        node = obj_utils.create_test_node(self.context, driver='fake')
5845
+        volume_target = obj_utils.create_test_volume_target(self.context,
5846
+                                                            node_id=node.id)
5847
+        self.service.destroy_node(self.context, node.id)
5848
+
5849
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
5850
+                                self.service.destroy_volume_target,
5851
+                                self.context, volume_target)
5852
+        # Compare true exception hidden by @messaging.expected_exceptions
5853
+        self.assertEqual(exception.NodeNotFound, exc.exc_info[0])
5854
+
5855
+    def test_destroy_volume_target_already_destroyed(self):
5856
+        node = obj_utils.create_test_node(self.context, driver='fake')
5857
+        volume_target = obj_utils.create_test_volume_target(self.context,
5858
+                                                            node_id=node.id)
5859
+        self.service.destroy_volume_target(self.context, volume_target)
5860
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
5861
+                                self.service.destroy_volume_target,
5862
+                                self.context, volume_target)
5863
+        # Compare true exception hidden by @messaging.expected_exceptions
5864
+        self.assertEqual(exception.VolumeTargetNotFound, exc.exc_info[0])
5865
+
5866
+
5867
+@mgr_utils.mock_record_keepalive
5868
+class UpdateVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
5869
+                                 tests_db_base.DbTestCase):
5870
+    def test_update_volume_target(self):
5871
+        node = obj_utils.create_test_node(self.context, driver='fake')
5872
+
5873
+        volume_target = obj_utils.create_test_volume_target(
5874
+            self.context, node_id=node.id, extra={'foo': 'bar'})
5875
+        new_extra = {'foo': 'baz'}
5876
+        volume_target.extra = new_extra
5877
+        res = self.service.update_volume_target(self.context, volume_target)
5878
+        self.assertEqual(new_extra, res.extra)
5879
+
5880
+    def test_update_volume_target_node_locked(self):
5881
+        node = obj_utils.create_test_node(self.context, driver='fake',
5882
+                                          reservation='fake-reserv')
5883
+        volume_target = obj_utils.create_test_volume_target(self.context,
5884
+                                                            node_id=node.id)
5885
+        volume_target.extra = {'foo': 'baz'}
5886
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
5887
+                                self.service.update_volume_target,
5888
+                                self.context, volume_target)
5889
+        # Compare true exception hidden by @messaging.expected_exceptions
5890
+        self.assertEqual(exception.NodeLocked, exc.exc_info[0])
5891
+
5892
+    def test_update_volume_target_volume_type(self):
5893
+        node = obj_utils.create_test_node(self.context, driver='fake')
5894
+        volume_target = obj_utils.create_test_volume_target(
5895
+            self.context, node_id=node.id, extra={'vol_id': 'fake-id'})
5896
+        new_volume_type = 'fibre_channel'
5897
+        volume_target.volume_type = new_volume_type
5898
+        res = self.service.update_volume_target(self.context,
5899
+                                                volume_target)
5900
+        self.assertEqual(new_volume_type, res.volume_type)
5901
+
5902
+    def test_update_volume_target_uuid(self):
5903
+        node = obj_utils.create_test_node(self.context, driver='fake')
5904
+        volume_target = obj_utils.create_test_volume_target(
5905
+            self.context, node_id=node.id)
5906
+        volume_target.uuid = uuidutils.generate_uuid()
5907
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
5908
+                                self.service.update_volume_target,
5909
+                                self.context, volume_target)
5910
+        # Compare true exception hidden by @messaging.expected_exceptions
5911
+        self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
5912
+
5913
+    def test_update_volume_target_duplicate(self):
5914
+        node = obj_utils.create_test_node(self.context, driver='fake')
5915
+        volume_target1 = obj_utils.create_test_volume_target(
5916
+            self.context, node_id=node.id)
5917
+        volume_target2 = obj_utils.create_test_volume_target(
5918
+            self.context, node_id=node.id, uuid=uuidutils.generate_uuid(),
5919
+            boot_index=volume_target1.boot_index + 1)
5920
+        volume_target2.boot_index = volume_target1.boot_index
5921
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
5922
+                                self.service.update_volume_target,
5923
+                                self.context, volume_target2)
5924
+        # Compare true exception hidden by @messaging.expected_exceptions
5925
+        self.assertEqual(exception.VolumeTargetBootIndexAlreadyExists,
5926
+                         exc.exc_info[0])
5927
+
5928
+    def _test_update_volume_target_exception(self, expected_exc):
5929
+        node = obj_utils.create_test_node(self.context, driver='fake')
5930
+        volume_target = obj_utils.create_test_volume_target(
5931
+            self.context, node_id=node.id, extra={'vol_id': 'fake-id'})
5932
+        new_volume_type = 'fibre_channel'
5933
+        volume_target.volume_type = new_volume_type
5934
+        with mock.patch.object(objects.VolumeTarget, 'save') as mock_save:
5935
+            mock_save.side_effect = expected_exc('Boo')
5936
+            exc = self.assertRaises(messaging.rpc.ExpectedException,
5937
+                                    self.service.update_volume_target,
5938
+                                    self.context, volume_target)
5939
+            # Compare true exception hidden by @messaging.expected_exceptions
5940
+            self.assertEqual(expected_exc, exc.exc_info[0])
5941
+
5942
+    def test_update_volume_target_node_not_found(self):
5943
+            self._test_update_volume_target_exception(exception.NodeNotFound)
5944
+
5945
+    def test_update_volume_target_not_found(self):
5946
+            self._test_update_volume_target_exception(
5947
+                exception.VolumeTargetNotFound)

+ 14
- 0
ironic/tests/unit/conductor/test_rpcapi.py View File

@@ -414,3 +414,17 @@ class RPCAPITestCase(base.DbTestCase):
414 414
                           'call',
415 415
                           version='1.35',
416 416
                           connector=fake_volume_connector)
417
+
418
+    def test_destroy_volume_target(self):
419
+        fake_volume_target = dbutils.get_test_volume_target()
420
+        self._test_rpcapi('destroy_volume_target',
421
+                          'call',
422
+                          version='1.37',
423
+                          target=fake_volume_target)
424
+
425
+    def test_update_volume_target(self):
426
+        fake_volume_target = dbutils.get_test_volume_target()
427
+        self._test_rpcapi('update_volume_target',
428
+                          'call',
429
+                          version='1.37',
430
+                          target=fake_volume_target)

+ 27
- 0
ironic/tests/unit/objects/utils.py View File

@@ -188,3 +188,30 @@ def create_test_volume_connector(ctxt, **kw):
188 188
     volume_connector = get_test_volume_connector(ctxt, **kw)
189 189
     volume_connector.create()
190 190
     return volume_connector
191
+
192
+
193
+def get_test_volume_target(ctxt, **kw):
194
+    """Return a VolumeTarget object with appropriate attributes.
195
+
196
+    NOTE: The object leaves the attributes marked as changed, such
197
+    that a create() could be used to commit it to the DB.
198
+    """
199
+    db_volume_target = db_utils.get_test_volume_target(**kw)
200
+    # Let DB generate ID if it isn't specified explicitly
201
+    if 'id' not in kw:
202
+        del db_volume_target['id']
203
+    volume_target = objects.VolumeTarget(ctxt)
204
+    for key in db_volume_target:
205
+        setattr(volume_target, key, db_volume_target[key])
206
+    return volume_target
207
+
208
+
209
+def create_test_volume_target(ctxt, **kw):
210
+    """Create and return a test volume target object.
211
+
212
+    Create a volume target in the DB and return a VolumeTarget object with
213
+    appropriate attributes.
214
+    """
215
+    volume_target = get_test_volume_target(ctxt, **kw)
216
+    volume_target.create()
217
+    return volume_target

Loading…
Cancel
Save