Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GH-241] Unity: adds lun migration support #243

Merged
merged 1 commit into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.rst
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
26 changes: 26 additions & 0 deletions storops/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."
26 changes: 26 additions & 0 deletions storops/unity/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
26 changes: 26 additions & 0 deletions storops/unity/parser_configs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
47 changes: 46 additions & 1 deletion storops/unity/resource/lun.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is with FAILED, CANCELLED or COMPLETED, it is completed too. We could return quickly here, or we need to wait until timeout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, will check FAILED and CANCELLED states.

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the lun is in cg?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If lun in cg, REST will return 409 source or dest does not exist error, storops will raise UnityMigrationSourceDestNotExistsError exception.

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}
Expand Down
76 changes: 76 additions & 0 deletions storops/unity/resource/move_session.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions storops/unity/resource/system.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
66 changes: 65 additions & 1 deletion storops_test/unity/resource/test_lun.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading