Skip to content

Commit

Permalink
Release 0.2.22
Browse files Browse the repository at this point in the history
Enhancements:
* Add Unity QoS support.
* Add Unity LUN snap creation and deletion support.
* Add Unity FC port list support.
* Support nested properties query for Unity.
  • Loading branch information
Cedric Zhuang committed Oct 26, 2016
2 parents d191683 + f96a38d commit 34b6575
Show file tree
Hide file tree
Showing 74 changed files with 3,644 additions and 268 deletions.
2 changes: 1 addition & 1 deletion README.rst
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.21
VERSION: 0.2.22

A minimalist Python library to manage VNX/Unity systems.
This document lies in the source code and go with the release.
Expand Down
39 changes: 38 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 All @@ -391,6 +419,15 @@ class UnityFileSystemSizeTooSmallError(UnityException):
error_code = 108008449


class UnityQosException(UnityException):
pass


@rest_exception
class UnityPolicyNameInUseError(UnityQosException):
error_code = 151032071


class UnityEthernetPortMtuSizeNotSupportError(UnityException):
message = "Specified MTU size is not supported."

Expand Down
10 changes: 9 additions & 1 deletion storops/lib/resource.py
Expand Up @@ -194,9 +194,14 @@ def _update_property_cache(self, name, value):
def _is_updated(self):
return self._parsed_resource is not None

def get_preloaded_prop_keys(self):
return []

def _get_property_from_raw(self, item):
if not self._is_updated():
if (not self._is_updated() and item not in
self.get_preloaded_prop_keys()):
self.update()

if item in self.property_names():
ret = self._get_value_by_key(item)
else:
Expand Down Expand Up @@ -264,6 +269,9 @@ def get_dict_repr(self, dec=1):
items = [item.get_dict_repr(dec - 1) for item in self.list]
return {self.__class__.__name__: items}

def get_preloaded_prop_keys(self):
return []

def __len__(self):
return len(self.list)

Expand Down
28 changes: 17 additions & 11 deletions storops/unity/client.py
Expand Up @@ -36,15 +36,16 @@ def __init__(self, ip, username, password, port=443):
self._rest = UnityRESTConnector(ip, port=port, user=username,
password=password)

def get_all(self, type_name, fields=None, the_filter=None):
def get_all(self, type_name, base_fields=None, the_filter=None,
nested_fields=None):
"""Get the resource by resource id.
:param the_filter: dictionary of filter like `{'name': 'abc'}`
:param type_name: Resource type. For example, pool, lun, nasServer.
:param fields: Resource fields to return
:return: List of resource class objects
"""
fields = self.get_fields(type_name, fields)
fields = self.get_fields(type_name, base_fields, nested_fields)
the_filter = self.dict_to_filter_string(the_filter)

url = '/api/types/{}/instances'.format(type_name)
Expand All @@ -66,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 Expand Up @@ -117,31 +119,35 @@ def _get_type_resource(self, type_name):
type_clz = storops.unity.resource.type_resource.UnityType
return type_clz(type_name, self)

def get_fields(self, type_name, fields=None):
if fields is not None:
ret = fields
def get_fields(self, type_name, base_fields=None, nested_fields=None):
if base_fields is not None:
ret = base_fields
else:
unity_type = self._get_type_resource(type_name)
ret = unity_type.fields
if nested_fields is not None:
if isinstance(nested_fields, six.text_type):
nested_fields = tuple([nested_fields])
ret = ret + nested_fields
return ret

def get_doc(self, clz):
return UnityDoc.get_doc(self, clz)

def get(self, type_name, obj_id, fields=None):
def get(self, type_name, obj_id, base_fields=None, nested_fields=None):
"""Get the resource by resource id.
:param type_name: Resource type. For example, pool, lun, nasServer.
:param obj_id: Resource id
:param fields: Resource fields to return
:param base_fields: Resource fields to return
:return: List of tuple [(name, res_inst)]
"""
fields = self.get_fields(type_name, fields)
base_fields = self.get_fields(type_name, base_fields, nested_fields)

url = '/api/instances/{}/{}'.format(
type_name, obj_id, ','.join(fields))
type_name, obj_id, ','.join(base_fields))

return self.rest_get(url, fields=fields)
return self.rest_get(url, fields=base_fields)

def post(self, type_name, **kwargs):
url = '/api/types/{}/instances'.format(type_name)
Expand Down
20 changes: 20 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 Expand Up @@ -544,6 +549,10 @@ class ConnectorTypeEnum(UnityEnum):
RJ45 = (1, 'RJ45')
LC = (2, 'LC')
MINI_SAS_HD = (3, 'MiniSAS HD')
COPPER_PIGTAIL = (4, "Copper pigtail")
NO_SEPARABLE_CONNECTOR = (5, "No separable connector")
NAS_COPPER = (6, "NAS copper")
NOT_PRESENT = (7, "Not present")


class EPSpeedValuesEnum(UnityEnum):
Expand Down Expand Up @@ -670,3 +679,14 @@ class ACEAccessLevelEnum(UnityEnum):
READ = (1, 'Read')
WRITE = (2, 'Write')
FULL = (4, 'Full')


class IOLimitPolicyStateEnum(UnityEnum):
GLOBAL_PAUSED = (1, 'Global Paused')
PAUSED = (2, 'Paused')
ACTIVE = (3, 'Active')


class IOLimitPolicyTypeEnum(UnityEnum):
ABSOLUTE = (1, 'Absolute Value')
DENSITY_BASED = (2, 'Density-based Value')
88 changes: 86 additions & 2 deletions storops/unity/parser.py
Expand Up @@ -16,6 +16,8 @@
from __future__ import unicode_literals

import logging
import re
import six

from storops.lib.common import cache
from storops.lib.parser import ParserConfigFactory, OutputParser
Expand Down Expand Up @@ -62,9 +64,10 @@ def parse(self, output, properties=None):
output = output.first_content
except AttributeError:
pass
return self._parse_object(output)
return self._parse_object(output, properties=None,
preloaded_props=properties)

def _parse_object(self, obj, properties=None):
def _parse_object(self, obj, properties=None, preloaded_props=None):
if properties is None:
properties = self.properties

Expand All @@ -76,8 +79,89 @@ def _parse_object(self, obj, properties=None):
continue
if p.label in obj.keys():
value = p.convert(obj[p.label])
if preloaded_props is not None and isinstance(
preloaded_props, NestedProperties):
subtree = preloaded_props.get_child_subtree(p.key)
if (subtree is not None and
hasattr(value, 'set_preloaded_properties')):
value.set_preloaded_properties(subtree)
ret[p.key] = value
return ret

def init_from_config(self, config):
self.name = config.name


class NestedProperty(object):
def __init__(self, key):
self.key = key

@property
def label(self):
return self.under_score_to_camel_case(self.key)

@classmethod
def under_score_to_camel_case(cls, value):
ret = re.sub(r'_([a-z])', lambda a: a.group(1).upper(), value)
return ret

def get_first_level_key(self):
return self.key.split('.')[0]

def remove_first_level_key(self):
pos = self.key.find('.')
if pos >= 0:
return self.key[pos + 1:]
else:
return None


class NestedProperties(object):
def __init__(self, *keys):
self._props = map(NestedProperty, keys)
self._map = None
self._query_fields = None

@classmethod
def build(cls, properties):
ret = None
if not properties:
ret = None
elif isinstance(properties, six.text_type):
ret = NestedProperties(properties)
elif isinstance(properties, (list, tuple, set)):
ret = NestedProperties(*properties)
else:
log.error('invalid properties {} to build NestedProperties '
'object.'.format(properties))
return ret

@property
def _prop_map(self):
if not self._map:
map = {}
for p in self._props:
key = p.get_first_level_key()
child_prop = p.remove_first_level_key()
if child_prop is not None:
map.setdefault(key, []).append(child_prop)
else:
map.setdefault(key, [])
self._map = map
return self._map

def get_properties(self):
return self._prop_map.keys()

def get_child_subtree(self, prop):
if prop not in self._prop_map:
return None
if len(self._prop_map[prop]) == 0:
return None
return NestedProperties.build(self._prop_map[prop])

@property
def query_fields(self):
if self._query_fields is None:
self._query_fields = [a.label for a in self._props]
return tuple(self._query_fields)

0 comments on commit 34b6575

Please sign in to comment.