Skip to content

Commit

Permalink
Release 0.2.12.
Browse files Browse the repository at this point in the history
Merge the bugfix from develop.
  • Loading branch information
Cedric Zhuang committed Jun 20, 2016
2 parents f059358 + 8829d06 commit 17e2c5a
Show file tree
Hide file tree
Showing 33 changed files with 838 additions and 101 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ StorOps: The Python Library for VNX & Unity
.. image:: https://img.shields.io/pypi/v/storops.svg
:target: https://pypi.python.org/pypi/storops

VERSION: 0.2.11
VERSION: 0.2.12

A minimalist Python library to manage VNX/Unity systems.
This document lies in the source code and go with the release.
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ requests!=2.9.0,>=2.8.1
retryz>=0.1.5
cachez>=0.1.0
six>=1.9.0
pywbemReq>=0.0.1
pywbemReq>=0.0.3
bitmath>=1.3.0
8 changes: 6 additions & 2 deletions storops/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,10 @@ class NoIndexException(StoropsException):


class UnityNameNotUniqueError(UnityException):
pass
def __init__(self, message="multiple objects found", objects=None):
super(UnityNameNotUniqueError, self).__init__()
self.message = message
self.objects = objects


class UnityCifsServiceNotEnabledError(UnityException):
Expand Down Expand Up @@ -287,8 +290,9 @@ class UnityHostNameInUseError(UnityException):
pass


@rest_exception
class UnityLunNameInUseError(UnityException):
pass
error_code = 108007744


@rest_exception
Expand Down
22 changes: 17 additions & 5 deletions storops/unity/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import storops.unity.resource.type_resource
from storops.exception import UnityException
from storops.lib.common import instance_cache, EnumList
from storops.unity.enums import UnityEnum, SmisReturnValueEnum
from storops.unity.enums import UnityEnum, UnityEnumList, SmisReturnValueEnum
from storops.unity.resource import UnityResource, UnityResourceList
from storops.unity.resp import RestResponse

Expand Down Expand Up @@ -55,15 +55,26 @@ def get_all(self, type_name, fields=None, the_filter=None):

@classmethod
def dict_to_filter_string(cls, the_filter):
def _get_non_list_value(k, v):
if isinstance(v, six.string_types):
ret = '{} eq "{}"'.format(k, v)
elif isinstance(v, UnityEnum):
ret = '{} eq {}'.format(k, v.value[0])
else:
ret = '{} eq {}'.format(k, v)
return ret

if the_filter:
items = []
for k, v in the_filter.items():
if v is None:
continue
if isinstance(v, six.string_types):
items.append('{} eq "{}"'.format(k, v))
if isinstance(v, (list, tuple, UnityEnumList)):
list_ret = ' or '.join([_get_non_list_value(k, item)
for item in v])
items.append(list_ret)
else:
items.append('{} eq {}'.format(k, v))
items.append(_get_non_list_value(k, v))
if items:
ret = ' and '.join(items)
else:
Expand Down Expand Up @@ -186,7 +197,8 @@ def make_body(cls, value=None, allow_empty=False, **kwargs):
v = cls.make_body(v, allow_empty=allow_empty)
if v is not None and (allow_empty or not cls._is_empty(v)):
ret[k] = v
elif isinstance(value, (list, tuple, UnityResourceList)):
elif isinstance(value, (list, tuple, UnityResourceList,
UnityEnumList)):
ret = [cls.make_body(v, allow_empty=allow_empty) for v in value]
elif isinstance(value, UnityEnum):
ret = value.index
Expand Down
2 changes: 1 addition & 1 deletion storops/unity/parser_configs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,7 @@ UnityCapabilityProfile:
- label: health
converter: UnityHealth
- label: virtualVolumes
converter: VirtualVolumeList
converter: UnityVirtualVolumeList


UnityHostVvolDatastore:
Expand Down
13 changes: 11 additions & 2 deletions storops/unity/resource/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ def _get_unity_rsc(self, clz, _id=None, **filters):
'{}:{} not found.'.format(clz_name, name))
elif len(ret) > 1:
raise UnityNameNotUniqueError(
'multiple {} with name {} found.'.format(clz_name, name))
'multiple {} with name {} found.'.format(clz_name, name),
# throw out the found multiple objects for later analysis
objects=ret)
else:
ret = ret[0]
return ret
Expand Down Expand Up @@ -196,11 +198,18 @@ def _get_raw_resource(self):
the_filter = {}
_parser = self._get_parser()
for k, v in self._rsc_filter.items():
label = _parser.get_property_label(k)
# if k is like host.id for "host.id eq XXX" rest filter
keys = k.split('.')
# ingore the left string after '.' since both are ok
# 'host=<host_id> or {'host.id': <host_id>}'
label = _parser.get_property_label(keys[0])
if not label:
raise ValueError(
'"{}" is not a valid property of {}.'.format(
k, self.get_resource_class().__name__))
# support {'host.id': <host_id>}
if len(keys) == 2:
label = k
the_filter[label] = v
return self._cli.get_all(self.resource_class, the_filter=the_filter)

Expand Down
76 changes: 30 additions & 46 deletions storops/unity/resource/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@

import six

from retryz import retry
from storops import exception as ex
from storops.lib import converter
from storops.lib.common import instance_cache
from storops.unity.enums import HostTypeEnum, HostInitiatorTypeEnum
from storops.unity.resource import UnityResource, UnityResourceList, \
UnityAttributeResource
Expand Down Expand Up @@ -84,56 +82,45 @@ def get_host(cls, cli, _id, force_create=False):
ret = cls.get(cli=cli, _id=_id)
return ret

@property
@instance_cache
def lun_ids(self):
if not self.host_luns:
return []
host_luns = filter(lambda x: x.lun, self.host_luns)
return map(lambda x: x.lun.id, host_luns)
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)))
return ret

def detach_alu(self, lun):
return lun.detach_from(self)

def attach_alu(self, lun, max_retires=3):
def _update():
self.update()
# if found attach, just exit the retry
if self.has_alu(lun):
raise ex.UnityAluAlreadyAttachedError()

@retry(on_error=lambda e: not isinstance(
e, ex.UnityAluAlreadyAttachedError),
on_retry=_update, limit=max_retires)
def _do(lun):
try:
lun.attach_to(self)
self.update()
alu = self.get_hlu(lun)
except ex.UnityAttachAluExceedLimitError:
# The number of luns exceeds system limit
raise
except ex.UnityException:
# other attach error, remove this lun if already attached
self.detach_alu(lun)
raise ex.UnityAttachAluError()
return alu

def attach_alu(self, lun):
if self.has_alu(lun):
raise ex.UnityAluAlreadyAttachedError()
else:
ret = _do(lun)

return ret
try:
lun.attach_to(self)
self.update()
hlu = self.get_hlu(lun)
except ex.UnityAttachAluExceedLimitError:
# The number of luns exceeds system limit
raise
except ex.UnityException:
# other attach error, remove this lun if already attached
self.detach_alu(lun)
raise ex.UnityAttachAluError()

return hlu

def has_alu(self, lun):
return lun.id in self.lun_ids
alu = self.get_hlu(lun=lun)
if alu is None:
return False
else:
return True

def get_hlu(self, lun):
if not self.host_luns:
return None
host_luns = [item for item in self.host_luns if item.lun]
which = [item for item in host_luns if item.lun.id == lun.id]
which = self._get_host_lun(lun=lun)
if not which:
log.debug('lun {} is not attached to host {}'
.format(lun.name, self.name))
Expand All @@ -144,11 +131,7 @@ def add_initiator(self, uid, force_create=True, **kwargs):
initiators = UnityHostInitiatorList.get(cli=self._cli,
initiator_id=uid)

# Even if no initiators are found, the initiators object still contain
# one fake initiator.
initiator = initiators.first_item
if not initiator.existed:

if not initiators:
# Set the ISCSI or FC type
if re.match("(\w{2}:){15}\w{2}", uid, re.I):
uid_type = HostInitiatorTypeEnum.FC
Expand All @@ -166,6 +149,7 @@ def add_initiator(self, uid, force_create=True, **kwargs):
'name {} not found under host {}.'
.format(uid, self.name))
else:
initiator = initiators.first_item
log.debug('initiator {} is existed in unity system.'.format(uid))

initiator.modify(self)
Expand Down
22 changes: 8 additions & 14 deletions storops/unity/resource/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
import logging
import bitmath

from storops import exception as ex
from storops.unity.resource import UnityResource, \
UnityAttributeResource, UnityResourceList
import storops.unity.resource.filesystem
from storops.unity.resource.lun import UnityLun, UnityLunList
from storops.unity.resource.lun import UnityLun

__author__ = 'Jay Xu'

Expand All @@ -46,18 +45,13 @@ def create_lun(self, lun_name=None, size_gb=1, sp=None, host_access=None,
is_thin=None, description=None, tiering_policy=None,
is_repl_dst=None, snap_schedule=None, iolimit_policy=None):
size = int(bitmath.GiB(size_gb).to_Byte().value)
lun = UnityLunList.get(self._cli, name=lun_name).first_item
if lun and lun.existed:
raise ex.UnityLunNameInUseError()
else:
lun = UnityLun.create(self._cli, lun_name, self, size, sp=sp,
host_access=host_access, is_thin=is_thin,
description=description,
is_repl_dst=is_repl_dst,
tiering_policy=tiering_policy,
snap_schedule=snap_schedule,
iolimit_policy=iolimit_policy)
return lun
return UnityLun.create(self._cli, lun_name, self, size, sp=sp,
host_access=host_access, is_thin=is_thin,
description=description,
is_repl_dst=is_repl_dst,
tiering_policy=tiering_policy,
snap_schedule=snap_schedule,
iolimit_policy=iolimit_policy)


class UnityPoolList(UnityResourceList):
Expand Down
14 changes: 9 additions & 5 deletions storops/unity/resource/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from storops.unity.resource.sp import UnityStorageProcessorList
from storops.unity.resource.port import UnityEthernetPortList, \
UnityIscsiPortalList
from storops.unity.resource.vmware import UnityCapabilityProfileList

__author__ = 'Jay Xu'

Expand All @@ -55,21 +56,24 @@ def __init__(self, host=None, username=None, password=None,
else:
self._cli = cli

def get_capability_profile(self, _id=None, name=None, **filters):
return self._get_unity_rsc(UnityCapabilityProfileList,
_id=_id, name=name, **filters)

def get_sp(self, _id=None, name=None, **filters):
return self._get_unity_rsc(UnityStorageProcessorList, _id=_id,
name=name, **filters)

def get_iscsi_portal(self, _id=None, name=None, **filters):
return self._get_unity_rsc(UnityIscsiPortalList, _id=_id,
name=name, **filters)
def get_iscsi_portal(self, _id=None, **filters):
return self._get_unity_rsc(UnityIscsiPortalList, _id=_id, **filters)

def get_ethernet_port(self, _id=None, name=None, **filters):
return self._get_unity_rsc(UnityEthernetPortList, _id=_id,
name=name, **filters)

def create_host(self, name, host_type=None, desc=None, os=None):
host = UnityHostList.get(self._cli, name=name).first_item
if host and host.existed:
host = UnityHostList.get(self._cli, name=name)
if host:
raise ex.UnityHostNameInUseError()
else:
host = UnityHost.create(self._cli, name, host_type=host_type,
Expand Down
4 changes: 3 additions & 1 deletion storops/unity/resp.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ def contents(self):
if 'entries' in self.body:
ret = [entry.get('content', {})
for entry in self.body['entries']]
elif 'content' in self.body:
ret = [self.body.get('content')]
else:
ret = [self.body.get('content', {})]
ret = []
return ret

@property
Expand Down
13 changes: 12 additions & 1 deletion test/test_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
from hamcrest import assert_that, equal_to, raises

from storops.exception import StoropsException, check_error, \
VNXSpNotAvailableError, VNXNotSupportedError
VNXSpNotAvailableError, VNXNotSupportedError, UnityNameNotUniqueError
from storops.unity.resource.host import UnityHost

__author__ = 'Cedric Zhuang'

Expand Down Expand Up @@ -78,3 +79,13 @@ def test_check_error_not_found_in_multiple_errors(self):
out = 'The specified source LUN is not currently migrating.'
# no exception, not checking a `VNXLunNotMigratingError`
check_error(out, VNXNotSupportedError, VNXSpNotAvailableError)

def test_unity_name_not_unique_error(self):
ex = UnityNameNotUniqueError(objects=None)
assert_that(str(ex), equal_to('multiple objects found'))
assert_that(ex.objects, equal_to(None))

host = UnityHost(_id=9999)
ex = UnityNameNotUniqueError("Multipath hosts found.", objects=host)
assert_that(str(ex), equal_to('Multipath hosts found.'))
assert_that(ex.objects, equal_to(host))
Loading

0 comments on commit 17e2c5a

Please sign in to comment.