Add OperationApplyTime support to Volume methods
The supported_values property in the OperationApplyTimeSupportField class returns an unmapped list of OperationApplyTime values supported by the service. The convention in Sushy for properties that return a list of schema-defined enumeration values is to return a MappedListField. This type of field performs mapping between Redfish schema-defined enumeration values and constants exposed by the Sushy package. This update adds the mapped_supported_values property to return a MappedListField of OperationApplyTime values supported by the service. The supported_values property is deprecated. The create_volume method in the VolumeCollection class and the delete_volume and initialize_volume methods in the Volume class are updated to take optional apply_time and timeout keyword parameters. This allows the caller of those volume methods to specify a preferred OperationApplyTime annotation and a maximum timeout for synchronous operations. If the specified apply_time is 'Immediate', the operation is called with blocking enabled. Otherwise blocking is disabled. For asynchronous operations, those three methods will now return a TaskMonitor instance that the caller can use to monitor the state of the task. Change-Id: I8ba2b9ff1e80fa0c2edc3a11ab80844d732e4394 Story: 2003514 Task: 41087
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
---
|
||||
deprecations:
|
||||
- |
|
||||
The ``supported_values`` property in the
|
||||
``OperationApplyTimeSupportField`` class is deprecated. Use the
|
||||
``mapped_supported_values`` property instead. The
|
||||
``mapped_supported_values`` property uses the ``MappedListField`` type
|
||||
to map the Redfish schema-defined enumeration values to constants exposed
|
||||
by the Sushy package.
|
||||
features:
|
||||
- |
|
||||
Update the ``create_volume`` method in the ``VolumeCollection`` class and
|
||||
the ``delete_volume`` and ``initialize_volume`` methods in the ``Volume``
|
||||
class to take optional ``apply_time`` and ``timeout`` keyword parameters.
|
||||
This allows the caller of those volume methods to specify a preferred
|
||||
``OperationApplyTime`` annotation and a maximum timeout for synchronous
|
||||
operations. For asynchronous operations, those three methods will now
|
||||
return a ``TaskMonitor`` instance that the caller can use to monitor the
|
||||
state of the task.
|
||||
@@ -42,8 +42,18 @@ class OperationApplyTimeSupportField(base.CompositeField):
|
||||
|
||||
supported_values = base.Field('SupportedValues', required=True,
|
||||
adapter=list)
|
||||
"""The client is allowed request when performing a create, delete, or
|
||||
action operation"""
|
||||
"""The types of apply times that the client is allowed request when
|
||||
performing a create, delete, or action operation returned as an unmapped
|
||||
list
|
||||
|
||||
Deprecated: Use `mapped_supported_values`.
|
||||
"""
|
||||
|
||||
mapped_supported_values = base.MappedListField(
|
||||
'SupportedValues', res_maps.APPLY_TIME_VALUE_MAP, required=True)
|
||||
"""The types of apply times that the client is allowed request when
|
||||
performing a create, delete, or action operation returned as a mapped
|
||||
list"""
|
||||
|
||||
|
||||
class ActionField(base.CompositeField):
|
||||
|
||||
@@ -18,7 +18,11 @@ import logging
|
||||
from sushy import exceptions
|
||||
from sushy.resources import base
|
||||
from sushy.resources import common
|
||||
from sushy.resources import constants as res_cons
|
||||
from sushy.resources import mappings as res_maps
|
||||
from sushy.resources.system.storage import constants as store_cons
|
||||
from sushy.resources.system.storage import mappings as store_maps
|
||||
from sushy.resources.task_monitor import TaskMonitor
|
||||
from sushy import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -86,30 +90,71 @@ class Volume(base.ResourceBase):
|
||||
set(store_maps.VOLUME_INIT_TYPE_MAP).
|
||||
intersection(action.allowed_values)])
|
||||
|
||||
def initialize_volume(self, value):
|
||||
def initialize_volume(self, value=store_cons.VOLUME_INIT_TYPE_FAST,
|
||||
apply_time=None, timeout=500):
|
||||
"""Initialize the volume.
|
||||
|
||||
:param value: The InitializeType value.
|
||||
:param apply_time: When to update the attributes. Optional.
|
||||
APPLY_TIME_IMMEDIATE - Immediate,
|
||||
APPLY_TIME_ON_RESET - On reset,
|
||||
APPLY_TIME_MAINT_START - During specified maintenance time
|
||||
APPLY_TIME_MAINT_RESET - On reset during specified maintenance time
|
||||
:param timeout: Max time in seconds to wait for blocking async call.
|
||||
:raises: InvalidParameterValueError, if the target value is not
|
||||
allowed.
|
||||
:raises: ConnectionError
|
||||
:raises: HTTPError
|
||||
:returns: TaskMonitor if async task or None if successful init
|
||||
"""
|
||||
valid_values = self.get_allowed_initialize_volume_values()
|
||||
if value not in valid_values:
|
||||
raise exceptions.InvalidParameterValueError(
|
||||
parameter='value', value=value, valid_values=valid_values)
|
||||
value = store_maps.VOLUME_INIT_TYPE_MAP_REV[value]
|
||||
payload = {'InitializeType': value}
|
||||
blocking = False
|
||||
oat_prop = '@Redfish.OperationApplyTime'
|
||||
if apply_time:
|
||||
payload[oat_prop] = res_maps.APPLY_TIME_VALUE_MAP_REV[apply_time]
|
||||
if (payload and payload.get(oat_prop) == res_maps.
|
||||
APPLY_TIME_VALUE_MAP_REV[res_cons.APPLY_TIME_IMMEDIATE]):
|
||||
blocking = True
|
||||
target_uri = self._get_initialize_action_element().target_uri
|
||||
self._conn.post(target_uri, data={'InitializeType': value},
|
||||
blocking=True)
|
||||
r = self._conn.post(target_uri, data=payload, blocking=blocking,
|
||||
timeout=timeout)
|
||||
if r.status_code == 202:
|
||||
return (TaskMonitor(self, r.headers.get('location'))
|
||||
.set_retry_after(r.headers.get('retry-after')))
|
||||
|
||||
def delete_volume(self, payload=None):
|
||||
def delete_volume(self, payload=None, apply_time=None, timeout=500):
|
||||
"""Delete the volume.
|
||||
|
||||
:param payload: May contain @Redfish.OperationApplyTime property
|
||||
:param apply_time: When to update the attributes. Optional.
|
||||
APPLY_TIME_IMMEDIATE - Immediate,
|
||||
APPLY_TIME_ON_RESET - On reset,
|
||||
APPLY_TIME_MAINT_START - During specified maintenance time
|
||||
APPLY_TIME_MAINT_RESET - On reset during specified maintenance time
|
||||
:param timeout: Max time in seconds to wait for blocking async call.
|
||||
:raises: ConnectionError
|
||||
:raises: HTTPError
|
||||
:returns: TaskMonitor if async task or None if successful deletion
|
||||
"""
|
||||
self._conn.delete(self._path, data=payload, blocking=True)
|
||||
blocking = False
|
||||
oat_prop = '@Redfish.OperationApplyTime'
|
||||
if apply_time:
|
||||
if payload is None:
|
||||
payload = {}
|
||||
payload[oat_prop] = res_maps.APPLY_TIME_VALUE_MAP_REV[apply_time]
|
||||
if (payload and payload.get(oat_prop) == res_maps.
|
||||
APPLY_TIME_VALUE_MAP_REV[res_cons.APPLY_TIME_IMMEDIATE]):
|
||||
blocking = True
|
||||
r = self._conn.delete(self._path, data=payload, blocking=blocking,
|
||||
timeout=timeout)
|
||||
if r.status_code == 202:
|
||||
return (TaskMonitor(self, r.headers.get('location'))
|
||||
.set_retry_after(r.headers.get('retry-after')))
|
||||
|
||||
|
||||
class VolumeCollection(base.ResourceCollectionBase):
|
||||
@@ -145,17 +190,36 @@ class VolumeCollection(base.ResourceCollectionBase):
|
||||
"""Indicates if a client is allowed to request for a specific apply
|
||||
time of a create, delete, or action operation of a given resource"""
|
||||
|
||||
def create_volume(self, payload):
|
||||
def create_volume(self, payload, apply_time=None, timeout=500):
|
||||
"""Create a volume.
|
||||
|
||||
:param payload: The payload representing the new volume to create.
|
||||
:param apply_time: When to update the attributes. Optional.
|
||||
APPLY_TIME_IMMEDIATE - Immediate,
|
||||
APPLY_TIME_ON_RESET - On reset,
|
||||
APPLY_TIME_MAINT_START - During specified maintenance time
|
||||
APPLY_TIME_MAINT_RESET - On reset during specified maintenance time
|
||||
:param timeout: Max time in seconds to wait for blocking async call.
|
||||
:raises: ConnectionError
|
||||
:raises: HTTPError
|
||||
:returns: Newly created Volume resource or None if no Location header
|
||||
:returns: Newly created Volume resource or TaskMonitor if async task
|
||||
"""
|
||||
r = self._conn.post(self._path, data=payload, blocking=True)
|
||||
blocking = False
|
||||
oat_prop = '@Redfish.OperationApplyTime'
|
||||
if apply_time:
|
||||
if payload is None:
|
||||
payload = {}
|
||||
payload[oat_prop] = res_maps.APPLY_TIME_VALUE_MAP_REV[apply_time]
|
||||
if (payload and payload.get(oat_prop) == res_maps.
|
||||
APPLY_TIME_VALUE_MAP_REV[res_cons.APPLY_TIME_IMMEDIATE]):
|
||||
blocking = True
|
||||
r = self._conn.post(self._path, data=payload, blocking=blocking,
|
||||
timeout=timeout)
|
||||
location = r.headers.get('Location')
|
||||
if r.status_code == 201:
|
||||
if location:
|
||||
self.refresh()
|
||||
return self.get_member(location)
|
||||
elif r.status_code == 202:
|
||||
return (TaskMonitor(self, location)
|
||||
.set_retry_after(r.headers.get('retry-after')))
|
||||
|
||||
@@ -17,6 +17,8 @@ from dateutil import parser
|
||||
|
||||
import sushy
|
||||
from sushy import exceptions
|
||||
from sushy.resources import constants as res_cons
|
||||
from sushy.resources.system.storage import constants as store_cons
|
||||
from sushy.resources.system.storage import volume
|
||||
from sushy.tests.unit import base
|
||||
|
||||
@@ -54,12 +56,27 @@ class VolumeTestCase(base.TestCase):
|
||||
identifier.durable_name)
|
||||
self.assertIsNone(self.stor_volume.block_size_bytes)
|
||||
|
||||
def test_initialize_volume(self):
|
||||
def test_initialize_volume_immediate(self):
|
||||
target_uri = '/redfish/v1/Systems/3/Storage/RAIDIntegrated/' \
|
||||
'Volumes/1/Actions/Volume.Initialize'
|
||||
self.stor_volume.initialize_volume('fast')
|
||||
self.stor_volume.initialize_volume(
|
||||
store_cons.VOLUME_INIT_TYPE_FAST,
|
||||
apply_time=res_cons.APPLY_TIME_IMMEDIATE)
|
||||
self.stor_volume._conn.post.assert_called_once_with(
|
||||
target_uri, data={'InitializeType': 'Fast'}, blocking=True)
|
||||
target_uri, data={'InitializeType': 'Fast',
|
||||
'@Redfish.OperationApplyTime': 'Immediate'},
|
||||
blocking=True, timeout=500)
|
||||
|
||||
def test_initialize_volume_on_reset(self):
|
||||
target_uri = '/redfish/v1/Systems/3/Storage/RAIDIntegrated/' \
|
||||
'Volumes/1/Actions/Volume.Initialize'
|
||||
self.stor_volume.initialize_volume(
|
||||
store_cons.VOLUME_INIT_TYPE_FAST,
|
||||
apply_time=res_cons.APPLY_TIME_ON_RESET)
|
||||
self.stor_volume._conn.post.assert_called_once_with(
|
||||
target_uri, data={'InitializeType': 'Fast',
|
||||
'@Redfish.OperationApplyTime': 'OnReset'},
|
||||
blocking=False, timeout=500)
|
||||
|
||||
def test_initialize_volume_bad_value(self):
|
||||
self.assertRaisesRegex(
|
||||
@@ -70,13 +87,28 @@ class VolumeTestCase(base.TestCase):
|
||||
def test_delete_volume(self):
|
||||
self.stor_volume.delete_volume()
|
||||
self.stor_volume._conn.delete.assert_called_once_with(
|
||||
self.stor_volume._path, data=None, blocking=True)
|
||||
self.stor_volume._path, data=None, blocking=False, timeout=500)
|
||||
|
||||
def test_delete_volume_with_payload(self):
|
||||
payload = {'@Redfish.OperationApplyTime': 'OnReset'}
|
||||
payload = {'@Redfish.OperationApplyTime': 'Immediate'}
|
||||
self.stor_volume.delete_volume(payload=payload)
|
||||
self.stor_volume._conn.delete.assert_called_once_with(
|
||||
self.stor_volume._path, data=payload, blocking=True)
|
||||
self.stor_volume._path, data=payload, blocking=True, timeout=500)
|
||||
|
||||
def test_delete_volume_immediate(self):
|
||||
payload = {}
|
||||
self.stor_volume.delete_volume(
|
||||
payload=payload, apply_time=res_cons.APPLY_TIME_IMMEDIATE)
|
||||
self.stor_volume._conn.delete.assert_called_once_with(
|
||||
self.stor_volume._path, data=payload, blocking=True, timeout=500)
|
||||
|
||||
def test_delete_volume_on_reset(self):
|
||||
payload = {}
|
||||
self.stor_volume.delete_volume(
|
||||
payload=payload, apply_time=res_cons.APPLY_TIME_ON_RESET,
|
||||
timeout=250)
|
||||
self.stor_volume._conn.delete.assert_called_once_with(
|
||||
self.stor_volume._path, data=payload, blocking=False, timeout=250)
|
||||
|
||||
|
||||
class VolumeCollectionTestCase(base.TestCase):
|
||||
@@ -113,6 +145,10 @@ class VolumeCollectionTestCase(base.TestCase):
|
||||
support._maintenance_window_resource.resource_uri)
|
||||
self.assertEqual(['Immediate', 'OnReset', 'AtMaintenanceWindowStart'],
|
||||
support.supported_values)
|
||||
self.assertEqual([res_cons.APPLY_TIME_IMMEDIATE,
|
||||
res_cons.APPLY_TIME_ON_RESET,
|
||||
res_cons.APPLY_TIME_MAINT_START],
|
||||
support.mapped_supported_values)
|
||||
|
||||
@mock.patch.object(volume, 'Volume', autospec=True)
|
||||
def test_get_member(self, Volume_mock):
|
||||
@@ -175,23 +211,54 @@ class VolumeCollectionTestCase(base.TestCase):
|
||||
|
||||
self.assertEqual(1073741824000, self.stor_vol_col.max_size_bytes)
|
||||
|
||||
def test_create_volume(self):
|
||||
def test_create_volume_immediate(self):
|
||||
payload = {
|
||||
'Name': 'My Volume 4',
|
||||
'VolumeType': 'Mirrored',
|
||||
'RAIDType': 'RAID1',
|
||||
'CapacityBytes': 107374182400
|
||||
}
|
||||
expected_payload = dict(payload)
|
||||
expected_payload['@Redfish.OperationApplyTime'] = 'Immediate'
|
||||
with open('sushy/tests/unit/json_samples/volume4.json') as f:
|
||||
self.conn.get.return_value.json.return_value = json.load(f)
|
||||
self.conn.post.return_value.status_code = 201
|
||||
self.conn.post.return_value.headers.return_value = {
|
||||
'Location': '/redfish/v1/Systems/437XR1138R2/Storage/1/Volumes/4'
|
||||
}
|
||||
new_vol = self.stor_vol_col.create_volume(payload)
|
||||
new_vol = self.stor_vol_col.create_volume(
|
||||
payload, apply_time=res_cons.APPLY_TIME_IMMEDIATE)
|
||||
self.stor_vol_col._conn.post.assert_called_once_with(
|
||||
'/redfish/v1/Systems/437XR1138R2/Storage/1/Volumes',
|
||||
data=payload, blocking=True)
|
||||
data=expected_payload, blocking=True, timeout=500)
|
||||
self.stor_vol_col.refresh.assert_called_once()
|
||||
self.assertIsNotNone(new_vol)
|
||||
self.assertEqual('4', new_vol.identity)
|
||||
self.assertEqual('My Volume 4', new_vol.name)
|
||||
self.assertEqual(107374182400, new_vol.capacity_bytes)
|
||||
self.assertEqual(sushy.VOLUME_TYPE_MIRRORED, new_vol.volume_type)
|
||||
self.assertEqual(sushy.RAID_TYPE_RAID1, new_vol.raid_type)
|
||||
|
||||
def test_create_volume_on_reset(self):
|
||||
payload = {
|
||||
'Name': 'My Volume 4',
|
||||
'VolumeType': 'Mirrored',
|
||||
'RAIDType': 'RAID1',
|
||||
'CapacityBytes': 107374182400
|
||||
}
|
||||
expected_payload = dict(payload)
|
||||
expected_payload['@Redfish.OperationApplyTime'] = 'OnReset'
|
||||
with open('sushy/tests/unit/json_samples/volume4.json') as f:
|
||||
self.conn.get.return_value.json.return_value = json.load(f)
|
||||
self.conn.post.return_value.status_code = 201
|
||||
self.conn.post.return_value.headers.return_value = {
|
||||
'Location': '/redfish/v1/Systems/437XR1138R2/Storage/1/Volumes/4'
|
||||
}
|
||||
new_vol = self.stor_vol_col.create_volume(
|
||||
payload, apply_time=res_cons.APPLY_TIME_ON_RESET)
|
||||
self.stor_vol_col._conn.post.assert_called_once_with(
|
||||
'/redfish/v1/Systems/437XR1138R2/Storage/1/Volumes',
|
||||
data=expected_payload, blocking=False, timeout=500)
|
||||
self.stor_vol_col.refresh.assert_called_once()
|
||||
self.assertIsNotNone(new_vol)
|
||||
self.assertEqual('4', new_vol.identity)
|
||||
|
||||
@@ -107,9 +107,12 @@ class SystemTestCase(base.TestCase):
|
||||
'/redfish/v1/Systems/437XR1138R2'},
|
||||
'maintenance_window_duration_in_seconds': 600,
|
||||
'maintenance_window_start_time':
|
||||
parser.parse('2017-05-03T23:12:37-05:00'),
|
||||
'supported_values':
|
||||
['Immediate', 'AtMaintenanceWindowStart']},
|
||||
parser.parse('2017-05-03T23:12:37-05:00'),
|
||||
'supported_values':
|
||||
['Immediate', 'AtMaintenanceWindowStart'],
|
||||
'mapped_supported_values':
|
||||
[res_cons.APPLY_TIME_IMMEDIATE,
|
||||
res_cons.APPLY_TIME_MAINT_START]},
|
||||
'target_uri':
|
||||
'/redfish/v1/Systems/437XR1138R2/Actions/'
|
||||
'ComputerSystem.Reset'}},
|
||||
@@ -206,6 +209,9 @@ class SystemTestCase(base.TestCase):
|
||||
self.assertIsNotNone(support)
|
||||
self.assertEqual(['Immediate', 'AtMaintenanceWindowStart'],
|
||||
support.supported_values)
|
||||
self.assertEqual([res_cons.APPLY_TIME_IMMEDIATE,
|
||||
res_cons.APPLY_TIME_MAINT_START],
|
||||
support.mapped_supported_values)
|
||||
self.assertEqual(parser.parse('2017-05-03T23:12:37-05:00'),
|
||||
support.maintenance_window_start_time)
|
||||
self.assertEqual(600, support.maintenance_window_duration_in_seconds)
|
||||
|
||||
Reference in New Issue
Block a user