Skip to content

Commit

Permalink
[GH-28] Unity lun snap creation and deletion
Browse files Browse the repository at this point in the history
Add support for Unity snap attach/detach.

Other changes:
* Pass name to LUN modify method.
* Add a LUN name modify shortcut.
* Support dummy LUN when attach LUN or Snap.
  • Loading branch information
Murray-LIANG authored and Cedric Zhuang committed Oct 24, 2016
1 parent a105685 commit 9e576fb
Show file tree
Hide file tree
Showing 31 changed files with 1,038 additions and 207 deletions.
30 changes: 29 additions & 1 deletion storops/exception.py
Expand Up @@ -278,6 +278,10 @@ class UnityAttachAluError(UnityException):
pass


class UnityAttachError(UnityException):
pass


class UnityHostNameInUseError(UnityException):
pass

Expand All @@ -293,10 +297,20 @@ class UnityAttachAluExceedLimitError(UnityAttachAluError):
message = "Numbers of LUNs exceeds system limit"


@rest_exception
class UnityAttachExceedLimitError(UnityAttachError):
error_code = 108008750
message = "Numbers of LUNs or Snaps exceeds system limit"


class UnityAluAlreadyAttachedError(UnityAttachAluError):
message = 'Requested LUN has already been added to this host'


class UnityResourceAlreadyAttachedError(UnityAttachError):
message = 'Requested LUN or Snap has already been attached to this host'


@rest_exception
class UnityResourceNotFoundError(UnityException):
error_code = 131149829
Expand Down Expand Up @@ -361,11 +375,25 @@ class UnityCreateSnapError(UnityException):
pass


class UnitySnapException(UnityException):
pass


@rest_exception
class UnitySnapNameInUseError(UnityException):
class UnitySnapNameInUseError(UnitySnapException):
error_code = (1903001605, 1903132675)


@rest_exception
class UnitySnapAlreadyPromotedException(UnitySnapException):
error_code = 100666332


@rest_exception
class UnityDeleteAttachedSnapError(UnityException):
error_code = 1903001603


@rest_exception
class UnityShareOnCkptSnapError(UnityException):
error_code = 1903001786
Expand Down
3 changes: 2 additions & 1 deletion storops/unity/client.py
Expand Up @@ -67,7 +67,8 @@ def _get_non_list_value(k, v):

if the_filter:
items = []
for key, value in the_filter.items():
for key in sorted(the_filter.keys()):
value = the_filter[key]
if value is None:
continue
if isinstance(value, (list, tuple, UnityEnumList)):
Expand Down
5 changes: 5 additions & 0 deletions storops/unity/enums.py
Expand Up @@ -217,6 +217,11 @@ class SnapStateEnum(UnityEnum):
DESTROYING = (9, 'Destroying')


class SnapAccessLevelEnum(UnityEnum):
READ_ONLY = (0, 'Read Only')
READ_WRITE = (1, 'Read Write')


class FilesystemSnapAccessTypeEnum(UnityEnum):
CHECKPOINT = (1, 'Checkpoint')
PROTOCOL = (2, 'Protocol')
Expand Down
14 changes: 13 additions & 1 deletion storops/unity/parser_configs.yaml
Expand Up @@ -487,6 +487,8 @@ UnitySnap:
- label: size
- label: ioLimitPolicy
converter: UnityIoLimitPolicy
- label: hostAccess
converter: UnitySnapHostAccessList


UnitySnapSchedule:
Expand Down Expand Up @@ -850,6 +852,16 @@ UnityBlockHostAccess:
converter: HostLUNAccessEnum


UnitySnapHostAccess:
data_src: rest
name: snapHostAccess
properties:
- label: host
converter: UnityHost
- label: allowedAccess
converter: SnapAccessLevelEnum


UnityHost:
data_src: rest
name: host
Expand Down Expand Up @@ -1355,4 +1367,4 @@ UnityAclUser:
- label: id
- label: userName
- label: domainName
- label: sid
- label: sid
121 changes: 104 additions & 17 deletions storops/unity/resource/host.py
Expand Up @@ -42,8 +42,20 @@ def get_resource_class(cls):
return UnityBlockHostAccess


class UnityHost(UnityResource):
class UnitySnapHostAccess(UnityAttributeResource):
pass


class UnitySnapHostAccessList(UnityResourceList):
@classmethod
def get_resource_class(cls):
return UnitySnapHostAccess


DUMMY_LUN_NAME = 'storops_dummy_lun'


class UnityHost(UnityResource):
@classmethod
def get_nested_properties(cls):
return (
Expand Down Expand Up @@ -92,19 +104,84 @@ def get_host(cls, cli, _id, force_create=False):
ret = cls.get(cli=cli, _id=_id)
return ret

def _get_host_lun(self, lun=None):
if lun:
ret = UnityHostLunList.get(self._cli, host=self.id, lun=lun.id)
else:
ret = UnityHostLunList.get(self._cli, host=self.id)
log.debug('Found {} host luns attached to this host'
.format(len(ret)))
def _get_host_lun(self, lun=None, snap=None, hlu=None):
lun_id = lun.id if lun is not None else None
snap_id = snap.id if snap is not None else None
hlu_no = hlu if hlu is not None else None

ret = UnityHostLunList.get(self._cli, host=self.id,
lun=lun_id, snap=snap_id, hlu=hlu_no)

if len(ret) != 1:
msg = ('Found {num} host luns attached to this host. '
'Filter: lun={lun_id}, snap={snap_id}, '
'hlu={hlu_no}.').format(num=len(ret), lun_id=lun_id,
snap_id=snap_id, hlu_no=hlu_no)
log.debug(msg)

# Need filter again for the hlu of LUN, excluding the hlu of snap.
if lun_id and snap_id is None:
ret = list(filter(lambda x: x.snap is None, ret))

return ret

def detach(self, lun_or_snap):
return lun_or_snap.detach_from(self)

def detach_alu(self, lun):
log.warn('Method detach_alu is deprecated. Use detach instead.')
return lun.detach_from(self)

def _create_attach_dummy_lun(self):
import storops.unity.resource.lun as lun_module
import storops.unity.resource.pool as pool_module
lun_list = lun_module.UnityLunList.get(self._cli, name=DUMMY_LUN_NAME)
if not lun_list:
try:
pool_list = pool_module.UnityPoolList.get(self._cli)
dummy_lun = pool_list[0].create_lun(lun_name=DUMMY_LUN_NAME)
except Exception as err:
# Ignore all errors of creating dummy lun.
log.warn('Failed to create dummy lun. Message: {}'.format(err))
dummy_lun = None
else:
dummy_lun = lun_list[0]

if dummy_lun:
try:
dummy_lun.attach_to(self)
except ex.UnityResourceAlreadyAttachedError:
pass
except Exception as err:
# Ignore all errors of attaching dummy lun.
log.warn('Failed to attach dummy lun. Message: {}'.format(err))

def attach(self, lun_or_snap, skip_hlu_0=False):
if self.has_hlu(lun_or_snap):
raise ex.UnityResourceAlreadyAttachedError()

if skip_hlu_0:
log.debug('Try to skip the hlu number 0 by attaching a dummy lun.')
hlu_0 = self._get_host_lun(hlu=0)
if not hlu_0:
self._create_attach_dummy_lun()

try:
lun_or_snap.attach_to(self)
self.update()
hlu = self.get_hlu(lun_or_snap)
except ex.UnityAttachExceedLimitError:
# The number of luns exceeds system limit
raise
except:
# other attach error, remove this lun if already attached
self.detach(lun_or_snap)
raise

return hlu

def attach_alu(self, lun):
log.warn('Method attach_alu is deprecated. Use attach instead.')
if self.has_alu(lun):
raise ex.UnityAluAlreadyAttachedError()

Expand All @@ -115,25 +192,36 @@ def attach_alu(self, lun):
except ex.UnityAttachAluExceedLimitError:
# The number of luns exceeds system limit
raise
except ex.UnityException:
except:
# other attach error, remove this lun if already attached
self.detach_alu(lun)
raise ex.UnityAttachAluError()
raise

return hlu

def has_hlu(self, lun_or_snap):
hlu = self.get_hlu(lun_or_snap)
return hlu is not None

def has_alu(self, lun):
alu = self.get_hlu(lun=lun)
log.warn('Method has_alu is deprecated. Use has_hlu instead.')
alu = self.get_hlu(lun)
if alu is None:
return False
else:
return True

def get_hlu(self, lun):
which = self._get_host_lun(lun=lun)
def get_hlu(self, resource):
import storops.unity.resource.lun as lun_module
import storops.unity.resource.snap as snap_module
which = None
if isinstance(resource, lun_module.UnityLun):
which = self._get_host_lun(lun=resource)
elif isinstance(resource, snap_module.UnitySnap):
which = self._get_host_lun(snap=resource)
if not which:
log.debug('lun {} is not attached to host {}'
.format(lun.name, self.name))
log.debug('Resource(LUN or Snap) {} is not attached to host {}'
.format(resource.name, self.name))
return None
return which[0].hlu

Expand All @@ -156,8 +244,7 @@ def add_initiator(self, uid, force_create=True, **kwargs):
self, uid_type, **kwargs)
else:
raise ex.UnityHostInitiatorNotFoundError(
'name {} not found under host {}.'
.format(uid, self.name))
'name {} not found under host {}.'.format(uid, self.name))
else:
initiator = initiators.first_item
log.debug('initiator {} is existed in unity system.'.format(uid))
Expand Down
46 changes: 37 additions & 9 deletions storops/unity/resource/lun.py
Expand Up @@ -22,6 +22,7 @@
from storops.unity.enums import TieringPolicyEnum, NodeEnum, HostLUNAccessEnum
from storops.unity.resource.storage_resource import UnityStorageResource
from storops.exception import UnityResourceNotFoundError
from storops.unity.resource.snap import UnitySnap, UnitySnapList
from storops.unity.resource.sp import UnityStorageProcessor

__author__ = 'Jay Xu'
Expand All @@ -48,6 +49,18 @@ def create(cls, cli, name, pool, size, sp=None, host_access=None,
sr = UnityStorageResource(_id=resp.resource_id, cli=cli)
return sr.luns[0]

@property
def name(self):
if hasattr(self, '_name') and self._name is not None:
name = self._name
else:
name = self._get_value_by_key('name')
return name

@name.setter
def name(self, new_name):
self.modify(name=new_name)

@staticmethod
def _compose_lun_parameter(cli, **kwargs):
sp = kwargs.get('sp')
Expand Down Expand Up @@ -77,19 +90,22 @@ def _compose_lun_parameter(cli, **kwargs):
)

# Empty host access can be used to wipe the host_access
if 'lunParameters' not in req_body:
req_body['lunParameters'] = {}
req_body['lunParameters']['hostAccess'] = cli.make_body(
host_access_value = cli.make_body(
kwargs.get('host_access'), allow_empty=True)

if host_access_value is not None:
if 'lunParameters' not in req_body:
req_body['lunParameters'] = {}
req_body['lunParameters']['hostAccess'] = host_access_value

return req_body

def modify(self, name=None, size=None, host_access=None,
description=None, is_thin=None, sp=None, iolimit_policy=None,
is_repl_dst=None, tiering_policy=None, snap_schedule=None):

req_body = self._compose_lun_parameter(
self._cli, name=None, pool=None, size=size, sp=sp,
self._cli, name=name, pool=None, size=size, sp=sp,
host_access=host_access, is_thin=is_thin, description=description,
iolimit_policy=iolimit_policy, is_repl_dst=is_repl_dst,
tiering_policy=tiering_policy, snap_schedule=snap_schedule
Expand Down Expand Up @@ -117,7 +133,7 @@ def attach_to(self, host, access_mask=HostLUNAccessEnum.PRODUCTION):
# If this lun has been attached to other host, don't overwrite it.
if self.host_access:
host_access += [{'host': item.host,
'accessMask': item.access_mask} for item
'accessMask': item.access_mask} for item
in self.host_access if host.id != item.host.id]

resp = self.modify(host_access=host_access)
Expand All @@ -128,14 +144,26 @@ def detach_from(self, host):
if self.host_access is None or not host:
return None

new_access = [{
'host': item.host,
'accessMask': item.access_mask} for item
in self.host_access if host.id != item.host.id]
new_access = [{'host': item.host,
'accessMask': item.access_mask} for item
in self.host_access if host.id != item.host.id]
resp = self.modify(host_access=new_access)
resp.raise_if_err()
return resp

def create_snap(self, name=None, description=None, is_auto_delete=None,
retention_duration=None):
return UnitySnap.create(self._cli, self.storage_resource,
name=name, description=description,
is_auto_delete=is_auto_delete,
retention_duration=retention_duration,
is_read_only=None, fs_access_type=None)

@property
def snapshots(self):
return UnitySnapList(cli=self._cli,
storage_resource=self.storage_resource)


class UnityLunList(UnityResourceList):
@classmethod
Expand Down

0 comments on commit 9e576fb

Please sign in to comment.