From 5ac41e1ea1d42f9a5b4c219e7ac95eee0f0f7e33 Mon Sep 17 00:00:00 2001 From: Yong Huang Date: Fri, 12 Oct 2018 09:35:13 +0000 Subject: [PATCH] Unity: adds lun migration support (#241) Use move session to implement lun migration. --- README.rst | 2 + storops/exception.py | 26 +++ storops/unity/enums.py | 26 +++ storops/unity/parser_configs.yaml | 26 +++ storops/unity/resource/lun.py | 47 ++++- storops/unity/resource/move_session.py | 76 ++++++++ storops/unity/resource/system.py | 4 + storops_test/unity/resource/test_lun.py | 66 ++++++- .../unity/resource/test_move_session.py | 83 +++++++++ storops_test/unity/resource/test_system.py | 8 + .../unity/rest_data/moveSession/all.json | 42 +++++ .../rest_data/moveSession/create_move_29.json | 13 ++ .../rest_data/moveSession/create_move_30.json | 13 ++ .../rest_data/moveSession/create_move_31.json | 13 ++ .../rest_data/moveSession/create_move_32.json | 13 ++ .../rest_data/moveSession/create_move_33.json | 13 ++ .../rest_data/moveSession/create_move_34.json | 13 ++ .../unity/rest_data/moveSession/empty.json | 0 .../moveSession/error_has_thin_clones.json | 12 ++ .../error_source_dest_not_exists.json | 12 ++ .../unity/rest_data/moveSession/index.json | 165 ++++++++++++++++++ .../unity/rest_data/moveSession/move_27.json | 30 ++++ .../unity/rest_data/moveSession/move_28.json | 30 ++++ .../unity/rest_data/moveSession/move_29.json | 30 ++++ .../unity/rest_data/moveSession/move_30.json | 30 ++++ .../unity/rest_data/moveSession/move_31.json | 30 ++++ .../unity/rest_data/moveSession/move_32.json | 30 ++++ .../unity/rest_data/moveSession/move_33.json | 30 ++++ .../unity/rest_data/moveSession/move_34.json | 30 ++++ .../unity/rest_data/moveSession/type.json | 81 +++++++++ storops_test/unity/rest_data/pool/index.json | 4 + storops_test/unity/rest_data/pool/pool_5.json | 101 +++++++++++ 32 files changed, 1097 insertions(+), 2 deletions(-) mode change 100644 => 100755 README.rst create mode 100644 storops/unity/resource/move_session.py mode change 100755 => 100644 storops/unity/resource/system.py create mode 100644 storops_test/unity/resource/test_move_session.py create mode 100644 storops_test/unity/rest_data/moveSession/all.json create mode 100644 storops_test/unity/rest_data/moveSession/create_move_29.json create mode 100644 storops_test/unity/rest_data/moveSession/create_move_30.json create mode 100644 storops_test/unity/rest_data/moveSession/create_move_31.json create mode 100644 storops_test/unity/rest_data/moveSession/create_move_32.json create mode 100755 storops_test/unity/rest_data/moveSession/create_move_33.json create mode 100755 storops_test/unity/rest_data/moveSession/create_move_34.json create mode 100644 storops_test/unity/rest_data/moveSession/empty.json create mode 100644 storops_test/unity/rest_data/moveSession/error_has_thin_clones.json create mode 100644 storops_test/unity/rest_data/moveSession/error_source_dest_not_exists.json create mode 100755 storops_test/unity/rest_data/moveSession/index.json create mode 100644 storops_test/unity/rest_data/moveSession/move_27.json create mode 100644 storops_test/unity/rest_data/moveSession/move_28.json create mode 100644 storops_test/unity/rest_data/moveSession/move_29.json create mode 100644 storops_test/unity/rest_data/moveSession/move_30.json create mode 100644 storops_test/unity/rest_data/moveSession/move_31.json create mode 100644 storops_test/unity/rest_data/moveSession/move_32.json create mode 100755 storops_test/unity/rest_data/moveSession/move_33.json create mode 100755 storops_test/unity/rest_data/moveSession/move_34.json create mode 100644 storops_test/unity/rest_data/moveSession/type.json create mode 100644 storops_test/unity/rest_data/pool/pool_5.json diff --git a/README.rst b/README.rst old mode 100644 new mode 100755 index 95c12783..b495455a --- a/README.rst +++ b/README.rst @@ -153,6 +153,7 @@ Feature List - list/create/delete iSCSI portals - list/create/delete link aggregations - list/create/delete Consistency Groups + - list/create/modify/cancel move sessions - list/create/delete metric real time query - list metrics query result - list disks @@ -172,6 +173,7 @@ Feature List - Persist historical metric data to csv files - Upload license - enable/disable LUN data reduction + - LUN migration - supported metrics - system - read/write/total IOPS diff --git a/storops/exception.py b/storops/exception.py index b4f8c03f..64a52c8f 100644 --- a/storops/exception.py +++ b/storops/exception.py @@ -1322,3 +1322,29 @@ class UnityCGMemberActionNotSupportError(UnityException): class UnityThinCloneNotAllowedError(UnityException): message = 'Thinclone not support on thick luns or snaps of thick lun.' + + +class UnityMigrationException(UnityException): + """Unity exceptions for Migration. + + Any migration related exceptions should inherit this exception.""" + pass + + +@rest_exception +class UnityMigrationSourceDestNotExistsError(UnityMigrationException): + error_code = 151036683 + + +@rest_exception +class UnityMigrationSourceIsThinCloneError(UnityMigrationException): + error_code = 151036719 + + +@rest_exception +class UnityMigrationSourceHasThinCloneError(UnityMigrationException): + error_code = 151036720 + + +class UnityMigrationTimeoutException(UnityMigrationException): + message = "Timeout when waiting for lun migration." diff --git a/storops/unity/enums.py b/storops/unity/enums.py index 4c284073..330a2d79 100644 --- a/storops/unity/enums.py +++ b/storops/unity/enums.py @@ -774,3 +774,29 @@ class InterfaceConfigModeEnum(UnityEnum): DISABLED = (0, "Disabled") STATIC = (1, "Static") AUTO = (2, "Auto") + + +class MoveSessionStateEnum(UnityEnum): + INITIALIZING = (0, "Initializing") + QUEUED = (1, "Queued") + RUNNING = (2, "Running") + FAILED = (3, "Failed") + CANCELLING = (4, "Cancelling") + CANCELLED = (5, "Cancelled") + COMPLETED = (6, "Completed") + + +class MoveSessionStatusEnum(UnityEnum): + INITIALIZING = (0, "OK") + POOL_OFFLINE = (1, "PoolOffline") + POOL_OUT_OF_SPACE = (2, "PoolOutOfSpace") + INTERNAL_ERROR = (3, "InternalError") + + +class MoveSessionPriorityEnum(UnityEnum): + IDLE = (0, "Idle") + LOW = (1, "Low") + BELOW_NORMAL = (2, "Below_Normal") + NORMAL = (3, "Normal") + ABOVE_NORMAL = (4, "Above_Normal") + HIGH = (5, "High") diff --git a/storops/unity/parser_configs.yaml b/storops/unity/parser_configs.yaml index b8d82d6b..e8214e0f 100644 --- a/storops/unity/parser_configs.yaml +++ b/storops/unity/parser_configs.yaml @@ -1883,3 +1883,29 @@ UnitySystemTierCapacity: - label: sizeFree - label: sizeTotal - label: sizeUsed + + +UnityMoveSession: + data_src: rest + name: moveSession + properties: + - label: id + is_index: True + - label: sourceStorageResource + converter: UnityStorageResource + - label: sourceMemberLun + converter: UnityLun + - label: destinationPool + converter: UnityPool + - label: health + converter: UnityHealth + - label: progressPct + - label: currentTransferRate + - label: avgTransferRate + - label: estTimeRemaining + - label: state + converter: MoveSessionStateEnum + - label: status + converter: MoveSessionStatusEnum + - label: priority + converter: MoveSessionPriorityEnum diff --git a/storops/unity/resource/lun.py b/storops/unity/resource/lun.py index 4bea8af9..1a00d54b 100755 --- a/storops/unity/resource/lun.py +++ b/storops/unity/resource/lun.py @@ -17,12 +17,17 @@ import logging +import retryz + +import storops.unity.resource.move_session import storops.unity.resource.pool from storops.exception import UnityBaseHasThinCloneError, \ UnityResourceNotFoundError, UnityCGMemberActionNotSupportError, \ - UnityThinCloneNotAllowedError + UnityThinCloneNotAllowedError, UnityMigrationSourceHasThinCloneError, \ + UnityMigrationTimeoutException from storops.lib.thinclone_helper import TCHelper from storops.lib.version import version +from storops.unity import enums from storops.unity.client import UnityClient from storops.unity.enums import TieringPolicyEnum, NodeEnum, \ HostLUNAccessEnum, ThinCloneActionEnum, StorageResourceTypeEnum @@ -353,6 +358,46 @@ def thin_clone(self, name, io_limit_policy=None, description=None): return TCHelper.thin_clone(self._cli, self, name, io_limit_policy, description) + def _is_move_session_supported(self, dest): + if self.is_thin_clone: + log.error('Not support move session, source lun is thin clone.') + return False + if self.is_data_reduction_enabled and not dest.is_all_flash: + log.error('Not support move session, source lun is compressed, ' + 'but destination pool is not all flash pool.') + return False + return True + + def migrate(self, dest, **kwargs): + if not self._is_move_session_supported(dest): + return False + + interval = kwargs.pop('interval', 5) + timeout = kwargs.pop('timeout', 1800) + + @retryz.retry(timeout=timeout, wait=interval, + on_return=lambda x: not isinstance(x, bool)) + def _do_check_move_session(move_session_id): + move_session = clz.get(self._cli, _id=move_session_id) + if move_session.state == enums.MoveSessionStateEnum.COMPLETED: + return True + if move_session.state in [enums.MoveSessionStateEnum.FAILED, + enums.MoveSessionStateEnum.CANCELLED]: + return False + + clz = storops.unity.resource.move_session.UnityMoveSession + is_compressed = self.is_data_reduction_enabled + + try: + move_session = clz.create(self._cli, self, dest, + is_data_reduction_applied=is_compressed) + return _do_check_move_session(move_session.id) + except UnityMigrationSourceHasThinCloneError: + log.error('Not support move session, source lun has thin clone.') + return False + except retryz.RetryTimeoutError: + raise UnityMigrationTimeoutException() + # `__getstate__` and `__setstate__` are used by Pickle. def __getstate__(self): return {'_id': self.get_id(), 'cli': self._cli} diff --git a/storops/unity/resource/move_session.py b/storops/unity/resource/move_session.py new file mode 100644 index 00000000..468dca17 --- /dev/null +++ b/storops/unity/resource/move_session.py @@ -0,0 +1,76 @@ +# Copyright (c) 2015 EMC Corporation. +# All Rights Reserved. +# +# 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 __future__ import unicode_literals + +import logging + +from storops.unity.resource import UnityResource, UnityResourceList + +__author__ = 'Yong Huang' + +LOG = logging.getLogger(__name__) + + +class UnityMoveSession(UnityResource): + @classmethod + def create(cls, cli, source_storage_resource, destination_pool, + source_member_lun=None, is_dest_thin=None, + is_data_reduction_applied=None, priority=None): + req_body = cls._compose_move_session_parameter( + cli, source_storage_resource=source_storage_resource, + destination_pool=destination_pool, + source_member_lun=source_member_lun, + is_dest_thin=is_dest_thin, + is_data_reduction_applied=is_data_reduction_applied, + priority=priority) + resp = cli.post(cls().resource_class, **req_body) + resp.raise_if_err() + move_session = cls.get(cli, resp.resource_id) + return move_session + + def modify(self, cli, priority=None): + req_body = self._compose_move_session_parameter(cli, + priority=priority) + resp = self.action('modify', **req_body) + resp.raise_if_err() + return resp + + def cancel(self): + resp = self.action('cancel') + resp.raise_if_err() + return resp + + @staticmethod + def _compose_move_session_parameter(cli, source_storage_resource=None, + destination_pool=None, + source_member_lun=None, + is_dest_thin=None, + is_data_reduction_applied=None, + priority=None): + req_body = cli.make_body( + sourceStorageResource=source_storage_resource, + destinationPool=destination_pool, + sourceMemberLun=source_member_lun, + isDestThin=is_dest_thin, + isDataReductionApplied=is_data_reduction_applied, + priority=priority) + return req_body + + +class UnityMoveSessionList(UnityResourceList): + @classmethod + def get_resource_class(cls): + return UnityMoveSession diff --git a/storops/unity/resource/system.py b/storops/unity/resource/system.py old mode 100755 new mode 100644 index fc8dd8be..404d6e06 --- a/storops/unity/resource/system.py +++ b/storops/unity/resource/system.py @@ -36,6 +36,7 @@ from storops.unity.resource.interface import UnityFileInterfaceList from storops.unity.resource.lun import UnityLunList from storops.unity.resource.metric import UnityMetricRealTimeQuery +from storops.unity.resource.move_session import UnityMoveSessionList from storops.unity.resource.nas_server import UnityNasServerList from storops.unity.resource.nfs_server import UnityNfsServerList from storops.unity.resource.nfs_share import UnityNfsShareList @@ -420,6 +421,9 @@ def get_system_capacity(self, _id=None, name=None, **filters): return self._get_unity_rsc( UnitySystemCapacityList, _id=_id, **filters) + def get_move_session(self, _id=None, **filters): + return self._get_unity_rsc(UnityMoveSessionList, _id=_id, **filters) + @property @instance_cache def info(self): diff --git a/storops_test/unity/resource/test_lun.py b/storops_test/unity/resource/test_lun.py index ca8bf756..137c9af6 100755 --- a/storops_test/unity/resource/test_lun.py +++ b/storops_test/unity/resource/test_lun.py @@ -26,7 +26,8 @@ UnityLunNameInUseError, UnityLunShrinkNotSupportedError, \ UnityNothingToModifyError, UnityPerfMonNotEnabledError, \ UnityThinCloneLimitExceededError, UnityCGMemberActionNotSupportError, \ - UnityThinCloneNotAllowedError + UnityThinCloneNotAllowedError, UnityMigrationTimeoutException, \ + UnityMigrationSourceDestNotExistsError from storops.unity.enums import HostLUNAccessEnum, NodeEnum, RaidTypeEnum from storops.unity.resource.disk import UnityDisk from storops.unity.resource.host import UnityBlockHostAccessList, UnityHost @@ -370,6 +371,69 @@ def test_update_hosts_no_change(self): r = lun.update_hosts(host_names=["10.244.209.90"]) assert_that(r, none()) + @patch_rest + def test_migrate_lun_success(self): + lun = UnityLun('sv_2', cli=t_rest()) + dest_pool = UnityPool('pool_4', cli=t_rest()) + r = lun.migrate(dest_pool) + assert_that(r, equal_to(True)) + + @patch_rest + def test_migrate_compressed_lun_success(self): + lun = UnityLun('sv_18', cli=t_rest()) + dest_pool = UnityPool('pool_5', cli=t_rest()) + r = lun.migrate(dest_pool) + assert_that(r, equal_to(True)) + + @patch_rest + def test_migrate_lun_source_is_thin_clone(self): + lun = UnityLun('sv_5606', cli=t_rest()) + dest_pool = UnityPool('pool_4', cli=t_rest()) + r = lun.migrate(dest_pool) + assert_that(r, equal_to(False)) + + @patch_rest + def test_migrate_lun_source_compressed_dest_not_flash(self): + lun = UnityLun('sv_18', cli=t_rest()) + dest_pool = UnityPool('pool_4', cli=t_rest()) + r = lun.migrate(dest_pool) + assert_that(r, equal_to(False)) + + @patch_rest + def test_migrate_lun_has_thin_clone(self): + lun = UnityLun('sv_2', cli=t_rest()) + dest_pool = UnityPool('pool_6', cli=t_rest()) + r = lun.migrate(dest_pool) + assert_that(r, equal_to(False)) + + @patch_rest + def test_migrate_lun_pool_does_not_exist(self): + lun = UnityLun('sv_2', cli=t_rest()) + dest_pool = UnityPool('pool_does_not_exist', cli=t_rest()) + assert_that(calling(lun.migrate).with_args(dest_pool), + raises(UnityMigrationSourceDestNotExistsError)) + + @patch_rest + def test_migrate_lun_failed(self): + lun = UnityLun('sv_2', cli=t_rest()) + dest_pool = UnityPool('pool_7', cli=t_rest()) + r = lun.migrate(dest_pool) + assert_that(r, equal_to(False)) + + @patch_rest + def test_migrate_lun_cancelled(self): + lun = UnityLun('sv_2', cli=t_rest()) + dest_pool = UnityPool('pool_8', cli=t_rest()) + r = lun.migrate(dest_pool) + assert_that(r, equal_to(False)) + + @patch_rest + def test_migrate_lun_timeout(self): + lun = UnityLun('sv_2', cli=t_rest()) + dest_pool = UnityPool('pool_5', cli=t_rest()) + assert_that(calling(lun.migrate).with_args(dest_pool, timeout=10), + raises(UnityMigrationTimeoutException)) + class UnityLunEnablePerfStatsTest(TestCase): @patch_rest diff --git a/storops_test/unity/resource/test_move_session.py b/storops_test/unity/resource/test_move_session.py new file mode 100644 index 00000000..49cae490 --- /dev/null +++ b/storops_test/unity/resource/test_move_session.py @@ -0,0 +1,83 @@ +# coding=utf-8 +# Copyright (c) 2015 EMC Corporation. +# All Rights Reserved. +# +# 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 __future__ import unicode_literals + +from unittest import TestCase + +from hamcrest import assert_that +from hamcrest import equal_to + +from storops.unity.enums import MoveSessionStateEnum, MoveSessionStatusEnum, \ + MoveSessionPriorityEnum +from storops.unity.resource.lun import UnityLun +from storops.unity.resource.move_session import UnityMoveSession +from storops.unity.resource.pool import UnityPool +from storops_test.unity.rest_mock import t_rest, patch_rest + +__author__ = 'Yong Huang' + + +class UnityMoveSessionTest(TestCase): + @patch_rest + def test_get_move_session_property(self): + move_session = UnityMoveSession(_id='move_27', cli=t_rest()) + assert_that(move_session.existed, equal_to(True)) + assert_that(move_session.id, equal_to('move_27')) + assert_that(move_session.state, + equal_to(MoveSessionStateEnum.COMPLETED)) + assert_that(move_session.status, + equal_to(MoveSessionStatusEnum.INITIALIZING)) + assert_that(move_session.priority, + equal_to(MoveSessionPriorityEnum.NORMAL)) + assert_that(move_session.progress_pct, equal_to(100)) + assert_that(move_session.current_transfer_rate, equal_to(0)) + assert_that(move_session.avg_transfer_rate, equal_to(5120)) + assert_that(move_session.est_time_remaining, equal_to("00:00:00.000")) + assert_that(move_session.source_storage_resource.id, equal_to("sv_18")) + assert_that(move_session.destination_pool.id, equal_to("pool_1")) + + @patch_rest + def test_create_move_session(self): + cli = t_rest() + lun = UnityLun.get(cli=cli, _id='sv_18') + dest_pool = UnityPool.get(cli=cli, _id='pool_5') + move_session = UnityMoveSession.create( + cli=cli, + source_storage_resource=lun, + destination_pool=dest_pool, + is_dest_thin=True, + is_data_reduction_applied=True, + priority=5) + assert_that(move_session.id, equal_to('move_32')) + assert_that(move_session.state, + equal_to(MoveSessionStateEnum.RUNNING)) + assert_that(move_session.status, + equal_to(MoveSessionStatusEnum.INITIALIZING)) + assert_that(move_session.priority, + equal_to(MoveSessionPriorityEnum.HIGH)) + + @patch_rest + def test_modify_move_session(self): + cli = t_rest() + move_session = UnityMoveSession(_id='move_32', cli=t_rest()) + move_session = move_session.modify(cli, priority=4) + assert_that(move_session.is_ok(), equal_to(True)) + + @patch_rest + def test_cancel_move_session(self): + move_session = UnityMoveSession(_id='move_32', cli=t_rest()) + resp = move_session.cancel() + assert_that(resp.is_ok(), equal_to(True)) diff --git a/storops_test/unity/resource/test_system.py b/storops_test/unity/resource/test_system.py index 9430525d..29284f75 100644 --- a/storops_test/unity/resource/test_system.py +++ b/storops_test/unity/resource/test_system.py @@ -46,6 +46,7 @@ from storops.unity.resource.lun import UnityLunList from storops.unity.resource.metric import UnityMetricQueryResultList, \ UnityMetricRealTimeQueryList +from storops.unity.resource.move_session import UnityMoveSessionList from storops.unity.resource.nas_server import UnityNasServer, \ UnityNasServerList from storops.unity.resource.nfs_server import UnityNfsServerList @@ -701,6 +702,13 @@ def test_get_system_capacity(self): assert_that(capacities, instance_of(UnitySystemCapacityList)) assert_that(len(capacities), equal_to(1)) + @patch_rest + def test_get_move_session(self): + unity = t_unity() + move_sessions = unity.get_move_session() + assert_that(move_sessions, instance_of(UnityMoveSessionList)) + assert_that(len(move_sessions), equal_to(1)) + class UnityDpeTest(TestCase): @patch_rest diff --git a/storops_test/unity/rest_data/moveSession/all.json b/storops_test/unity/rest_data/moveSession/all.json new file mode 100644 index 00000000..3bd941c8 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/all.json @@ -0,0 +1,42 @@ +{ + "@base": "https://10.245.101.39/api/types/moveSession/instances?fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status&per_page=2000&compact=true", + "updated": "2018-10-09T06:28:40.773Z", + "links": [ + { + "rel": "self", + "href": "&page=1" + } + ], + "entries": [ + { + "content": { + "id": "move_27", + "state": 6, + "status": 0, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 100, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_882" + }, + "destinationPool": { + "id": "pool_3" + } + } + } + ] +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/create_move_29.json b/storops_test/unity/rest_data/moveSession/create_move_29.json new file mode 100644 index 00000000..aad6d1fe --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/create_move_29.json @@ -0,0 +1,13 @@ +{ + "@base": "https://10.245.101.39/api/instances/moveSession", + "updated": "2018-10-09T07:29:22.182Z", + "links": [ + { + "rel": "self", + "href": "/move_29" + } + ], + "content": { + "id": "move_29" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/create_move_30.json b/storops_test/unity/rest_data/moveSession/create_move_30.json new file mode 100644 index 00000000..6bc9ae94 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/create_move_30.json @@ -0,0 +1,13 @@ +{ + "@base": "https://10.245.101.39/api/instances/moveSession", + "updated": "2018-10-09T07:29:22.182Z", + "links": [ + { + "rel": "self", + "href": "/move_30" + } + ], + "content": { + "id": "move_30" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/create_move_31.json b/storops_test/unity/rest_data/moveSession/create_move_31.json new file mode 100644 index 00000000..62af553b --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/create_move_31.json @@ -0,0 +1,13 @@ +{ + "@base": "https://10.245.101.39/api/instances/moveSession", + "updated": "2018-10-09T07:29:22.182Z", + "links": [ + { + "rel": "self", + "href": "/move_31" + } + ], + "content": { + "id": "move_31" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/create_move_32.json b/storops_test/unity/rest_data/moveSession/create_move_32.json new file mode 100644 index 00000000..74818998 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/create_move_32.json @@ -0,0 +1,13 @@ +{ + "@base": "https://10.245.101.39/api/instances/moveSession", + "updated": "2018-10-09T07:29:22.182Z", + "links": [ + { + "rel": "self", + "href": "/move_32" + } + ], + "content": { + "id": "move_32" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/create_move_33.json b/storops_test/unity/rest_data/moveSession/create_move_33.json new file mode 100755 index 00000000..dde4068d --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/create_move_33.json @@ -0,0 +1,13 @@ +{ + "@base": "https://10.245.101.39/api/instances/moveSession", + "updated": "2018-10-09T07:29:22.182Z", + "links": [ + { + "rel": "self", + "href": "/move_33" + } + ], + "content": { + "id": "move_33" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/create_move_34.json b/storops_test/unity/rest_data/moveSession/create_move_34.json new file mode 100755 index 00000000..7cd1ac63 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/create_move_34.json @@ -0,0 +1,13 @@ +{ + "@base": "https://10.245.101.39/api/instances/moveSession", + "updated": "2018-10-09T07:29:22.182Z", + "links": [ + { + "rel": "self", + "href": "/move_34" + } + ], + "content": { + "id": "move_34" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/empty.json b/storops_test/unity/rest_data/moveSession/empty.json new file mode 100644 index 00000000..e69de29b diff --git a/storops_test/unity/rest_data/moveSession/error_has_thin_clones.json b/storops_test/unity/rest_data/moveSession/error_has_thin_clones.json new file mode 100644 index 00000000..2b5b5f9f --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/error_has_thin_clones.json @@ -0,0 +1,12 @@ +{ + "error": { + "errorCode": 151036720, + "httpStatusCode": 409, + "messages": [ + { + "en-US": "The requested operation cannot be performed because the resource has Thin Clones. (Error Code:0x900a330)" + } + ], + "created": "2018-10-12T05:32:13.623Z" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/error_source_dest_not_exists.json b/storops_test/unity/rest_data/moveSession/error_source_dest_not_exists.json new file mode 100644 index 00000000..55a37acd --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/error_source_dest_not_exists.json @@ -0,0 +1,12 @@ +{ + "error": { + "errorCode": 151036683, + "httpStatusCode": 409, + "messages": [ + { + "en-US": "The requested operation cannot be performed because the source or dest specified does not exist. (Error Code:0x900a30b)" + } + ], + "created": "2018-10-12T05:48:56.052Z" + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/index.json b/storops_test/unity/rest_data/moveSession/index.json new file mode 100755 index 00000000..b8df36d1 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/index.json @@ -0,0 +1,165 @@ +{ + "indices": [ + { + "url": "/api/types/moveSession?compact=True&fields=attributes.description,attributes.displayValue,attributes.initialValue,attributes.name,attributes.type,description,documentation,name,type", + "response": "type.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "all.json" + }, + { + "url": "/api/instances/moveSession/move_27?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_27.json" + }, + { + "url": "/api/instances/moveSession/move_28?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_28.json" + }, + { + "url": "/api/instances/moveSession/move_29?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_29.json" + }, + { + "url": "/api/instances/moveSession/move_30?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_30.json" + }, + { + "url": "/api/instances/moveSession/move_31?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_31.json" + }, + { + "url": "/api/instances/moveSession/move_32?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_32.json" + }, + { + "url": "/api/instances/moveSession/move_33?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_33.json" + }, + { + "url": "/api/instances/moveSession/move_34?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "move_34.json" + }, + { + "url": "/api/instances/moveSession/move_31?compact=True&fields=avgTransferRate,currentTransferRate,destinationPool,estTimeRemaining,health,id,priority,progressPct,sourceMemberLun,sourceStorageResource,state,status", + "response": "error_has_thin_clones.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": false, + "destinationPool": { + "id": "pool_4" + }, + "sourceStorageResource": { + "id": "sv_2" + } + }, + "response": "create_move_29.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": true, + "destinationPool": { + "id": "pool_5" + }, + "sourceStorageResource": { + "id": "sv_18" + } + }, + "response": "create_move_30.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": false, + "destinationPool": { + "id": "pool_6" + }, + "sourceStorageResource": { + "id": "sv_2" + } + }, + "response": "error_has_thin_clones.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": false, + "destinationPool": { + "id": "pool_does_not_exist" + }, + "sourceStorageResource": { + "id": "sv_2" + } + }, + "response": "error_source_dest_not_exists.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": false, + "destinationPool": { + "id": "pool_5" + }, + "sourceStorageResource": { + "id": "sv_2" + } + }, + "response": "create_move_31.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": true, + "isDestThin": true, + "priority": 5, + "destinationPool": { + "id": "pool_5" + }, + "sourceStorageResource": { + "id": "sv_18" + } + }, + "response": "create_move_32.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": false, + "destinationPool": { + "id": "pool_7" + }, + "sourceStorageResource": { + "id": "sv_2" + } + }, + "response": "create_move_33.json" + }, + { + "url": "/api/types/moveSession/instances?compact=True", + "body": { + "isDataReductionApplied": false, + "destinationPool": { + "id": "pool_8" + }, + "sourceStorageResource": { + "id": "sv_2" + } + }, + "response": "create_move_34.json" + }, + { + "url": "/api/instances/moveSession/move_32/action/modify?compact=True", + "body": { + "priority": 4 + }, + "response": "empty.json" + }, + { + "url": "/api/instances/moveSession/move_32/action/cancel?compact=True", + "response": "empty.json" + } + ] +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_27.json b/storops_test/unity/rest_data/moveSession/move_27.json new file mode 100644 index 00000000..62fdcdaa --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_27.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_27", + "state": 6, + "status": 0, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 100, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_18" + }, + "destinationPool": { + "id": "pool_1" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_28.json b/storops_test/unity/rest_data/moveSession/move_28.json new file mode 100644 index 00000000..1a6cc5c2 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_28.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_28", + "state": 6, + "status": 0, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 100, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_2" + }, + "destinationPool": { + "id": "pool_3" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_29.json b/storops_test/unity/rest_data/moveSession/move_29.json new file mode 100644 index 00000000..12dff0bc --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_29.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_29", + "state": 6, + "status": 0, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 100, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_2" + }, + "destinationPool": { + "id": "pool_4" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_30.json b/storops_test/unity/rest_data/moveSession/move_30.json new file mode 100644 index 00000000..e1405377 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_30.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_30", + "state": 6, + "status": 0, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 100, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_18" + }, + "destinationPool": { + "id": "pool_5" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_31.json b/storops_test/unity/rest_data/moveSession/move_31.json new file mode 100644 index 00000000..810a9d7c --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_31.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_31", + "state": 2, + "status": 0, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 15, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_2" + }, + "destinationPool": { + "id": "pool_5" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_32.json b/storops_test/unity/rest_data/moveSession/move_32.json new file mode 100644 index 00000000..8c0a2f56 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_32.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_32", + "state": 2, + "status": 0, + "priority": 5, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 52, + "currentTransferRate": 2090, + "avgTransferRate": 1562, + "estTimeRemaining": "00:00:21.000", + "sourceStorageResource": { + "id": "sv_18" + }, + "destinationPool": { + "id": "pool_5" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_33.json b/storops_test/unity/rest_data/moveSession/move_33.json new file mode 100755 index 00000000..f19797ed --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_33.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_33", + "state": 3, + "status": 3, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 0, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_2" + }, + "destinationPool": { + "id": "pool_7" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/move_34.json b/storops_test/unity/rest_data/moveSession/move_34.json new file mode 100755 index 00000000..fedd8238 --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/move_34.json @@ -0,0 +1,30 @@ +{ + "content": { + "id": "move_34", + "state": 5, + "status": 0, + "priority": 3, + "health": { + "value": 5, + "descriptionIds": [ + "0" + ], + "descriptions": [ + "The specified storage resource move session is operating normally." + ], + "resolutionIds": [ + "0" + ] + }, + "progressPct": 30, + "currentTransferRate": 0, + "avgTransferRate": 5120, + "estTimeRemaining": "00:00:00.000", + "sourceStorageResource": { + "id": "sv_2" + }, + "destinationPool": { + "id": "pool_8" + } + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/moveSession/type.json b/storops_test/unity/rest_data/moveSession/type.json new file mode 100644 index 00000000..05b87c9c --- /dev/null +++ b/storops_test/unity/rest_data/moveSession/type.json @@ -0,0 +1,81 @@ +{ + "content": { + "name": "moveSession", + "description": "Information about movesession.

A customer environment is often ever-changing, and as a result the ability to deliver business continuity and flexibility is paramount. The new local LUN migration feature address this concern, by adding the ability to move LUNs and Consistency Groups between Pools on a system. Local LUN migration can be used to rebalance storage resources across Pools when customer activity changes and an individual Pool's usage becomes oversaturated. Another use case for local LUN migration is to provide LUNs with a destination when a Pool is to be decommissioned. By leveraging Unity's Transparent Data Transfer (TDX) engine, host access remains fully online during the migration session.", + "documentation": "https://10.245.101.39/apidocs/classes/moveSession.html", + "attributes": [ + { + "name": "destinationPool", + "type": "pool", + "description": "Destination pool for the move.", + "displayValue": "destinationPool" + }, + { + "name": "state", + "type": "MoveSessionStateEnum", + "description": "The current state of the session. The session state represents the lifecycle of a session.", + "displayValue": "state" + }, + { + "name": "currentTransferRate", + "type": "Integer", + "description": "The current transfer rate of the session in MB/sec.", + "displayValue": "currentTransferRate" + }, + { + "name": "priority", + "type": "MoveSessionPriorityEnum", + "description": "The priority of this storageResource move relative to other moves.", + "displayValue": "priority" + }, + { + "name": "progressPct", + "type": "Integer", + "description": "The progress of the session expressed as a percentage.", + "displayValue": "progressPct" + }, + { + "name": "sourceStorageResource", + "type": "storageResource", + "description": "Storage resource to be moved.", + "displayValue": "sourceStorageResource" + }, + { + "name": "id", + "type": "String", + "description": "Unique identifier of the session.", + "displayValue": "id" + }, + { + "name": "health", + "type": "health", + "description": "The health of the session.", + "displayValue": "health" + }, + { + "name": "sourceMemberLun", + "type": "lun", + "description": "The LUN being moved when the corresponding storageResource isn't specific enough, i.e. a Consistency Group member LUN or LUN VMFS Datastore.", + "displayValue": "sourceMemberLun" + }, + { + "name": "avgTransferRate", + "type": "Integer", + "description": "The average transfer rate of the session in MB/sec.", + "displayValue": "avgTransferRate" + }, + { + "name": "status", + "type": "MoveSessionStatusEnum", + "description": "The current session status of the TDX session.", + "displayValue": "status" + }, + { + "name": "estTimeRemaining", + "type": "DateTime", + "description": "The estimated time remaining based on the current transfer rate.", + "displayValue": "estTimeRemaining" + } + ] + } +} \ No newline at end of file diff --git a/storops_test/unity/rest_data/pool/index.json b/storops_test/unity/rest_data/pool/index.json index 3deb80e7..4527cc45 100644 --- a/storops_test/unity/rest_data/pool/index.json +++ b/storops_test/unity/rest_data/pool/index.json @@ -8,6 +8,10 @@ "url": "/api/instances/pool/pool_1?compact=True&fields=alertThreshold,creationTime,description,harvestState,health,id,instanceId,isAllFlash,isEmpty,isFASTCacheEnabled,isHarvestEnabled,isSnapHarvestEnabled,metadataSizeSubscribed,metadataSizeUsed,name,objectId,operationalStatus,poolFastVP,poolSpaceHarvestHighThreshold,poolSpaceHarvestLowThreshold,raidType,rebalanceProgress,sizeFree,sizeSubscribed,sizeTotal,sizeUsed,snapSizeSubscribed,snapSizeUsed,snapSpaceHarvestHighThreshold,snapSpaceHarvestLowThreshold,storageResourceType,tiers,type", "response": "pool_1.json" }, + { + "url": "/api/instances/pool/pool_5?compact=True&fields=alertThreshold,creationTime,description,harvestState,health,id,instanceId,isAllFlash,isEmpty,isFASTCacheEnabled,isHarvestEnabled,isSnapHarvestEnabled,metadataSizeSubscribed,metadataSizeUsed,name,objectId,operationalStatus,poolFastVP,poolSpaceHarvestHighThreshold,poolSpaceHarvestLowThreshold,raidType,rebalanceProgress,sizeFree,sizeSubscribed,sizeTotal,sizeUsed,snapSizeSubscribed,snapSizeUsed,snapSpaceHarvestHighThreshold,snapSpaceHarvestLowThreshold,storageResourceType,tiers,type", + "response": "pool_5.json" + }, { "url": "/api/types/pool/instances?compact=True&fields=alertThreshold,creationTime,description,harvestState,health,id,instanceId,isAllFlash,isEmpty,isFASTCacheEnabled,isHarvestEnabled,isSnapHarvestEnabled,metadataSizeSubscribed,metadataSizeUsed,name,objectId,operationalStatus,poolFastVP,poolSpaceHarvestHighThreshold,poolSpaceHarvestLowThreshold,raidType,rebalanceProgress,sizeFree,sizeSubscribed,sizeTotal,sizeUsed,snapSizeSubscribed,snapSizeUsed,snapSpaceHarvestHighThreshold,snapSpaceHarvestLowThreshold,storageResourceType,tiers,type", "response": "all.json" diff --git a/storops_test/unity/rest_data/pool/pool_5.json b/storops_test/unity/rest_data/pool/pool_5.json new file mode 100644 index 00000000..6f74e041 --- /dev/null +++ b/storops_test/unity/rest_data/pool/pool_5.json @@ -0,0 +1,101 @@ +{ + "content": { + "id": "pool_4", + "operationalStatus": [ + 2 + ], + "raidType": 1, + "harvestState": 0, + "type": 1, + "instanceId": "root/emc:EMC_UEM_MappedStoragePoolLeaf%InstanceID=pool_4", + "objectId": 12884901893, + "health": { + "value": 5, + "descriptionIds": [ + "ALRT_COMPONENT_OK" + ], + "descriptions": [ + "The component is operating normally. No action is required." + ] + }, + "name": "test_pool", + "description": "Unity test pool.", + "sizeFree": 1903207383040, + "sizeTotal": 1903207383040, + "sizeUsed": 0, + "sizeSubscribed": 0, + "alertThreshold": 70, + "isFASTCacheEnabled": false, + "tiers": [ + { + "tierType": 10, + "raidType": 0, + "instanceId": "root/emc:EMC_UEM_StoragePoolTierLeaf%InstanceID=7", + "sizeTotal": 0, + "sizeUsed": 0, + "sizeFree": 0, + "sizeMovingDown": 0, + "sizeMovingUp": 0, + "sizeMovingWithin": 0, + "name": "Extreme Performance", + "diskCount": 0 + }, + { + "tierType": 20, + "stripeWidth": 5, + "raidType": 1, + "instanceId": "root/emc:EMC_UEM_StoragePoolTierLeaf%InstanceID=9", + "sizeTotal": 1903207383040, + "sizeUsed": 0, + "sizeFree": 1903207383040, + "sizeMovingDown": 0, + "sizeMovingUp": 0, + "sizeMovingWithin": 0, + "name": "Performance", + "diskCount": 5, + "poolUnits": [ + { + "id": "rg_4" + } + ] + }, + { + "tierType": 30, + "raidType": 0, + "instanceId": "root/emc:EMC_UEM_StoragePoolTierLeaf%InstanceID=8", + "sizeTotal": 0, + "sizeUsed": 0, + "sizeFree": 0, + "sizeMovingDown": 0, + "sizeMovingUp": 0, + "sizeMovingWithin": 0, + "name": "Capacity", + "diskCount": 0 + } + ], + "creationTime": "2017-05-04T05:42:46.000Z", + "isEmpty": true, + "poolFastVP": { + "status": 4, + "relocationRate": 2, + "type": 2, + "isScheduleEnabled": true, + "relocationDurationEstimate": "00:00:00.000", + "sizeMovingDown": 0, + "sizeMovingUp": 0, + "sizeMovingWithin": 0, + "percentComplete": 0 + }, + "isHarvestEnabled": true, + "isSnapHarvestEnabled": false, + "poolSpaceHarvestHighThreshold": 95, + "poolSpaceHarvestLowThreshold": 85, + "snapSpaceHarvestHighThreshold": 25, + "snapSpaceHarvestLowThreshold": 20, + "metadataSizeSubscribed": 0, + "snapSizeSubscribed": 0, + "metadataSizeUsed": 0, + "snapSizeUsed": 0, + "isAllFlash": true + } +} \ No newline at end of file