Browse Source

Add node power state validation to volume resource update/deletion

This patch validate the power state of a node when the following
actions regarding volume resources associated with the node are
requested.
  - update a volume connector
  - delete a volume connector
  - update a volume target
  - delete a volume target

These actions should allowed only when the node is tuned off as
designed in the SPEC.

Change-Id: I5d0465c6ac2d2c6ddac03385e6ed0ccb37556306
Partial-Bug: 1526231
tags/9.0.0
Hironori Shiina 2 years ago
parent
commit
ddcd97714c
2 changed files with 115 additions and 18 deletions
  1. 42
    6
      ironic/conductor/manager.py
  2. 73
    12
      ironic/tests/unit/conductor/test_manager.py

+ 42
- 6
ironic/conductor/manager.py View File

@@ -1682,7 +1682,8 @@ class ConductorManager(base_manager.BaseConductorManager):
1682 1682
     @METRICS.timer('ConductorManager.destroy_volume_connector')
1683 1683
     @messaging.expected_exceptions(exception.NodeLocked,
1684 1684
                                    exception.NodeNotFound,
1685
-                                   exception.VolumeConnectorNotFound)
1685
+                                   exception.VolumeConnectorNotFound,
1686
+                                   exception.InvalidStateRequested)
1686 1687
     def destroy_volume_connector(self, context, connector):
1687 1688
         """Delete a volume connector.
1688 1689
 
@@ -1693,12 +1694,20 @@ class ConductorManager(base_manager.BaseConductorManager):
1693 1694
                  not exist
1694 1695
         :raises: VolumeConnectorNotFound if the volume connector cannot be
1695 1696
                  found
1697
+        :raises: InvalidStateRequested if the node associated with the
1698
+                 connector is not powered off.
1696 1699
         """
1697 1700
         LOG.debug('RPC destroy_volume_connector called for volume connector '
1698 1701
                   '%(connector)s',
1699 1702
                   {'connector': connector.uuid})
1700 1703
         with task_manager.acquire(context, connector.node_id,
1701 1704
                                   purpose='volume connector deletion') as task:
1705
+            node = task.node
1706
+            if node.power_state != states.POWER_OFF:
1707
+                raise exception.InvalidStateRequested(
1708
+                    action='volume connector deletion',
1709
+                    node=node.uuid,
1710
+                    state=node.power_state)
1702 1711
             connector.destroy()
1703 1712
             LOG.info('Successfully deleted volume connector %(connector)s. '
1704 1713
                      'The node associated with the connector was %(node)s',
@@ -1707,7 +1716,8 @@ class ConductorManager(base_manager.BaseConductorManager):
1707 1716
     @METRICS.timer('ConductorManager.destroy_volume_target')
1708 1717
     @messaging.expected_exceptions(exception.NodeLocked,
1709 1718
                                    exception.NodeNotFound,
1710
-                                   exception.VolumeTargetNotFound)
1719
+                                   exception.VolumeTargetNotFound,
1720
+                                   exception.InvalidStateRequested)
1711 1721
     def destroy_volume_target(self, context, target):
1712 1722
         """Delete a volume target.
1713 1723
 
@@ -1717,12 +1727,20 @@ class ConductorManager(base_manager.BaseConductorManager):
1717 1727
         :raises: NodeNotFound if the node associated with the target does
1718 1728
                  not exist
1719 1729
         :raises: VolumeTargetNotFound if the volume target cannot be found
1730
+        :raises: InvalidStateRequested if the node associated with the target
1731
+                 is not powered off.
1720 1732
         """
1721 1733
         LOG.debug('RPC destroy_volume_target called for volume target '
1722 1734
                   '%(target)s',
1723 1735
                   {'target': target.uuid})
1724 1736
         with task_manager.acquire(context, target.node_id,
1725 1737
                                   purpose='volume target deletion') as task:
1738
+            node = task.node
1739
+            if node.power_state != states.POWER_OFF:
1740
+                raise exception.InvalidStateRequested(
1741
+                    action='volume target deletion',
1742
+                    node=node.uuid,
1743
+                    state=node.power_state)
1726 1744
             target.destroy()
1727 1745
             LOG.info('Successfully deleted volume target %(target)s. '
1728 1746
                      'The node associated with the target was %(node)s',
@@ -2030,7 +2048,8 @@ class ConductorManager(base_manager.BaseConductorManager):
2030 2048
         exception.NodeLocked,
2031 2049
         exception.NodeNotFound,
2032 2050
         exception.VolumeConnectorNotFound,
2033
-        exception.VolumeConnectorTypeAndIdAlreadyExists)
2051
+        exception.VolumeConnectorTypeAndIdAlreadyExists,
2052
+        exception.InvalidStateRequested)
2034 2053
     def update_volume_connector(self, context, connector):
2035 2054
         """Update a volume connector.
2036 2055
 
@@ -2047,13 +2066,21 @@ class ConductorManager(base_manager.BaseConductorManager):
2047 2066
         :raises: VolumeConnectorTypeAndIdAlreadyExists if another connector
2048 2067
                  already exists with the same values for type and connector_id
2049 2068
                  fields
2069
+        :raises: InvalidStateRequested if the node associated with the
2070
+                 connector is not powered off.
2050 2071
         """
2051 2072
         LOG.debug("RPC update_volume_connector called for connector "
2052 2073
                   "%(connector)s.",
2053 2074
                   {'connector': connector.uuid})
2054 2075
 
2055 2076
         with task_manager.acquire(context, connector.node_id,
2056
-                                  purpose='volume connector update'):
2077
+                                  purpose='volume connector update') as task:
2078
+            node = task.node
2079
+            if node.power_state != states.POWER_OFF:
2080
+                raise exception.InvalidStateRequested(
2081
+                    action='volume connector update',
2082
+                    node=node.uuid,
2083
+                    state=node.power_state)
2057 2084
             connector.save()
2058 2085
             LOG.info("Successfully updated volume connector %(connector)s.",
2059 2086
                      {'connector': connector.uuid})
@@ -2065,7 +2092,8 @@ class ConductorManager(base_manager.BaseConductorManager):
2065 2092
         exception.NodeLocked,
2066 2093
         exception.NodeNotFound,
2067 2094
         exception.VolumeTargetNotFound,
2068
-        exception.VolumeTargetBootIndexAlreadyExists)
2095
+        exception.VolumeTargetBootIndexAlreadyExists,
2096
+        exception.InvalidStateRequested)
2069 2097
     def update_volume_target(self, context, target):
2070 2098
         """Update a volume target.
2071 2099
 
@@ -2080,12 +2108,20 @@ class ConductorManager(base_manager.BaseConductorManager):
2080 2108
         :raises: VolumeTargetNotFound if the volume target cannot be found
2081 2109
         :raises: VolumeTargetBootIndexAlreadyExists if a volume target already
2082 2110
                  exists with the same node ID and boot index values
2111
+        :raises: InvalidStateRequested if the node associated with the target
2112
+                 is not powered off.
2083 2113
         """
2084 2114
         LOG.debug("RPC update_volume_target called for target %(target)s.",
2085 2115
                   {'target': target.uuid})
2086 2116
 
2087 2117
         with task_manager.acquire(context, target.node_id,
2088
-                                  purpose='volume target update'):
2118
+                                  purpose='volume target update') as task:
2119
+            node = task.node
2120
+            if node.power_state != states.POWER_OFF:
2121
+                raise exception.InvalidStateRequested(
2122
+                    action='volume target update',
2123
+                    node=node.uuid,
2124
+                    state=node.power_state)
2089 2125
             target.save()
2090 2126
             LOG.info("Successfully updated volume target %(target)s.",
2091 2127
                      {'target': target.uuid})

+ 73
- 12
ironic/tests/unit/conductor/test_manager.py View File

@@ -6041,7 +6041,8 @@ class DoNodeAdoptionTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
6041 6041
 class DestroyVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
6042 6042
                                      db_base.DbTestCase):
6043 6043
     def test_destroy_volume_connector(self):
6044
-        node = obj_utils.create_test_node(self.context, driver='fake')
6044
+        node = obj_utils.create_test_node(self.context, driver='fake',
6045
+                                          power_state=states.POWER_OFF)
6045 6046
 
6046 6047
         volume_connector = obj_utils.create_test_volume_connector(
6047 6048
             self.context, node_id=node.id)
@@ -6064,12 +6065,25 @@ class DestroyVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
6064 6065
         # Compare true exception hidden by @messaging.expected_exceptions
6065 6066
         self.assertEqual(exception.NodeLocked, exc.exc_info[0])
6066 6067
 
6068
+    def test_destroy_volume_connector_node_power_on(self):
6069
+        node = obj_utils.create_test_node(self.context, driver='fake',
6070
+                                          power_state=states.POWER_ON)
6071
+
6072
+        volume_connector = obj_utils.create_test_volume_connector(
6073
+            self.context, node_id=node.id)
6074
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
6075
+                                self.service.destroy_volume_connector,
6076
+                                self.context, volume_connector)
6077
+        # Compare true exception hidden by @messaging.expected_exceptions
6078
+        self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
6079
+
6067 6080
 
6068 6081
 @mgr_utils.mock_record_keepalive
6069 6082
 class UpdateVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
6070 6083
                                     db_base.DbTestCase):
6071 6084
     def test_update_volume_connector(self):
6072
-        node = obj_utils.create_test_node(self.context, driver='fake')
6085
+        node = obj_utils.create_test_node(self.context, driver='fake',
6086
+                                          power_state=states.POWER_OFF)
6073 6087
 
6074 6088
         volume_connector = obj_utils.create_test_volume_connector(
6075 6089
             self.context, node_id=node.id, extra={'foo': 'bar'})
@@ -6093,7 +6107,8 @@ class UpdateVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
6093 6107
         self.assertEqual(exception.NodeLocked, exc.exc_info[0])
6094 6108
 
6095 6109
     def test_update_volume_connector_type(self):
6096
-        node = obj_utils.create_test_node(self.context, driver='fake')
6110
+        node = obj_utils.create_test_node(self.context, driver='fake',
6111
+                                          power_state=states.POWER_OFF)
6097 6112
         volume_connector = obj_utils.create_test_volume_connector(
6098 6113
             self.context, node_id=node.id, extra={'vol_id': 'fake-id'})
6099 6114
         new_type = 'wwnn'
@@ -6103,7 +6118,8 @@ class UpdateVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
6103 6118
         self.assertEqual(new_type, res.type)
6104 6119
 
6105 6120
     def test_update_volume_connector_uuid(self):
6106
-        node = obj_utils.create_test_node(self.context, driver='fake')
6121
+        node = obj_utils.create_test_node(self.context, driver='fake',
6122
+                                          power_state=states.POWER_OFF)
6107 6123
         volume_connector = obj_utils.create_test_volume_connector(
6108 6124
             self.context, node_id=node.id)
6109 6125
         volume_connector.uuid = uuidutils.generate_uuid()
@@ -6114,7 +6130,8 @@ class UpdateVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
6114 6130
         self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
6115 6131
 
6116 6132
     def test_update_volume_connector_duplicate(self):
6117
-        node = obj_utils.create_test_node(self.context, driver='fake')
6133
+        node = obj_utils.create_test_node(self.context, driver='fake',
6134
+                                          power_state=states.POWER_OFF)
6118 6135
         volume_connector1 = obj_utils.create_test_volume_connector(
6119 6136
             self.context, node_id=node.id)
6120 6137
         volume_connector2 = obj_utils.create_test_volume_connector(
@@ -6128,12 +6145,26 @@ class UpdateVolumeConnectorTestCase(mgr_utils.ServiceSetUpMixin,
6128 6145
         self.assertEqual(exception.VolumeConnectorTypeAndIdAlreadyExists,
6129 6146
                          exc.exc_info[0])
6130 6147
 
6148
+    def test_update_volume_connector_node_power_on(self):
6149
+        node = obj_utils.create_test_node(self.context, driver='fake',
6150
+                                          power_state=states.POWER_ON)
6151
+
6152
+        volume_connector = obj_utils.create_test_volume_connector(
6153
+            self.context, node_id=node.id)
6154
+        volume_connector.extra = {'foo': 'baz'}
6155
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
6156
+                                self.service.update_volume_connector,
6157
+                                self.context, volume_connector)
6158
+        # Compare true exception hidden by @messaging.expected_exceptions
6159
+        self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
6160
+
6131 6161
 
6132 6162
 @mgr_utils.mock_record_keepalive
6133 6163
 class DestroyVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6134 6164
                                   db_base.DbTestCase):
6135 6165
     def test_destroy_volume_target(self):
6136
-        node = obj_utils.create_test_node(self.context, driver='fake')
6166
+        node = obj_utils.create_test_node(self.context, driver='fake',
6167
+                                          power_state=states.POWER_OFF)
6137 6168
 
6138 6169
         volume_target = obj_utils.create_test_volume_target(self.context,
6139 6170
                                                             node_id=node.id)
@@ -6169,7 +6200,8 @@ class DestroyVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6169 6200
         self.assertEqual(exception.NodeNotFound, exc.exc_info[0])
6170 6201
 
6171 6202
     def test_destroy_volume_target_already_destroyed(self):
6172
-        node = obj_utils.create_test_node(self.context, driver='fake')
6203
+        node = obj_utils.create_test_node(self.context, driver='fake',
6204
+                                          power_state=states.POWER_OFF)
6173 6205
         volume_target = obj_utils.create_test_volume_target(self.context,
6174 6206
                                                             node_id=node.id)
6175 6207
         self.service.destroy_volume_target(self.context, volume_target)
@@ -6179,12 +6211,25 @@ class DestroyVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6179 6211
         # Compare true exception hidden by @messaging.expected_exceptions
6180 6212
         self.assertEqual(exception.VolumeTargetNotFound, exc.exc_info[0])
6181 6213
 
6214
+    def test_destroy_volume_target_node_power_on(self):
6215
+        node = obj_utils.create_test_node(self.context, driver='fake',
6216
+                                          power_state=states.POWER_ON)
6217
+
6218
+        volume_target = obj_utils.create_test_volume_target(self.context,
6219
+                                                            node_id=node.id)
6220
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
6221
+                                self.service.destroy_volume_target,
6222
+                                self.context, volume_target)
6223
+        # Compare true exception hidden by @messaging.expected_exceptions
6224
+        self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])
6225
+
6182 6226
 
6183 6227
 @mgr_utils.mock_record_keepalive
6184 6228
 class UpdateVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6185 6229
                                  db_base.DbTestCase):
6186 6230
     def test_update_volume_target(self):
6187
-        node = obj_utils.create_test_node(self.context, driver='fake')
6231
+        node = obj_utils.create_test_node(self.context, driver='fake',
6232
+                                          power_state=states.POWER_OFF)
6188 6233
 
6189 6234
         volume_target = obj_utils.create_test_volume_target(
6190 6235
             self.context, node_id=node.id, extra={'foo': 'bar'})
@@ -6206,7 +6251,8 @@ class UpdateVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6206 6251
         self.assertEqual(exception.NodeLocked, exc.exc_info[0])
6207 6252
 
6208 6253
     def test_update_volume_target_volume_type(self):
6209
-        node = obj_utils.create_test_node(self.context, driver='fake')
6254
+        node = obj_utils.create_test_node(self.context, driver='fake',
6255
+                                          power_state=states.POWER_OFF)
6210 6256
         volume_target = obj_utils.create_test_volume_target(
6211 6257
             self.context, node_id=node.id, extra={'vol_id': 'fake-id'})
6212 6258
         new_volume_type = 'fibre_channel'
@@ -6216,7 +6262,8 @@ class UpdateVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6216 6262
         self.assertEqual(new_volume_type, res.volume_type)
6217 6263
 
6218 6264
     def test_update_volume_target_uuid(self):
6219
-        node = obj_utils.create_test_node(self.context, driver='fake')
6265
+        node = obj_utils.create_test_node(self.context, driver='fake',
6266
+                                          power_state=states.POWER_OFF)
6220 6267
         volume_target = obj_utils.create_test_volume_target(
6221 6268
             self.context, node_id=node.id)
6222 6269
         volume_target.uuid = uuidutils.generate_uuid()
@@ -6227,7 +6274,8 @@ class UpdateVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6227 6274
         self.assertEqual(exception.InvalidParameterValue, exc.exc_info[0])
6228 6275
 
6229 6276
     def test_update_volume_target_duplicate(self):
6230
-        node = obj_utils.create_test_node(self.context, driver='fake')
6277
+        node = obj_utils.create_test_node(self.context, driver='fake',
6278
+                                          power_state=states.POWER_OFF)
6231 6279
         volume_target1 = obj_utils.create_test_volume_target(
6232 6280
             self.context, node_id=node.id)
6233 6281
         volume_target2 = obj_utils.create_test_volume_target(
@@ -6242,7 +6290,8 @@ class UpdateVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6242 6290
                          exc.exc_info[0])
6243 6291
 
6244 6292
     def _test_update_volume_target_exception(self, expected_exc):
6245
-        node = obj_utils.create_test_node(self.context, driver='fake')
6293
+        node = obj_utils.create_test_node(self.context, driver='fake',
6294
+                                          power_state=states.POWER_OFF)
6246 6295
         volume_target = obj_utils.create_test_volume_target(
6247 6296
             self.context, node_id=node.id, extra={'vol_id': 'fake-id'})
6248 6297
         new_volume_type = 'fibre_channel'
@@ -6261,3 +6310,15 @@ class UpdateVolumeTargetTestCase(mgr_utils.ServiceSetUpMixin,
6261 6310
     def test_update_volume_target_not_found(self):
6262 6311
             self._test_update_volume_target_exception(
6263 6312
                 exception.VolumeTargetNotFound)
6313
+
6314
+    def test_update_volume_target_node_power_on(self):
6315
+        node = obj_utils.create_test_node(self.context, driver='fake',
6316
+                                          power_state=states.POWER_ON)
6317
+        volume_target = obj_utils.create_test_volume_target(self.context,
6318
+                                                            node_id=node.id)
6319
+        volume_target.extra = {'foo': 'baz'}
6320
+        exc = self.assertRaises(messaging.rpc.ExpectedException,
6321
+                                self.service.update_volume_target,
6322
+                                self.context, volume_target)
6323
+        # Compare true exception hidden by @messaging.expected_exceptions
6324
+        self.assertEqual(exception.InvalidStateRequested, exc.exc_info[0])

Loading…
Cancel
Save