Discussion:
[Libstoragemgmt-devel] [PATCH 0/3] lsm.AccessGroup, lsm.MaskInfo and lsm.Initiator
Gris Ge
2014-04-21 14:44:45 UTC
Permalink
Sorry for big patch. It's pretty hard to split them into pieces.
* Update Python API codes to compliant with API documents for
volume masking part:
* Update lsm.AccessGroup
* Remove lsm.Initiator
* Add lsm.MaskInfo
* simulator plugin update for this change.
* Introduce PluginHelper for searching/filtering.
* lsmcli update for support this change.

I just retype this patch set in three days after a data lose.
It surely has pretty errors. Please kindly let me know if you found anything
wrong or suspicious.

TODO:
1. C/C++ codes.
2. Test codes. # I can do the python test codes part.
3. iSCSI CHAP.
# I will add a section in API document and provide patch before end of
# this week.
4. Query on lsm.Client.volumes()
# This will come alone with code-doc sync on lsm.Volume patch set.
5. Update these plugins for this change:
* SMI-S -- smis.py # I will take this
* Simulator C -- simc_lsmplugin.c
* ONTAP -- ontap.py
* targetd -- targetd.py # I will take this
* IBM V7K -- ibmv7k.py
* NStor -- nstor.py
6. Add more search/filter support to other query methods.
Goal is allowing the same set of search_keys on every query methods.

Gris Ge (3):
Python API: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator
lsmcli: update volume masking related commands
simulator.py: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator

doc/man/lsmcli.1.in | 131 ++++-----
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 596 +++++++++++++++++++++++++++++------------
lsm/lsm/_cmdline.py | 362 ++++++++++---------------
lsm/lsm/_common.py | 45 ++++
lsm/lsm/_data.py | 168 ++++++------
lsm/lsm/lsmcli_data_display.py | 151 ++++++++---
lsm/lsm/plugin_helper.py | 72 +++++
lsm/lsm/simarray.py | 380 +++++++++-----------------
lsm/lsm/simulator.py | 91 +++----
10 files changed, 1111 insertions(+), 891 deletions(-)
create mode 100644 lsm/lsm/plugin_helper.py
--
1.8.3.1
Gris Ge
2014-04-21 14:44:46 UTC
Permalink
* Removed these methods:
access_group_add_initiator() # replaced by access_group_edit()
access_group_del_initiator() # replaced by access_group_edit()
access_group_grant() # replaced by volume_mask()
access_group_revoke() # replaced by volume_unmask()
access_groups_granted_to_volume # replaced by access_groups()
initiators_granted_to_volume() # No need.
* Renamed these methods:
access_group_del() -> access_group_delete()
access_group_list() -> access_groups()
* Added these methods:
volume_mask()
volume_unmask()
mask_infos()
* Removed this class:
lsm.Initiator
* Added this class:
lsm.MaskInfo
* Updated this class:
lsm.AccessGroup
* Add property 'optional_data', 'init_type'.
* Renam property 'initiators' to 'init_ids'.
* No doxygen document included as we have better one.
* Method document copied from API document.
* Purged private constants and methods of lsm.AccessGroup about value
converting. They have been moved to lsmcli_data_display.py.

Signed-off-by: Gris Ge <***@redhat.com>
---
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 596 +++++++++++++++++++++++++++++++++++++---------------
lsm/lsm/_common.py | 45 ++++
lsm/lsm/_data.py | 168 ++++++++-------
4 files changed, 563 insertions(+), 252 deletions(-)

diff --git a/lsm/lsm/__init__.py b/lsm/lsm/__init__.py
index 0635b7e..836ab75 100644
--- a/lsm/lsm/__init__.py
+++ b/lsm/lsm/__init__.py
@@ -4,9 +4,9 @@ from version import VERSION

from _common import Error, Info, LsmError, ErrorLevel, ErrorNumber, \
JobStatus, uri_parse, md5, Proxy, size_bytes_2_size_human
-from _data import Initiator, Disk, \
- Volume, Pool, System, FileSystem, FsSnapshot, NfsExport, BlockRange, \
- AccessGroup, OptionalData, Capabilities, txt_a
+from _data import Disk, Volume, Pool, System, FileSystem, FsSnapshot, \
+ NfsExport, BlockRange, AccessGroup, OptionalData, Capabilities, MaskInfo, \
+ txt_a
from _iplugin import IPlugin, IStorageAreaNetwork, INetworkAttachedStorage, \
INfs

diff --git a/lsm/lsm/_client.py b/lsm/lsm/_client.py
index a8a30c2..fdfd542 100644
--- a/lsm/lsm/_client.py
+++ b/lsm/lsm/_client.py
@@ -19,9 +19,9 @@ import time
import os
import unittest
from lsm import (Volume, NfsExport, Capabilities, Pool, System,
- Initiator, Disk, AccessGroup, FileSystem, FsSnapshot,
+ Disk, AccessGroup, FileSystem, FsSnapshot,
uri_parse, LsmError, JobStatus, ErrorNumber,
- INetworkAttachedStorage)
+ INetworkAttachedStorage, MaskInfo)

from _common import return_requires as _return_requires
from _common import UDS_PATH as _UDS_PATH
@@ -427,17 +427,6 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('systems', _del_self(locals()))

- ## Returns an array of initiator objects
- # @param self The this pointer
- # @param flags Reserved for future use, must be zero.
- # @returns An array of initiator objects.
- @_return_requires([Initiator])
- def initiators(self, flags=0):
- """
- Return an array of initiator objects
- """
- return self._tp.rpc('initiators', _del_self(locals()))
-
## Register a user/password for the specified initiator for CHAP
# authentication.
# Note: If you pass an empty user and password the expected behavior is to
@@ -459,57 +448,6 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('iscsi_chap_auth', _del_self(locals()))

- ## Grants access so an initiator can read/write the specified volume.
- # @param self The this pointer
- # @param initiator_id The iqn, WWID etc.
- # @param initiator_type Enumerated initiator type
- # @param volume Volume to grant access to
- # @param access Enumerated access type
- # @param flags Reserved for future use, must be zero
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- flags=0):
- """
- Allows an initiator to access a volume.
- """
- return self._tp.rpc('initiator_grant', _del_self(locals()))
-
- ## Revokes access for a volume for the specified initiator.
- # @param self The this pointer
- # @param initiator The iqn, WWID etc.
- # @param volume The volume to revoke access for
- # @param flags Reserved for future use, must be zero
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def initiator_revoke(self, initiator, volume, flags=0):
- """
- Revokes access to a volume for the specified initiator
- """
- return self._tp.rpc('initiator_revoke', _del_self(locals()))
-
- ## Returns a list of volumes that are accessible from the specified
- # initiator.
- # @param self The this pointer
- # @param initiator The initiator object
- # @param flags Reserved for future use, must be zero
- @_return_requires([Volume])
- def volumes_accessible_by_initiator(self, initiator, flags=0):
- """
- Returns a list of volumes that the initiator has access to.
- """
- return self._tp.rpc('volumes_accessible_by_initiator',
- _del_self(locals()))
-
- ## Returns a list of initiators that have access to the specified volume.
- # @param self The this pointer
- # @param volume The volume in question
- # @param flags Reserved for future use, must be zero
- # @returns List of initiators
- @_return_requires([Initiator])
- def initiators_granted_to_volume(self, volume, flags=0):
- return self._tp.rpc('initiators_granted_to_volume', _del_self(locals()))
-
## Returns an array of volume objects
# @param self The this pointer
# @param flags Reserved for future use, must be zero.
@@ -658,6 +596,197 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('volume_offline', _del_self(locals()))

+ @_return_requires([MaskInfo])
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ """
+ lsm.Client.mask_infos(self, search_key=None, search_value=None,
+ flags=0)
+
+ Usage:
+ Change the initiators of certain access group.
+ If 'search_key' and 'search_value' is defined, only system matches
+ will be contained in return list.
+ Valid search keys are stored in lsm.MaskInfo.SUPPORTED_SEARCH_KEYS:
+ id # Search lsm.AccessGroup.id property
+ volume_id # Search lsm.AccessGroup.volume_id property
+ access_group_id # Search lsm.AccessGroup.access_group_id
+ # property
+ system_id # Search mask info on given system.
+ When no matches found, return a empty list
+ Parameters:
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # Valid value:
+ # lsm.MaskInfo.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.MaskInfo object will contain
+ # # optional properties. But currently,
+ # # there is no optional property for
+ # # lsm.MaskInfo yet, this will make no
+ # # difference.
+ Returns:
+ [lsm.MaskInfo,] # A list of lsm.MaskInfo objects.
+ or
+ [] # Nothing found.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.MASK_QUERY_INVALID_SEARCH_KEY
+ # Got invalid search key.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.MASK_INFOS
+ # Indicate current systems support query mask info via
+ # lsm.Client.mask_infos() method.
+ lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all mask info
+ # without 'search_key' parameter, then filter in user's code
+ # space.
+ """
+ if search_key and search_key not in MaskInfo.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.MASK_QUERY_INVALID_SEARCH_KEY,
+ "Invalid search key %s" % search_key)
+
+ return self._tp.rpc('mask_infos',
+ {"search_key":search_key,
+ "search_value":search_value,
+ "flags":flags})
+
+ @_return_requires(MaskInfo)
+ def volume_mask(self, volume, access_group, access_type, flags=0):
+ """
+ lsm.Client.volume_mask(self, volume, access_group, access_type,
+ flags=0)
+
+ Usage:
+ Mask certain volume to defined access group.
+ To change access_type of existing mask info, just invoke this
+ method with new access_type.
+ Storage will never mask single volume to single access group with
+ two or more LUN IDs, duplicated call of this method will not
+ resulting mulitpal masking for single voluem to single access
+ group.
+ For those storage array which does not support changing existing
+ masking relationship, they will deleting existing and creating new
+ one.
+ Parameters:
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ access_type # Integer. Check lsm.MaskInfo.access_type property.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ lsm.MaskInfo # Object of lsm.MaskInfo.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT
+ # Does not support
+ lsm.ErrorNumber.VOL_MASK_INVALID_VOLUME
+ # Invalid lsm.Volume
+ lsm.ErrorNumber.VOL_MASK_INVALID_ACCESS_GROUP
+ # Invalid lsm.AccessGroup
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RW
+ # RW access type is not support by storage system
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RO
+ # RO access type is not support by storage system
+ lsm.ErrorNumber.VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RW
+ # Defined volume does not allow RW access
+ # Example: DR(Disaster Recover) site volumes does not
+ # allow RW access.
+ lsm.ErrorNumber.VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RO
+ # Defined volume does not allow RO access.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.VOLUME_MASK
+ # Indicate current systems support volume masking via
+ # lsm.Client.volume_mask() method.
+ """
+ return self._tp.rpc('volume_mask', _del_self(locals()))
+
+ @_return_requires(None)
+ def volume_unmask(self, mask_info=None, volume=None, access_group=None,
+ flags=0):
+ """
+ lsm.Client.volume_unmask(self, mask_info=None, volume=None,
+ access_group=None, flags=0)
+
+ Usage:
+ Unmask the volume.
+ This method support two way of unmask:
+ * lsm.Client.volume_unmask(self, mask_info=lsm.MaskInfo, flags=0)
+ * lsm.Client.volume_unmask(self, volume=lsm.Volume,
+ access_group=lsm.AccessGroup, flags=0)
+ The 'mask_info' parameter should not be used with 'volume' or
+ access_group parameters, a LsmError will be raise if so.
+ If define volume is not masked to access group, this method will
+ do nothing but return.
+ Parameters:
+ mask_info # Object of lsm.Maskino
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Unmask done without error.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_PARAMETER
+ # Neither 'mask_info' nor ('volume' and 'access_group')
+ # is define.
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_MASK_INFO
+ # Invalid lsm.MaskInfo
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_VOLUME
+ # Invalid lsm.Volume
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_ACCESS_GROUP
+ # Invalid lsm.AccessGroup
+ lsm.ErrorNumber.VOL_UNMASK_NO_SUPPORT
+ # Does not support volume unmask.
+ lsm.ErrorNumber.VOL_UNMASK_CONFLICT_PARAMETERS
+ # Got 'mask_info' parameters with 'volume' or
+ # 'access_group' parameters
+ lsm.ErrorNumber.VOL_UNMASK_MISSING_VOLUME
+ # Only got 'access_group' parameter defined.
+ lsm.ErrorNumber.VOL_UNMASK_MISSING_ACCESS_GROUP
+ # Only got 'volume' parameter defined.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.VOLUME_UNMASK
+ # Indicate current systems support volume unmasking via
+ # lsm.Client.volume_unmask() method.
+ """
+ if (mask_info is not None and volume is not None) or \
+ (mask_info is not None and access_group is not None):
+ raise LsmError(ErrorNumber.VOL_UNMASK_CONFLICT_PARAMETERS,
+ "Argument conflicting: both 'mask_info' and "
+ "('volume' or/and 'access_group') are defined.")
+ elif volume is not None and access_group is None:
+ raise LsmError(ErrorNumber.VOL_UNMASK_MISSING_ACCESS_GROUP,
+ "Only 'volume' is defined in volume_unmask()"
+ "parameter, missing 'access_group'.")
+ elif access_group is not None and volume is None:
+ raise LsmError(ErrorNumber.VOL_UNMASK_MISSING_VOLUME,
+ "Only 'access_group' is defined in volume_unmask()"
+ "parameter, missing 'volume'.")
+ elif mask_info is None and volume is None:
+ raise LsmError(ErrorNumber.VOL_UNMASK_INVALID_PRAMETER,
+ "Neither 'mask_info' nor ('volume' and "
+ "'access_group') is defined")
+ return self._tp.rpc('volume_unmask', _del_self(locals()))
+
## Returns an array of disk objects
# @param self The this pointer
# @param flags When equal to DISK.RETRIEVE_FULL_INFO
@@ -672,128 +801,251 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('disks', _del_self(locals()))

- ## Access control for allowing an access group to access a volume
- # @param self The this pointer
- # @param group The access group
- # @param volume The volume to grant access to
- # @param access The desired access
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_grant(self, group, volume, access, flags=0):
- """
- Allows an access group to access a volume.
- """
- return self._tp.rpc('access_group_grant', _del_self(locals()))
-
- ## Revokes access to a volume to initiators in an access group
- # @param self The this pointer
- # @param group The access group
- # @param volume The volume to grant access to
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_revoke(self, group, volume, flags=0):
- """
- Revokes access for an access group for a volume
- """
- return self._tp.rpc('access_group_revoke', _del_self(locals()))
-
- ## Returns a list of access group objects
- # @param self The this pointer
- # @param flags Reserved for future use, must be zero.
- # @returns List of access groups
@_return_requires([AccessGroup])
- def access_groups(self, flags=0):
- """
- Returns a list of access groups
- """
- return self._tp.rpc('access_group_list', _del_self(locals()))
-
- ## Creates an access a group with the specified initiator in it.
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ """
+ lsm.Client.access_groups(self, search_key=None, search_value=None,
+ flags=0)
+
+ Usage:
+ Return a list of objects of lsm.AccessGroup.
+ If 'search_key' and 'search_value' is defined, only access group
+ matches will be contained in return list.
+ Valid search keys are stored in
+ lsm.AccessGroup.SUPPORTED_SEARCH_KEYS:
+ id # Search lsm.AccessGroup.id property
+ access_group_id # Search lsm.AccessGroup.id property
+ name # Search lsm.AccessGroup.name property
+ init_id # If given init_id is contained in
+ # lsm.AccessGroup.init_ids
+ volume_id # If access group is masked to given volume.
+ system_id # Return access group for given system only.
+ When no matches found, return a empty list
+ Parameters:
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # Valid value:
+ # lsm.AccessGroup.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.AccessGroup object will
+ # # contain optional properties.
+ # # But currently, there is no optional
+ # # property for lsm.AccessGroup yet, this
+ # # will make no difference.
+ Returns:
+ [lsm.AccessGroup,] # A list of lsm.AccessGroup objects.
+ or
+ [] # Nothing found.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query.
+ lsm.ErrorNumber.AG_QUERY_INVALID_SEARCH_KEY
+ # Got invalid search key. Should be one of
+ # lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ access_groups = lsm_cli_obj.access_groups(
+ search_key=None, search_value=None, flags=0)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUPS
+ # Indicate current systems support access group query via
+ # lsm.Client.access_groups() method.
+ lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all access
+ # groups without 'search_key' parameter, then filter in user's
+ # code space.
+ """
+ if search_key and search_key not in AccessGroup.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.AG_QUERY_INVALID_SEARCH_KEY,
+ "Invalid search key %s" % search_key)
+
+ # Just save plugin's code lines.
+ if search_key and search_key == 'access_group_id':
+ search_key = 'id'
+
+ return self._tp.rpc('access_groups',
+ {"search_key":search_key,
+ "search_value":search_value,
+ "flags":flags})
+
+ ## Creates an access group with the specified initiators in it.
# @param self The this pointer
- # @param name The initiator group name
- # @param initiator_id Initiator id
- # @param id_type Type of initiator (Enumeration)
+ # @param access_group_name The access group name
+ # @param init_ids List, Initiator ids
+ # @param init_type Type of initiator (Enumeration)
# @param system_id Which system to create this group on
# @param flags Reserved for future use, must be zero.
# @returns AccessGroup on success, else raises LsmError
@_return_requires(AccessGroup)
- def access_group_create(self, name, initiator_id, id_type, system_id,
- flags=0):
- """
- Creates an access group and add the specified initiator id, id_type and
- desired access.
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
+ """
+ lsm.Client.access_group_create(self, access_group_name, init_ids,
+ init_type, system_ids, flags=0)
+
+ Usage:
+ Create a access group.
+ Parameters:
+ access_group_name # String. The name for new access group.
+ # If provided access_group_name does not meet
+ # the requirement of storage system, storage
+ # system will chose a valid one in stead of
+ # raise a error.
+ init_ids # List of string. The initiator IDs.
+ init_type # Integer. The type of initiator, could be one
+ # of these values:
+ # * lsm.AccessGroup.INIT_TYPE_WWPN
+ # * lsm.AccessGroup.INIT_TYPE_WWNN
+ # * lsm.AccessGroup.INIT_TYPE_HOSTNAME
+ # * lsm.AccessGroup.INIT_TYPE_ISCSI_IQN
+ # * lsm.AccessGroup.INIT_TYPE_SAS
+ system_id # The id of lsm.System where this access group
+ # should create on.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ lsm.AccessGroup # Object of lsm.AccessGroup
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT
+ # Does not support creating access group.
+ lsm.ErrorNumber.AG_CREATE_INVALID_INIT_ID
+ # Provide init_ids contain ilegal string. Exmaple:
+ # WWPN does not in (?:[0-9a-f]{2}:){7}[0-9a-f]{2} format.
+ # Its user's responsibity for convering init_ids into lsm
+ # requested format.
+ lsm.ErrorNumber.AG_CREATE_INVALID_INIT_TYPE
+ # Got one of these ilegal init_type:
+ # * lsm.AccessGroup.INIT_TYPE_OTHER
+ # * lsm.AccessGroup.INIT_TYPE_UNKNOWN
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_WWPN
+ # System does not support WWPN initiator type.
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_WWNN
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_HOSTNAME
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_ISCSI_IQN
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_SAS
+ lsm.ErrorNumber.AG_CREATE_EXCEED_LIMITATION_INIT_COUNT
+ # System is reaching limitation of access group initator
+ # count. Example, if a system only allow up to 10
+ # initiators in one access group, if user provides 11 or
+ # more initiators, this error will be raised.
+ lsm.ErrorNumber.AG_CREATE_EXCEED_LIMITATION_AG_COUNT
+ # System is reaching limitation of total access group
+ # counts. Example, if a system only allow 100 access
+ # groups in total, when trying to create the 101 one, this
+ # error will be raised.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_CREATE
+ # Indicate current systems support create access group via
+ # lsm.Client.access_group_create() method.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWPN
+ # Support creating WWPN access group.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWNN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_HOSTNAME
+ lsm.Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_SAS
"""
return self._tp.rpc('access_group_create', _del_self(locals()))

## Deletes an access group.
- # @param self The this pointer
- # @param group The access group to delete
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_del(self, group, flags=0):
- """
- Deletes an access group
- """
- return self._tp.rpc('access_group_del', _del_self(locals()))
-
- ## Adds an initiator to an access group
- # @param self The this pointer
- # @param group Group to add initiator to
- # @param initiator_id Initiators id
- # @param id_type Initiator id type (enumeration)
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_add_initiator(self, group, initiator_id, id_type,
- flags=0):
- """
- Adds an initiator to an access group
- """
- return self._tp.rpc('access_group_add_initiator', _del_self(locals()))
-
- ## Deletes an initiator from an access group
# @param self The this pointer
- # @param group The access group to remove initiator from
- # @param initiator_id The initiator to remove from the group
+ # @param access_group The lsm.AccessGroup to delete
# @param flags Reserved for future use, must be zero.
# @returns None on success, throws LsmError on errors.
@_return_requires(None)
- def access_group_del_initiator(self, group, initiator_id, flags=0):
- """
- Deletes an initiator from an access group
- """
- return self._tp.rpc('access_group_del_initiator', _del_self(locals()))
-
- ## Returns the list of volumes that access group has access to.
- # @param self The this pointer
- # @param group The access group to list volumes for
- # @param flags Reserved for future use, must be zero.
- # @returns list of volumes
- @_return_requires([Volume])
- def volumes_accessible_by_access_group(self, group, flags=0):
- """
- Returns the list of volumes that access group has access to.
- """
- return self._tp.rpc('volumes_accessible_by_access_group',
- _del_self(locals()))
-
- ##Returns the list of access groups that have access to the specified
- #volume.
- # @param self The this pointer
- # @param volume The volume to list access groups for
- # @param flags Reserved for future use, must be zero.
- # @returns list of access groups
- @_return_requires([AccessGroup])
- def access_groups_granted_to_volume(self, volume, flags=0):
- """
- Returns the list of access groups that have access to the specified
- volume.
- """
- return self._tp.rpc('access_groups_granted_to_volume',
- _del_self(locals()))
+ def access_group_delete(self, access_group, flags=0):
+ """
+ lsm.Client.access_group_delete(self, access_group, flags=0)
+
+ Usage:
+ Delete a access group. Only access group has no volume masked to
+ can be deleted.
+ User can use this method to query volumes masked to give access
+ group:
+ lsm.Client.volumes(self, search_key='access_group_id',
+ search_value=access_group.id)
+ Parameters:
+ access_group # Object of lsm.AccessGroup to delete.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Access group deleted without errors.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.AG_DELETE_NO_SUPPORT
+ # Does not support deleting a access group.
+ lsm.ErrorNumber.AG_DELETE_IS_MASKED
+ # Access group has volume masked to.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ lsm_cli_obj.access_group_delete(new_ag)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_DELETE
+ # Indicate current systems support delete access group via
+ # lsm.Client.access_group_delete() method.
+ """
+ return self._tp.rpc('access_group_delete', _del_self(locals()))
+
+ ## Update an existing access group with new initiators.
+ # @param self The this pointer
+ # @param access_group The lsm.AccessGroup object
+ # @param new_init_ids New initiator IDs
+ # @param flags Reserved for future use, must be zero.
+ # @returns AccessGroup on success, else raises LsmError
+ @_return_requires(AccessGroup)
+ def access_group_edit(self, access_group, new_init_ids, flags=0):
+ """
+ lsm.Client.access_group_edit(self, access_group, new_init_ids, flags=0)
+
+ Usage:
+ Change the initiators of certain access group.
+ Parameters:
+ access_group # Object of lsm.AccessGroup to edit.
+ new_init_ids # List of string. New initiator IDs. Old one will
+ # be overided.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Access group edited without errors.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.AG_EDIT_NO_SUPPORT
+ # Does not support deleting a access group.
+ lsm.ErrorNumber.AG_EDIT_INVALID_INIT_ID
+ # Provide new_init_ids contain ilegal string.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ # Remove one WWPN out.
+ lsm_cli_obj.access_group_edit(new_ag, ['20:00:00:1b:21:67:65:d7'])
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_EDIT
+ # Indicate current systems support edit access group member
+ # via lsm.Client.access_group_edit() method.
+ """
+ return self._tp.rpc('access_group_edit', _del_self(locals()))

## Checks to see if a volume has child dependencies.
# @param self The this pointer
diff --git a/lsm/lsm/_common.py b/lsm/lsm/_common.py
index 19b0a24..fbf9c67 100644
--- a/lsm/lsm/_common.py
+++ b/lsm/lsm/_common.py
@@ -503,6 +503,51 @@ class ErrorNumber(object):
DISK_BUSY = 500
VOLUME_BUSY = 501

+ AG_QUERY_INVALID_SEARCH_KEY = 600
+ # Reserve 60x for AG_QUERY_XXX
+
+ AG_CREATE_NO_SUPPORT = 611
+ AG_CREATE_INVALID_INIT_ID = 612
+ AG_CREATE_INVALID_INIT_TYPE = 613
+ AG_CREATE_NO_SUPPORT_WWPN = 614
+ AG_CREATE_NO_SUPPORT_WWNN = 615
+ AG_CREATE_NO_SUPPORT_HOSTNAME = 616
+ AG_CREATE_NO_SUPPORT_ISCSI_IQN = 617
+ AG_CREATE_NO_SUPPORT_SAS = 618
+ AG_CREATE_EXCEED_LIMITATION_INIT_COUNT = 619
+ AG_CREATE_EXCEED_LIMITATION_AG_COUNT = 610
+ # Reserve 62x for AG_CREATE_XXX
+
+ AG_DELETE_NO_SUPPORT = 630
+ AG_DELETE_IS_MASKED = 631
+ # Reserve 63x for AG_DELETE_XXX
+
+ AG_EDIT_NO_SUPPORT = 640
+ AG_EDIT_INVALID_INIT_ID = 641
+ # Reserve 64x for AG_EDIT_XXX
+
+ MASK_QUERY_INVALID_SEARCH_KEY = 650
+ # Reserve 65x for MASK_QUERY_XXX
+
+ VOL_MASK_NO_SUPPORT = 660
+ VOL_MASK_INVALID_VOLUME = 661
+ VOL_MASK_INVALID_ACCESS_GROUP = 662
+ VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RW = 663
+ VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RO = 664
+ VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RW = 665
+ VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RO = 666
+ # Reserve 66x for MASK_QUERY_XXX
+ # Reserve 67x for MASK_QUERY_XXX
+
+ VOL_UNMASK_INVALID_PARAMETER = 680
+ VOL_UNMASK_INVALID_MASK_INFO = 681
+ VOL_UNMASK_INVALID_VOLUME = 682
+ VOL_UNMASK_INVALID_ACCESS_GROUP = 683
+ VOL_UNMASK_NO_SUPPORT = 684
+ VOL_UNMASK_CONFLICT_PARAMETERS = 685
+ VOL_UNMASK_MISSING_VOLUME = 686
+ VOL_UNMASK_MISSING_ACCESS_GROUP = 687
+

class JobStatus(object):
INPROGRESS = 1
diff --git a/lsm/lsm/_data.py b/lsm/lsm/_data.py
index 603bf16..219f3bd 100644
--- a/lsm/lsm/_data.py
+++ b/lsm/lsm/_data.py
@@ -263,48 +263,6 @@ class IData(object):


@default_property('id', doc="Unique identifier")
-@default_property('type', doc="Enumerated initiator type")
-@default_property('name', doc="User supplied name")
-class Initiator(IData):
- """
- Represents an initiator.
- """
- (TYPE_OTHER, TYPE_PORT_WWN, TYPE_NODE_WWN, TYPE_HOSTNAME, TYPE_ISCSI,
- TYPE_SAS) = (1, 2, 3, 4, 5, 7)
-
- _type_map = {1: 'Other', 2: 'Port WWN', 3: 'Node WWN', 4: 'Hostname',
- 5: 'iSCSI', 7: "SAS"}
-
- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'type': 'Type',
- }
-
- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'type']
-
- def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
- if not enum_as_number:
- if key_name == 'type':
- value = Initiator._type_to_str(value)
- return value
-
- @staticmethod
- def _type_to_str(init_type):
- return Initiator._type_map[init_type]
-
- def __init__(self, _id, _type, _name):
-
- if not _name or not len(_name):
- name = "Unsupported"
-
- self._id = _id # Identifier
- self._type = _type # Initiator type id
- self._name = _name # Initiator name
-
-
-@default_property('id', doc="Unique identifier")
@default_property('name', doc="Disk name (aka. vendor)")
@default_property('disk_type', doc="Enumerated type of disk")
@default_property('block_size', doc="Size of each block")
@@ -1404,31 +1362,48 @@ class BlockRange(IData):

@default_property('id', doc="Unique instance identifier")
@default_property('name', doc="Access group name")
-@default_property('initiators', doc="List of initiators")
+@default_property('init_ids', doc="List of initiators")
+@default_property('init_type', doc="Initiator type")
@default_property('system_id', doc="System identifier")
+@default_property("optional_data", doc="Optional data")
class AccessGroup(IData):
- def __init__(self, _id, _name, _initiators, _system_id='NA'):
- self._id = _id
- self._name = _name # AccessGroup name
- self._initiators = _initiators # List of initiators
- self._system_id = _system_id # System id this group belongs

- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'initiators': 'Initiator IDs',
- 'system_id': 'System ID',
- }
+ FLAG_RETRIEVE_FULL_INFO = 1 << 0

- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'initiators', 'system_id']
- _OPT_PROPERTIES_SEQUENCE = []
+ SUPPORTED_SEARCH_KEYS = ['id', 'access_group_id', 'name', 'init_ids',
+ 'volume_id', 'system_id']

- def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
- if list_convert:
- if key_name == 'initiators':
- value = ','.join(str(x) for x in value)
- return value
+ INIT_TYPE_UNKNOWN = 0
+ INIT_TYPE_OTHER = 1
+ INIT_TYPE_WWPN = 2
+ INIT_TYPE_WWNN = 3
+ INIT_TYPE_HOSTNAME = 4
+ INIT_TYPE_ISCSI_IQN = 5
+ INIT_TYPE_SAS = 6
+
+ OPT_PROPERTIES = []
+
+ def __init__(self, _id, _name, _init_ids, _init_type, _system_id,
+ _optional_data=None):
+ self._id = _id
+ self._name = _name # AccessGroup name
+ self._init_ids = _init_ids # List of initiators
+ self._init_type = _init_type # Initiator type
+ self._system_id = _system_id # System id this group belongs
+
+ if _optional_data is None:
+ self._optional_data = OptionalData()
+ else:
+ #Make sure the properties only contain ones we permit
+ allowed = set(AccessGroup.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ if actual <= allowed:
+ self._optional_data = _optional_data
+ else:
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))


class OptionalData(IData):
@@ -1492,30 +1467,30 @@ class Capabilities(IData):
VOLUME_ONLINE = 34
VOLUME_OFFLINE = 35

- ACCESS_GROUP_GRANT = 36
- ACCESS_GROUP_REVOKE = 37
- ACCESS_GROUP_LIST = 38
- ACCESS_GROUP_CREATE = 39
- ACCESS_GROUP_DELETE = 40
- ACCESS_GROUP_ADD_INITIATOR = 41
- ACCESS_GROUP_DEL_INITIATOR = 42
+ ACCESS_GROUPS = 36
+ ACCESS_GROUPS_QUICK_SEARCH = 37
+ ACCESS_GROUP_CREATE = 38
+ ACCESS_GROUP_DELETE = 39
+ ACCESS_GROUP_EDIT = 39

- VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
- ACCESS_GROUPS_GRANTED_TO_VOLUME = 44
+ ACCESS_GROUP_CREATE_WWPN = 40
+ ACCESS_GROUP_CREATE_WWNN = 41
+ ACCESS_GROUP_CREATE_HOSTNAME = 42
+ ACCESS_GROUP_CREATE_ISCSI_IQ = 43
+ ACCESS_GROUP_CREATE_SAS = 44

VOLUME_CHILD_DEPENDENCY = 45
VOLUME_CHILD_DEPENDENCY_RM = 46

- INITIATORS = 47
- INITIATORS_GRANTED_TO_VOLUME = 48
-
- VOLUME_INITIATOR_GRANT = 50
- VOLUME_INITIATOR_REVOKE = 51
- VOLUME_ACCESSIBLE_BY_INITIATOR = 52
VOLUME_ISCSI_CHAP_AUTHENTICATION = 53

VOLUME_THIN = 55

+ MASK_INFOS = 60
+ MASK_INFOS_QUICK_SEARCH = 61
+ VOLUME_MASK = 62
+ VOLUME_UNMASK = 63
+
#File system
FS = 100
FS_DELETE = 101
@@ -1622,6 +1597,45 @@ class PlugData(IData):
self.desc = description
self.version = plugin_version

+@default_property('id', doc="Unique Identifier")
+@default_property('volume_id', doc="Volume ID")
+@default_property('access_group_id', doc="Access Group ID")
+@default_property('access_type', doc="Access Type")
+@default_property('system_id', doc="System ID")
+@default_property("optional_data", doc="Optional data")
+class MaskInfo(IData):
+
+ ACCESS_TYPE_UNKNOWN = 0
+ ACCESS_TYPE_READ_WRITE = 1
+ ACCESS_TYPE_READ_ONLY = 2
+
+ OPT_PROPERTIES = []
+
+ SUPPORTED_SEARCH_KEYS = ['id', 'volume_id', 'access_group_id',
+ 'system_id']
+
+ def __init__(self, _id, _volume_id, _access_group_id, _access_type,
+ _system_id, _optional_data=None):
+ self._id = _id
+ self._volume_id = _volume_id
+ self._access_group_id = _access_group_id
+ self._access_type = _access_type
+ self._system_id = _system_id
+
+ if _optional_data is None:
+ self._optional_data = OptionalData()
+ else:
+ #Make sure the properties only contain ones we permit
+ allowed = set(MaskInfo.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ if actual <= allowed:
+ self._optional_data = _optional_data
+ else:
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))
+

if __name__ == '__main__':
#TODO Need some unit tests that encode/decode all the types with nested
--
1.8.3.1
Tony Asleson
2014-04-21 16:26:10 UTC
Permalink
Comments below...
Post by Gris Ge
access_group_add_initiator() # replaced by access_group_edit()
access_group_del_initiator() # replaced by access_group_edit()
access_group_grant() # replaced by volume_mask()
access_group_revoke() # replaced by volume_unmask()
access_groups_granted_to_volume # replaced by access_groups()
initiators_granted_to_volume() # No need.
access_group_del() -> access_group_delete()
access_group_list() -> access_groups()
volume_mask()
volume_unmask()
mask_infos()
lsm.Initiator
lsm.MaskInfo
lsm.AccessGroup
* Add property 'optional_data', 'init_type'.
* Renam property 'initiators' to 'init_ids'.
* No doxygen document included as we have better one.
* Method document copied from API document.
* Purged private constants and methods of lsm.AccessGroup about value
converting. They have been moved to lsmcli_data_display.py.
---
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 596 +++++++++++++++++++++++++++++++++++++---------------
lsm/lsm/_common.py | 45 ++++
lsm/lsm/_data.py | 168 ++++++++-------
4 files changed, 563 insertions(+), 252 deletions(-)
diff --git a/lsm/lsm/__init__.py b/lsm/lsm/__init__.py
index 0635b7e..836ab75 100644
--- a/lsm/lsm/__init__.py
+++ b/lsm/lsm/__init__.py
@@ -4,9 +4,9 @@ from version import VERSION
from _common import Error, Info, LsmError, ErrorLevel, ErrorNumber, \
JobStatus, uri_parse, md5, Proxy, size_bytes_2_size_human
-from _data import Initiator, Disk, \
- Volume, Pool, System, FileSystem, FsSnapshot, NfsExport, BlockRange, \
- AccessGroup, OptionalData, Capabilities, txt_a
+from _data import Disk, Volume, Pool, System, FileSystem, FsSnapshot, \
+ NfsExport, BlockRange, AccessGroup, OptionalData, Capabilities, MaskInfo, \
+ txt_a
from _iplugin import IPlugin, IStorageAreaNetwork, INetworkAttachedStorage, \
INfs
diff --git a/lsm/lsm/_client.py b/lsm/lsm/_client.py
index a8a30c2..fdfd542 100644
--- a/lsm/lsm/_client.py
+++ b/lsm/lsm/_client.py
@@ -19,9 +19,9 @@ import time
import os
import unittest
from lsm import (Volume, NfsExport, Capabilities, Pool, System,
- Initiator, Disk, AccessGroup, FileSystem, FsSnapshot,
+ Disk, AccessGroup, FileSystem, FsSnapshot,
uri_parse, LsmError, JobStatus, ErrorNumber,
- INetworkAttachedStorage)
+ INetworkAttachedStorage, MaskInfo)
from _common import return_requires as _return_requires
from _common import UDS_PATH as _UDS_PATH
"""
return self._tp.rpc('systems', _del_self(locals()))
- ## Returns an array of initiator objects
- """
- Return an array of initiator objects
- """
- return self._tp.rpc('initiators', _del_self(locals()))
-
## Register a user/password for the specified initiator for CHAP
# authentication.
# Note: If you pass an empty user and password the expected behavior is to
"""
return self._tp.rpc('iscsi_chap_auth', _del_self(locals()))
- ## Grants access so an initiator can read/write the specified volume.
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- """
- Allows an initiator to access a volume.
- """
- return self._tp.rpc('initiator_grant', _del_self(locals()))
-
- ## Revokes access for a volume for the specified initiator.
- """
- Revokes access to a volume for the specified initiator
- """
- return self._tp.rpc('initiator_revoke', _del_self(locals()))
-
- ## Returns a list of volumes that are accessible from the specified
- # initiator.
- """
- Returns a list of volumes that the initiator has access to.
- """
- return self._tp.rpc('volumes_accessible_by_initiator',
- _del_self(locals()))
-
- ## Returns a list of initiators that have access to the specified volume.
- return self._tp.rpc('initiators_granted_to_volume', _del_self(locals()))
-
## Returns an array of volume objects
"""
return self._tp.rpc('volume_offline', _del_self(locals()))
+ """
+ lsm.Client.mask_infos(self, search_key=None, search_value=None,
+ flags=0)
+
+ Change the initiators of certain access group.
+ If 'search_key' and 'search_value' is defined, only system matches
+ will be contained in return list.
+ id # Search lsm.AccessGroup.id property
+ volume_id # Search lsm.AccessGroup.volume_id property
+ access_group_id # Search lsm.AccessGroup.access_group_id
+ # property
+ system_id # Search mask info on given system.
+ When no matches found, return a empty list
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # lsm.MaskInfo.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.MaskInfo object will contain
+ # # optional properties. But currently,
+ # # there is no optional property for
+ # # lsm.MaskInfo yet, this will make no
+ # # difference.
+ [lsm.MaskInfo,] # A list of lsm.MaskInfo objects.
+ or
+ [] # Nothing found.
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.MASK_QUERY_INVALID_SEARCH_KEY
+ # Got invalid search key.
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ lsm.Capabilities.MASK_INFOS
+ # Indicate current systems support query mask info via
+ # lsm.Client.mask_infos() method.
+ lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all mask info
+ # without 'search_key' parameter, then filter in user's code
+ # space.
+ """
+ raise LsmError(ErrorNumber.MASK_QUERY_INVALID_SEARCH_KEY,
+ "Invalid search key %s" % search_key)
+
+ return self._tp.rpc('mask_infos',
+ {"search_key":search_key,
+ "search_value":search_value,
+ "flags":flags})
Why not use the same format as other calls for rpc call? eg.
return self._tp.rpc('access_groups', _del_self(locals()))
Post by Gris Ge
+
+ """
+ lsm.Client.volume_mask(self, volume, access_group, access_type,
+ flags=0)
+
+ Mask certain volume to defined access group.
+ To change access_type of existing mask info, just invoke this
+ method with new access_type.
+ Storage will never mask single volume to single access group with
+ two or more LUN IDs, duplicated call of this method will not
+ resulting mulitpal masking for single voluem to single access
+ group.
+ For those storage array which does not support changing existing
+ masking relationship, they will deleting existing and creating new
+ one.
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ access_type # Integer. Check lsm.MaskInfo.access_type property.
+ flags # Reserved for future use, should be 0.
+ lsm.MaskInfo # Object of lsm.MaskInfo.
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT
ErrorNumber.NO_SUPPORT exists
Post by Gris Ge
+ # Does not support
+ lsm.ErrorNumber.VOL_MASK_INVALID_VOLUME
+ # Invalid lsm.Volume
ErrorNumber.INVALID_VOLUME exists
Post by Gris Ge
+ lsm.ErrorNumber.VOL_MASK_INVALID_ACCESS_GROUP
+ # Invalid lsm.AccessGroup
ErrorNumber.INVALID_ACCESS_GROUP exists
Post by Gris Ge
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RW
+ # RW access type is not support by storage system
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RO
+ # RO access type is not support by storage system
+ lsm.ErrorNumber.VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RW
+ # Defined volume does not allow RW access
+ # Example: DR(Disaster Recover) site volumes does not
+ # allow RW access.
+ lsm.ErrorNumber.VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RO
+ # Defined volume does not allow RO access.
Are we adding the ability to query for these features too, though the
capability call?
Post by Gris Ge
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ lsm.Capabilities.VOLUME_MASK
+ # Indicate current systems support volume masking via
+ # lsm.Client.volume_mask() method.
+ """
+ return self._tp.rpc('volume_mask', _del_self(locals()))
+
+ def volume_unmask(self, mask_info=None, volume=None, access_group=None,
+ """
+ lsm.Client.volume_unmask(self, mask_info=None, volume=None,
+ access_group=None, flags=0)
+
+ Unmask the volume.
+ * lsm.Client.volume_unmask(self, mask_info=lsm.MaskInfo, flags=0)
+ * lsm.Client.volume_unmask(self, volume=lsm.Volume,
+ access_group=lsm.AccessGroup, flags=0)
+ The 'mask_info' parameter should not be used with 'volume' or
+ access_group parameters, a LsmError will be raise if so.
+ If define volume is not masked to access group, this method will
+ do nothing but return.
My preference would be to either:

Support this operation which only takes the mask_info or break this into
two different calls, one which takes the mask_info and another call
which takes the volume and access group.

eg.

def volume_unmask_by_mask_info(self, mask_info, flags)
def volume_unmask_by_acess_group(self, access_group, volume, flags)

C API Users can't use named parameters so they would always need to fill
out un-used parameters with NULL. To support this much flexibility
reminds me of the single pool create method which you broke into 4
different calls. Makes it easier to call the method correctly as you
don't need to know about implied relationships between the arguments.
Post by Gris Ge
+ mask_info # Object of lsm.Maskino
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ flags # Reserved for future use, should be 0.
+ None # Unmask done without error.
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_PARAMETER
+ # Neither 'mask_info' nor ('volume' and 'access_group')
+ # is define.
Replace with ErrorNumber.INVALID_ARGUMENT
Post by Gris Ge
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_MASK_INFO
+ # Invalid lsm.MaskInfo
Add, ErrorNumber.INVALID_MASKINFO
Post by Gris Ge
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_VOLUME
+ # Invalid lsm.Volume
ErrorNumber.INVALID_VOLUME exists.
Post by Gris Ge
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_ACCESS_GROUP
+ # Invalid lsm.AccessGroup
ErrorNumber.INVALID_ACCESS_GROUP exists.
Post by Gris Ge
+ lsm.ErrorNumber.VOL_UNMASK_NO_SUPPORT
+ # Does not support volume unmask.
+ lsm.ErrorNumber.VOL_UNMASK_CONFLICT_PARAMETERS
+ # Got 'mask_info' parameters with 'volume' or
+ # 'access_group' parameters
+ lsm.ErrorNumber.VOL_UNMASK_MISSING_VOLUME
+ # Only got 'access_group' parameter defined.
+ lsm.ErrorNumber.VOL_UNMASK_MISSING_ACCESS_GROUP
+ # Only got 'volume' parameter defined.
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ lsm.Capabilities.VOLUME_UNMASK
+ # Indicate current systems support volume unmasking via
+ # lsm.Client.volume_unmask() method.
+ """
+ if (mask_info is not None and volume is not None) or \
+ raise LsmError(ErrorNumber.VOL_UNMASK_CONFLICT_PARAMETERS,
+ "Argument conflicting: both 'mask_info' and "
+ "('volume' or/and 'access_group') are defined.")
+ raise LsmError(ErrorNumber.VOL_UNMASK_MISSING_ACCESS_GROUP,
+ "Only 'volume' is defined in volume_unmask()"
+ "parameter, missing 'access_group'.")
+ raise LsmError(ErrorNumber.VOL_UNMASK_MISSING_VOLUME,
+ "Only 'access_group' is defined in volume_unmask()"
+ "parameter, missing 'volume'.")
+ raise LsmError(ErrorNumber.VOL_UNMASK_INVALID_PRAMETER,
+ "Neither 'mask_info' nor ('volume' and "
+ "'access_group') is defined")
Today we have the generic error INVALID_ARGUMENT. We also have error
codes for invalid classes which we use heavily in the C code with magic
number checks to make sure pointer if of the correct type.

Can we leverage the generic INVALID_ARGUMENT instead of introducing
specific error code for each parameter? Otherwise the API error codes
is going to get huge when we go back and make the other calls match what
you are doing here.


Note: VOL_UNMASK_INVALID_PRAMETER does not exist in this patch and it
has a typo.
Post by Gris Ge
+ return self._tp.rpc('volume_unmask', _del_self(locals()))
+
## Returns an array of disk objects
"""
return self._tp.rpc('disks', _del_self(locals()))
- ## Access control for allowing an access group to access a volume
- """
- Allows an access group to access a volume.
- """
- return self._tp.rpc('access_group_grant', _del_self(locals()))
-
- ## Revokes access to a volume to initiators in an access group
- """
- Revokes access for an access group for a volume
- """
- return self._tp.rpc('access_group_revoke', _del_self(locals()))
-
- ## Returns a list of access group objects
@_return_requires([AccessGroup])
- """
- Returns a list of access groups
- """
- return self._tp.rpc('access_group_list', _del_self(locals()))
-
- ## Creates an access a group with the specified initiator in it.
+ """
+ lsm.Client.access_groups(self, search_key=None, search_value=None,
+ flags=0)
+
+ Return a list of objects of lsm.AccessGroup.
+ If 'search_key' and 'search_value' is defined, only access group
+ matches will be contained in return list.
+ Valid search keys are stored in
+ id # Search lsm.AccessGroup.id property
+ access_group_id # Search lsm.AccessGroup.id property
+ name # Search lsm.AccessGroup.name property
+ init_id # If given init_id is contained in
+ # lsm.AccessGroup.init_ids
+ volume_id # If access group is masked to given volume.
+ system_id # Return access group for given system only.
+ When no matches found, return a empty list
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # lsm.AccessGroup.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.AccessGroup object will
+ # # contain optional properties.
+ # # But currently, there is no optional
+ # # property for lsm.AccessGroup yet, this
+ # # will make no difference.
+ [lsm.AccessGroup,] # A list of lsm.AccessGroup objects.
+ or
+ [] # Nothing found.
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query.
+ lsm.ErrorNumber.AG_QUERY_INVALID_SEARCH_KEY
+ # Got invalid search key. Should be one of
+ # lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
+ lsm_cli_obj = lsm.Client('sim://')
+ access_groups = lsm_cli_obj.access_groups(
+ search_key=None, search_value=None, flags=0)
+ lsm.Capabilities.ACCESS_GROUPS
+ # Indicate current systems support access group query via
+ # lsm.Client.access_groups() method.
+ lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all access
+ # groups without 'search_key' parameter, then filter in user's
+ # code space.
+ """
+ raise LsmError(ErrorNumber.AG_QUERY_INVALID_SEARCH_KEY,
+ "Invalid search key %s" % search_key)
+
+ # Just save plugin's code lines.
+ search_key = 'id'
+
+ return self._tp.rpc('access_groups',
+ {"search_key":search_key,
+ "search_value":search_value,
+ "flags":flags})
Why not use the same format as other calls for rpc call? eg.
return self._tp.rpc('access_groups', _del_self(locals()))
Post by Gris Ge
+
+ ## Creates an access group with the specified initiators in it.
@_return_requires(AccessGroup)
- def access_group_create(self, name, initiator_id, id_type, system_id,
- """
- Creates an access group and add the specified initiator id, id_type and
- desired access.
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ """
+ lsm.Client.access_group_create(self, access_group_name, init_ids,
+ init_type, system_ids, flags=0)
+
+ Create a access group.
+ access_group_name # String. The name for new access group.
+ # If provided access_group_name does not meet
+ # the requirement of storage system, storage
+ # system will chose a valid one in stead of
+ # raise a error.
+ init_ids # List of string. The initiator IDs.
+ init_type # Integer. The type of initiator, could be one
+ # * lsm.AccessGroup.INIT_TYPE_WWPN
+ # * lsm.AccessGroup.INIT_TYPE_WWNN
+ # * lsm.AccessGroup.INIT_TYPE_HOSTNAME
+ # * lsm.AccessGroup.INIT_TYPE_ISCSI_IQN
+ # * lsm.AccessGroup.INIT_TYPE_SAS
+ system_id # The id of lsm.System where this access group
+ # should create on.
+ flags # Reserved for future use, should be 0.
+ lsm.AccessGroup # Object of lsm.AccessGroup
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT
+ # Does not support creating access group.
+ lsm.ErrorNumber.AG_CREATE_INVALID_INIT_ID
+ # WWPN does not in (?:[0-9a-f]{2}:){7}[0-9a-f]{2} format.
+ # Its user's responsibity for convering init_ids into lsm
+ # requested format.
+ lsm.ErrorNumber.AG_CREATE_INVALID_INIT_TYPE
+ # * lsm.AccessGroup.INIT_TYPE_OTHER
+ # * lsm.AccessGroup.INIT_TYPE_UNKNOWN
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_WWPN
+ # System does not support WWPN initiator type.
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_WWNN
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_HOSTNAME
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_ISCSI_IQN
+ lsm.ErrorNumber.AG_CREATE_NO_SUPPORT_SAS
+ lsm.ErrorNumber.AG_CREATE_EXCEED_LIMITATION_INIT_COUNT
+ # System is reaching limitation of access group initator
+ # count. Example, if a system only allow up to 10
+ # initiators in one access group, if user provides 11 or
+ # more initiators, this error will be raised.
+ lsm.ErrorNumber.AG_CREATE_EXCEED_LIMITATION_AG_COUNT
+ # System is reaching limitation of total access group
+ # counts. Example, if a system only allow 100 access
+ # groups in total, when trying to create the 101 one, this
+ # error will be raised.
Same questions as above, can we utilize existing, can be utilize more
generic error values?
Post by Gris Ge
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ lsm.Capabilities.ACCESS_GROUP_CREATE
+ # Indicate current systems support create access group via
+ # lsm.Client.access_group_create() method.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWPN
+ # Support creating WWPN access group.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWNN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_HOSTNAME
+ lsm.Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_SAS
"""
return self._tp.rpc('access_group_create', _del_self(locals()))
## Deletes an access group.
- """
- Deletes an access group
- """
- return self._tp.rpc('access_group_del', _del_self(locals()))
-
- ## Adds an initiator to an access group
- def access_group_add_initiator(self, group, initiator_id, id_type,
- """
- Adds an initiator to an access group
- """
- return self._tp.rpc('access_group_add_initiator', _del_self(locals()))
-
- ## Deletes an initiator from an access group
@_return_requires(None)
- """
- Deletes an initiator from an access group
- """
- return self._tp.rpc('access_group_del_initiator', _del_self(locals()))
-
- ## Returns the list of volumes that access group has access to.
- """
- Returns the list of volumes that access group has access to.
- """
- return self._tp.rpc('volumes_accessible_by_access_group',
- _del_self(locals()))
-
- ##Returns the list of access groups that have access to the specified
- #volume.
- """
- Returns the list of access groups that have access to the specified
- volume.
- """
- return self._tp.rpc('access_groups_granted_to_volume',
- _del_self(locals()))
+ """
+ lsm.Client.access_group_delete(self, access_group, flags=0)
+
+ Delete a access group. Only access group has no volume masked to
+ can be deleted.
+ User can use this method to query volumes masked to give access
+ lsm.Client.volumes(self, search_key='access_group_id',
+ search_value=access_group.id)
+ access_group # Object of lsm.AccessGroup to delete.
+ flags # Reserved for future use, should be 0.
+ None # Access group deleted without errors.
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.AG_DELETE_NO_SUPPORT
+ # Does not support deleting a access group.
ErrorNumber.NO_SUPPORT
Post by Gris Ge
+ lsm.ErrorNumber.AG_DELETE_IS_MASKED
+ # Access group has volume masked to.
ErrorNumber.IS_MASKED or introduce a generic IN_USE?
Post by Gris Ge
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ lsm_cli_obj.access_group_delete(new_ag)
+ lsm.Capabilities.ACCESS_GROUP_DELETE
+ # Indicate current systems support delete access group via
+ # lsm.Client.access_group_delete() method.
+ """
+ return self._tp.rpc('access_group_delete', _del_self(locals()))
+
+ ## Update an existing access group with new initiators.
+ """
+ lsm.Client.access_group_edit(self, access_group, new_init_ids, flags=0)
+
+ Change the initiators of certain access group.
+ access_group # Object of lsm.AccessGroup to edit.
+ new_init_ids # List of string. New initiator IDs. Old one will
+ # be overided.
+ flags # Reserved for future use, should be 0.
+ None # Access group edited without errors.
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.AG_EDIT_NO_SUPPORT
+ # Does not support deleting a access group.
ErrorNumber.NO_SUPPORT
Post by Gris Ge
+ lsm.ErrorNumber.AG_EDIT_INVALID_INIT_ID
+ # Provide new_init_ids contain ilegal string.
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ # Remove one WWPN out.
+ lsm_cli_obj.access_group_edit(new_ag, ['20:00:00:1b:21:67:65:d7'])
+ lsm.Capabilities.ACCESS_GROUP_EDIT
+ # Indicate current systems support edit access group member
+ # via lsm.Client.access_group_edit() method.
+ """
Comment on the documentation. Please add something to the effect that
the new_init_ids replaces existing just to make sure the user it
prefectly clear what the behavior is.
Post by Gris Ge
+ return self._tp.rpc('access_group_edit', _del_self(locals()))
## Checks to see if a volume has child dependencies.
diff --git a/lsm/lsm/_common.py b/lsm/lsm/_common.py
index 19b0a24..fbf9c67 100644
--- a/lsm/lsm/_common.py
+++ b/lsm/lsm/_common.py
DISK_BUSY = 500
VOLUME_BUSY = 501
+ AG_QUERY_INVALID_SEARCH_KEY = 600
+ # Reserve 60x for AG_QUERY_XXX
+
+ AG_CREATE_NO_SUPPORT = 611
+ AG_CREATE_INVALID_INIT_ID = 612
+ AG_CREATE_INVALID_INIT_TYPE = 613
+ AG_CREATE_NO_SUPPORT_WWPN = 614
+ AG_CREATE_NO_SUPPORT_WWNN = 615
+ AG_CREATE_NO_SUPPORT_HOSTNAME = 616
+ AG_CREATE_NO_SUPPORT_ISCSI_IQN = 617
+ AG_CREATE_NO_SUPPORT_SAS = 618
+ AG_CREATE_EXCEED_LIMITATION_INIT_COUNT = 619
+ AG_CREATE_EXCEED_LIMITATION_AG_COUNT = 610
+ # Reserve 62x for AG_CREATE_XXX
+
+ AG_DELETE_NO_SUPPORT = 630
+ AG_DELETE_IS_MASKED = 631
+ # Reserve 63x for AG_DELETE_XXX
+
+ AG_EDIT_NO_SUPPORT = 640
+ AG_EDIT_INVALID_INIT_ID = 641
+ # Reserve 64x for AG_EDIT_XXX
+
+ MASK_QUERY_INVALID_SEARCH_KEY = 650
+ # Reserve 65x for MASK_QUERY_XXX
+
+ VOL_MASK_NO_SUPPORT = 660
+ VOL_MASK_INVALID_VOLUME = 661
+ VOL_MASK_INVALID_ACCESS_GROUP = 662
+ VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RW = 663
+ VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RO = 664
+ VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RW = 665
+ VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RO = 666
+ # Reserve 66x for MASK_QUERY_XXX
+ # Reserve 67x for MASK_QUERY_XXX
+
+ VOL_UNMASK_INVALID_PARAMETER = 680
+ VOL_UNMASK_INVALID_MASK_INFO = 681
+ VOL_UNMASK_INVALID_VOLUME = 682
+ VOL_UNMASK_INVALID_ACCESS_GROUP = 683
+ VOL_UNMASK_NO_SUPPORT = 684
+ VOL_UNMASK_CONFLICT_PARAMETERS = 685
+ VOL_UNMASK_MISSING_VOLUME = 686
+ VOL_UNMASK_MISSING_ACCESS_GROUP = 687
+
INPROGRESS = 1
diff --git a/lsm/lsm/_data.py b/lsm/lsm/_data.py
index 603bf16..219f3bd 100644
--- a/lsm/lsm/_data.py
+++ b/lsm/lsm/_data.py
@default_property('id', doc="Unique identifier")
- """
- Represents an initiator.
- """
- (TYPE_OTHER, TYPE_PORT_WWN, TYPE_NODE_WWN, TYPE_HOSTNAME, TYPE_ISCSI,
- TYPE_SAS) = (1, 2, 3, 4, 5, 7)
-
- _type_map = {1: 'Other', 2: 'Port WWN', 3: 'Node WWN', 4: 'Hostname',
- 5: 'iSCSI', 7: "SAS"}
-
- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'type': 'Type',
- }
-
- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'type']
-
- def _value_convert(self, key_name, value, human, enum_as_number,
- value = Initiator._type_to_str(value)
- return value
-
- return Initiator._type_map[init_type]
-
-
- name = "Unsupported"
-
- self._id = _id # Identifier
- self._type = _type # Initiator type id
- self._name = _name # Initiator name
-
-
@default_property('name', doc="Disk name (aka. vendor)")
@default_property('disk_type', doc="Enumerated type of disk")
@default_property('block_size', doc="Size of each block")
@default_property('id', doc="Unique instance identifier")
@default_property('name', doc="Access group name")
doc="List of initiators IDs"
Post by Gris Ge
@default_property('system_id', doc="System identifier")
- self._id = _id
- self._name = _name # AccessGroup name
- self._initiators = _initiators # List of initiators
- self._system_id = _system_id # System id this group belongs
- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'initiators': 'Initiator IDs',
- 'system_id': 'System ID',
- }
+ FLAG_RETRIEVE_FULL_INFO = 1 << 0
- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'initiators', 'system_id']
- _OPT_PROPERTIES_SEQUENCE = []
+ SUPPORTED_SEARCH_KEYS = ['id', 'access_group_id', 'name', 'init_ids',
+ 'volume_id', 'system_id']
- def _value_convert(self, key_name, value, human, enum_as_number,
- value = ','.join(str(x) for x in value)
- return value
+ INIT_TYPE_UNKNOWN = 0
+ INIT_TYPE_OTHER = 1
+ INIT_TYPE_WWPN = 2
+ INIT_TYPE_WWNN = 3
+ INIT_TYPE_HOSTNAME = 4
+ INIT_TYPE_ISCSI_IQN = 5
+ INIT_TYPE_SAS = 6
+
+ OPT_PROPERTIES = []
+
+ def __init__(self, _id, _name, _init_ids, _init_type, _system_id,
+ self._id = _id
+ self._name = _name # AccessGroup name
+ self._init_ids = _init_ids # List of initiators
+ self._init_type = _init_type # Initiator type
+ self._system_id = _system_id # System id this group belongs
+
+ self._optional_data = OptionalData()
+ #Make sure the properties only contain ones we permit
+ allowed = set(AccessGroup.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ self._optional_data = _optional_data
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))
VOLUME_ONLINE = 34
VOLUME_OFFLINE = 35
- ACCESS_GROUP_GRANT = 36
- ACCESS_GROUP_REVOKE = 37
- ACCESS_GROUP_LIST = 38
- ACCESS_GROUP_CREATE = 39
- ACCESS_GROUP_DELETE = 40
- ACCESS_GROUP_ADD_INITIATOR = 41
- ACCESS_GROUP_DEL_INITIATOR = 42
+ ACCESS_GROUPS = 36
+ ACCESS_GROUPS_QUICK_SEARCH = 37
+ ACCESS_GROUP_CREATE = 38
+ ACCESS_GROUP_DELETE = 39
+ ACCESS_GROUP_EDIT = 39
- VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
- ACCESS_GROUPS_GRANTED_TO_VOLUME = 44
+ ACCESS_GROUP_CREATE_WWPN = 40
+ ACCESS_GROUP_CREATE_WWNN = 41
+ ACCESS_GROUP_CREATE_HOSTNAME = 42
+ ACCESS_GROUP_CREATE_ISCSI_IQ = 43
+ ACCESS_GROUP_CREATE_SAS = 44
VOLUME_CHILD_DEPENDENCY = 45
VOLUME_CHILD_DEPENDENCY_RM = 46
- INITIATORS = 47
- INITIATORS_GRANTED_TO_VOLUME = 48
-
- VOLUME_INITIATOR_GRANT = 50
- VOLUME_INITIATOR_REVOKE = 51
- VOLUME_ACCESSIBLE_BY_INITIATOR = 52
VOLUME_ISCSI_CHAP_AUTHENTICATION = 53
VOLUME_THIN = 55
+ MASK_INFOS = 60
+ MASK_INFOS_QUICK_SEARCH = 61
+ VOLUME_MASK = 62
+ VOLUME_UNMASK = 63
+
#File system
FS = 100
FS_DELETE = 101
self.desc = description
self.version = plugin_version
+
+ ACCESS_TYPE_UNKNOWN = 0
+ ACCESS_TYPE_READ_WRITE = 1
+ ACCESS_TYPE_READ_ONLY = 2
+
+ OPT_PROPERTIES = []
+
+ SUPPORTED_SEARCH_KEYS = ['id', 'volume_id', 'access_group_id',
+ 'system_id']
+
+ def __init__(self, _id, _volume_id, _access_group_id, _access_type,
+ self._id = _id
+ self._volume_id = _volume_id
+ self._access_group_id = _access_group_id
+ self._access_type = _access_type
+ self._system_id = _system_id
+
+ self._optional_data = OptionalData()
+ #Make sure the properties only contain ones we permit
+ allowed = set(MaskInfo.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ self._optional_data = _optional_data
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))
+
#TODO Need some unit tests that encode/decode all the types with nested
Thanks,
Tony
Gris Ge
2014-04-22 10:36:15 UTC
Permalink
Post by Tony Asleson
Comments below...
Post by Gris Ge
+ return self._tp.rpc('mask_infos',
+ {"search_key":search_key,
+ "search_value":search_value,
+ "flags":flags})
Why not use the same format as other calls for rpc call? eg.
return self._tp.rpc('access_groups', _del_self(locals()))
Will change it back. It was copied from access_groups() where search_key
will be updated.
Post by Tony Asleson
Post by Gris Ge
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT
ErrorNumber.NO_SUPPORT exists
Post by Gris Ge
+ # Does not support
+ lsm.ErrorNumber.VOL_MASK_INVALID_VOLUME
+ # Invalid lsm.Volume
ErrorNumber.INVALID_VOLUME exists
Post by Gris Ge
+ lsm.ErrorNumber.VOL_MASK_INVALID_ACCESS_GROUP
+ # Invalid lsm.AccessGroup
ErrorNumber.INVALID_ACCESS_GROUP exists
Check below for detail discuss.
Post by Tony Asleson
Post by Gris Ge
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RW
+ # RW access type is not support by storage system
+ lsm.ErrorNumber.VOL_MASK_NO_SUPPORT_ACCESS_TYPE_RO
+ # RO access type is not support by storage system
+ lsm.ErrorNumber.VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RW
+ # Defined volume does not allow RW access
+ # Example: DR(Disaster Recover) site volumes does not
+ # allow RW access.
+ lsm.ErrorNumber.VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RO
+ # Defined volume does not allow RO access.
Are we adding the ability to query for these features too, though the
capability call?
I have no real world experience with this kind of masking failure.
We can discuss that if we have any array do fail for that reason.
Quite rare change will user hit this error.
Post by Tony Asleson
Support this operation which only takes the mask_info or break this into
two different calls, one which takes the mask_info and another call
which takes the volume and access group.
eg.
def volume_unmask_by_mask_info(self, mask_info, flags)
def volume_unmask_by_acess_group(self, access_group, volume, flags)
C API Users can't use named parameters so they would always need to fill
out un-used parameters with NULL. To support this much flexibility
reminds me of the single pool create method which you broke into 4
different calls. Makes it easier to call the method correctly as you
don't need to know about implied relationships between the arguments.
Good point. I will split them into these two:
* volume_unmask(self, volume, access_group, flags=0)
# Just taking identical parameters as volume_mask() do.
* volume_unmask_by_mask_info(self, mask_info, flags=0)
Post by Tony Asleson
Replace with ErrorNumber.INVALID_ARGUMENT
Post by Gris Ge
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_MASK_INFO
+ # Invalid lsm.MaskInfo
Add, ErrorNumber.INVALID_MASKINFO
Post by Gris Ge
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_VOLUME
+ # Invalid lsm.Volume
ErrorNumber.INVALID_VOLUME exists.
Post by Gris Ge
+ lsm.ErrorNumber.VOL_UNMASK_INVALID_ACCESS_GROUP
+ # Invalid lsm.AccessGroup
ErrorNumber.INVALID_ACCESS_GROUP exists.
The same as above. Please let me know your decision.
Post by Tony Asleson
Today we have the generic error INVALID_ARGUMENT. We also have error
codes for invalid classes which we use heavily in the C code with magic
number checks to make sure pointer if of the correct type.
Can we leverage the generic INVALID_ARGUMENT instead of introducing
specific error code for each parameter? Otherwise the API error codes
is going to get huge when we go back and make the other calls match what
you are doing here.
Check below for detail discuss.
Post by Tony Asleson
Note: VOL_UNMASK_INVALID_PRAMETER does not exist in this patch and it
has a typo.
Post by Gris Ge
+ # Just save plugin's code lines.
+ search_key = 'id'
+
+ return self._tp.rpc('access_groups',
+ {"search_key":search_key,
+ "search_value":search_value,
+ "flags":flags})
Why not use the same format as other calls for rpc call? eg.
return self._tp.rpc('access_groups', _del_self(locals()))
I though after I change 'search_key', locals() will still containing the
old value. Just check, it will use the updated one.
I will change back to locals().
Post by Tony Asleson
Post by Gris Ge
+
Same questions as above, can we utilize existing, can be utilize more
generic error values?
ErrorNumber.NO_SUPPORT
Post by Gris Ge
+ lsm.ErrorNumber.AG_DELETE_IS_MASKED
+ # Access group has volume masked to.
ErrorNumber.IS_MASKED or introduce a generic IN_USE?
My suggest on maintaining ErrorNumber:
* Error number different between methods.
# It give user clear knowledge which method gone rogue.
* Clear definition on every error.
# Every error only contain one root cause and has clear suggestion
# on how to fix it. For example:
# INVALID_ARGUMENT might caused:
# * non-exist resource,
# * Incorrect type of resource provided.
# * Unknown enumerate number got in argument.
# If user got this error, they have to __guess__ which part went
# wrong.
# With VG_MASK_NO_EXIST_VOLUME error, its pretty clear user is
# masking a non-exist(might be recently deleted) volume.
* Error count could be goes pretty huge.
# Just checked with you months ago, both C and python can hold a
# large count of error numbers.

You suggest:
* Sharing errors between methods. Example:
INVALID_VOLUME in volume_mask() and volume_unmask()
* Using all-in-one error like INVALID_ARGUMENT for any argument
errors, IN_USE for any resource busy.
# My concern is, all-in-one error will conflict/overlap with
# other detail errors. Some plugin developer will use the
# all-in-one error instead of detailed error.
# For example, when volume_mask() on a RO snapshot with RW
# access_type, plugin developer can easily raise
# INVALID_ARGUMENT error as user defined incorrect access_type.
# But actually, VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RW is the correct
# error to raise.
# Another problem about all-in-one error is user has to guess
# the root cause from all possibilities of all-in-one error.
Post by Tony Asleson
Comment on the documentation. Please add something to the effect that
the new_init_ids replaces existing just to make sure the user it
prefectly clear what the behavior is.
Will do.
Post by Tony Asleson
doc="List of initiators IDs"
Will do.
Post by Tony Asleson
Thanks,
Tony
Thank you for the detailed review and comments.
Best regards.
--
Gris Ge
Tony Asleson
2014-04-22 15:44:43 UTC
Permalink
Post by Gris Ge
Post by Tony Asleson
ErrorNumber.IS_MASKED or introduce a generic IN_USE?
* Error number different between methods.
# It give user clear knowledge which method gone rogue.
The user knows what function they are calling.

I don't see why we need to provide a different error code that means the
same thing across different functions. If we do this then a user cannot
write a common error handler, they have to write a specific error
handler for every function they call or map a number of similar errors
to mean the same thing.

You haven't advocated using a separate return code for every function
that indicates success, why not? This would follow in your thought that
the user needs to know which method call has been successful too.
Post by Gris Ge
* Clear definition on every error.
# Every error only contain one root cause and has clear suggestion
# * non-exist resource,
# * Incorrect type of resource provided.
# * Unknown enumerate number got in argument.
# If user got this error, they have to __guess__ which part went
# wrong.
Yes, this is a valid concern, one which can be addressed by using the
text part of the error message giving more details. Invalid arguments
should be found during development when using the library. Not found by
end users of the library. Users should be getting errors like
NOT_FOUND_VOLUME when making a call that uses a volume as an argument.
Post by Gris Ge
# With VG_MASK_NO_EXIST_VOLUME error, its pretty clear user is
# masking a non-exist(might be recently deleted) volume.
The user gets the same information when getting a return code
NOT_FOUND_VOLUME which can be reused on any function that you pass a
volume. The only thing being added is the function call, which the user
already knows.
Post by Gris Ge
* Error count could be goes pretty huge.
# Just checked with you months ago, both C and python can hold a
# large count of error numbers.
INVALID_VOLUME in volume_mask() and volume_unmask()
If they mean the same thing, most definitely yes.
Post by Gris Ge
* Using all-in-one error like INVALID_ARGUMENT for any argument
errors, IN_USE for any resource busy.
# My concern is, all-in-one error will conflict/overlap with
# other detail errors. Some plugin developer will use the
# all-in-one error instead of detailed error.
# For example, when volume_mask() on a RO snapshot with RW
# access_type, plugin developer can easily raise
# INVALID_ARGUMENT error as user defined incorrect access_type.
# But actually, VOL_MASK_NOT_ALLOW_ACCESS_TYPE_RW is the correct
# error to raise.
In this specific case, yes we should have a more specific error code
than INVALID_ARGUMENT because the value is in the domain of valid
values. However, I would still advocate that it isn't a function
specific error code.

Regards,
Tony
Gris Ge
2014-04-23 08:15:14 UTC
Permalink
Post by Tony Asleson
In this specific case, yes we should have a more specific error code
than INVALID_ARGUMENT because the value is in the domain of valid
values. However, I would still advocate that it isn't a function
specific error code.
Regards,
Tony
OK. You have convinced me. I will submit the V2 patch and update the
wiki document.

Thanks.
Best regards.
--
Gris Ge
Gris Ge
2014-04-21 14:44:47 UTC
Permalink
* Removed old volume masking commands:
access-grant
access-group-grant
access-revoke
access-group-revoke
volumes_accessible_by_access_group
volumes-accessible-initiator
access-group-volumes
initiators-granted-volume
* Added new commands:
volume-mask
volume-unmask
* New list type:
MASK_INFOS
* New options for list command on filtering:
--vol
--ag
--sys
# Currently only supported by list type: MASK_INFOS and ACCESS_GROUPS
* When '-o, --optional' define, the display method will be forced to script
way.

Signed-off-by: Gris Ge <***@redhat.com>
---
doc/man/lsmcli.1.in | 131 +++++++--------
lsm/lsm/_cmdline.py | 362 ++++++++++++++++-------------------------
lsm/lsm/lsmcli_data_display.py | 151 +++++++++++++----
3 files changed, 315 insertions(+), 329 deletions(-)

diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..0db7670 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -117,7 +117,7 @@ circumstance, \fb-b\fR will be effective, command will wait until finished.
\fB-s\fR, \fB--script\fR
Displaying data in script friendly way.
.br
-Without this option, data is displayed in this manner:
+Without this option, data is displayed in this column way:

ID | Name | Member IDs
-------------------------------------+-------+-------------
@@ -126,7 +126,7 @@ Without this option, data is displayed in this manner:
aa0ffc70-3dba-11df-b8cf-00a0980ad71b | aggr1 | ID_C
| | ID_D

-With this option, data is displayed in this manner.
+With this option, data is displayed in this script way.

--------------------------------------------------
ID | 87720e90-3a83-11df-b8bf-00a0980ad71b
@@ -139,6 +139,11 @@ With this option, data is displayed in this manner.
Member IDs | ID_C
| ID_D
--------------------------------------------------
+Please note:
+Not all mandatory properties will be displayed in column way considering the
+user's console text width. All mandatory properties will be displayed in
+script way. When with "-o, --optional", both mandatory properties and optional
+properties will be displayed.

.SH COMMANDS
.SS list
@@ -147,11 +152,25 @@ List information on LSM objects
\fB--type\fR \fI<TYPE>\fR
Required. Valid values are:
.br
-\fBVOLUMES\fR, \fBINITIATORS\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR,
+\fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR, \fBMASK_INFOS\fR,
\fBEXPORTS\fR, \fBDISKS\fR,
.br
\fBNFS_CLIENT_AUTH\fR, \fBACCESS_GROUPS\fR,
\fBSYSTEMS\fR, \fBPLUGINS.
+.br
+The \fBVOLUMES\fR well-known as LUN by many storage array vendors. It is a
+virtual storage space which host operation system treated as a or many block
+device(s).
+.br
+The \fBACCESS_GROUPS\fR define a group of initiators to access volume.
+For example, most FC/FCoE HBA cards has two ports, each of two ports has
+one(or more with NPIV) WWPN(s). When performing volume masking, a volume is
+often mask to a access group containing all of these WWPNs. On EMC devices,
+access group is refered as "Host Group". On NetApp ONTAP devices, access group
+is refered as "Initiator Group(igroup)".
+.br
+The \fBMASK_INFOS\fR reprent a relationship between access group and volume.
+The volume masking is well-known as "LUN Masking".
.TP
\fB--fs\fR \fI<FS_ID>\fR
Required for \fB--type\fR=\fBSNAPSHOTS\fR. List the snapshots of certain
@@ -160,7 +179,18 @@ PLUGINS will list all supported plugins of LSM, not only the current one.
.TP
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
-available.
+available. When define, will use '--script' display method.
+By default(without this argument), only mandatory properties will be
+retrieved.
+.TP
+\fB--vol\fR \fI<VOL_ID>\fR
+Optional. Filter with volume ID: <VOL_ID>
+.TP
+\fB--ag\fR \fI<AG_ID>\fR
+Optional. Filter with access group ID: <AG_ID>
+.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Optional. Filter with system ID: <SYS_ID>

.SS job-status
Retrieve information about a job.
@@ -281,43 +311,35 @@ Removes volume dependencies(like replication).
\fB--vol\fR \fI<VOL_ID>\fR
Required. The ID of volume to remove dependency.

-.SS volume-access-group
-Lists the access group(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS volumes-accessible-initiator
-Lists the initiator(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS access-group-grant
+.SS volume-mask
.TP 15
-Grant a access group the RO or RW access to certain volume. Like LUN masking
-or NFS export.
+Mask a volume to certain access group. When defined volume is already masked
+to defined access group, the new '--access' will overide the old one.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to access.
+Required. The ID of volume to mask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to grant.
-
+Required. ID of access group to mask.
.TP
\fB--access\fR \fI<ACCESS>\fR
-Optional. Permission of access, valid values are \fBRO\fR, \fBRW\fR. Default
-value is \fBRW\fR.
+Optional. Define the access type, valid values are \fBRW\fR and \fBRO\fR,
+stand for "Read and Write" and "Read Only". Default value is RW.

-.SS access-group-revoke
+.SS volume-unmask
.TP 15
-Revoke an access group the RO or RW access to certain volume.
+Unmask a volume from certain access group. Two ways to unmask a volume:
+ * \fBlsmcli volume-unmask --mask <MASK_ID>\fR
+ * \fBlsmcli volume-unmask --vol <VOL_ID> --ag <AG_ID>\fR
+.TP
+\fB--mask\fR \fI<MASK_ID>\fR
+Optional. The ID of mask to unmask.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
+Optional. The ID of volume to unmask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to revoke.
+Optional. ID of access group to unmask.

.SS access-group-create
.TP 15
@@ -327,77 +349,36 @@ Create an access group.
Required. The human friendly name for new access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. The first initiator ID of new access group.
+Required. Repeatable. The initiator ID of new access group. For more than one
+initiators, use this: '\fB--init INIT_ID_1 --init INIT_ID_2\fR'.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
+\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR. All initiators defined should be in
+same initiator type.
.TP
\fB--sys\fR \fI<SYS_ID>\fR
Required. The ID of system where this access group should reside on.

-.SS access-group-add
+.SS access-group-edit
Adds an initiator to an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to add.
+Required. Repeatable. New initiator ID.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.

-.SS access-group-remove
-Removes an initiator from an access group.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. ID of access group.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to remove.
-
.SS access-group-delete
Delete an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group to delete.

-.SS access-grant
-Grants access to an initiator to a volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to grant access.
-.TP
-\fB--init-type\fR \fI<INIT_TYPE>\fR
-Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
-.TP
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to grant access.
-
-.SS access-revoke
-Removes access for an initiator to a volume
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to revoke.
-
-.SS access-group-volumes
-Lists the volumes that the access group has been granted access to.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to query.
-
-.SS initiators-granted-volume
-Lists the initiators that have been granted access to specified volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. The ID of initiator to query.
-
.SS iscsi-chap
Configures ISCSI inbound/outbound CHAP authentication
.TP 15
diff --git a/lsm/lsm/_cmdline.py b/lsm/lsm/_cmdline.py
index 778647e..821e5f0 100644
--- a/lsm/lsm/_cmdline.py
+++ b/lsm/lsm/_cmdline.py
@@ -23,8 +23,8 @@ from argparse import ArgumentParser
from argparse import RawTextHelpFormatter

from lsm import (Client, Pool, VERSION, LsmError, Capabilities, Disk,
- Initiator, Volume, JobStatus, ErrorNumber, BlockRange,
- uri_parse)
+ Volume, JobStatus, ErrorNumber, BlockRange, uri_parse,
+ AccessGroup, MaskInfo)

from _data import PlugData
from _common import getch, size_human_2_size_bytes, Proxy
@@ -92,7 +92,7 @@ def _get_item(l, the_id, friendly_name='item'):
return i
raise ArgError('%s with id %s not found!' % (friendly_name, the_id))

-list_choices = ['VOLUMES', 'INITIATORS', 'POOLS', 'FS', 'SNAPSHOTS',
+list_choices = ['VOLUMES', 'MASK_INFOS', 'POOLS', 'FS', 'SNAPSHOTS',
'EXPORTS', "NFS_CLIENT_AUTH", 'ACCESS_GROUPS',
'SYSTEMS', 'DISKS', 'PLUGINS']

@@ -142,6 +142,7 @@ ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access group ID')
init_id_opt = dict(name='--init', metavar='<INIT_ID>', help='Initiator ID')
snap_id_opt = dict(name='--snap', metavar='<SNAP_ID>', help='Snapshot ID')
export_id_opt = dict(name='--export', metavar='<EXPORT_ID>', help='Export ID')
+mask_id_opt = dict(name='--mask', metavar='<MASK_ID>', help='Mask ID')

size_opt = dict(name='--size', metavar='<SIZE>', help=size_help)
access_opt = dict(name='--access', metavar='<ACCESS>', help=access_help,
@@ -165,13 +166,22 @@ cmds = (
type=str.upper),
],
optional=[
- dict(name=('-a', '--all'),
- help='Retrieve and display in script friendly way with ' +
- 'all information including optional data if available',
+ dict(name=('-o', '--optional'),
+ help='Retrieve both mandatory and optional properties.\n'
+ 'Once define, will use "-s, --script" display way',
default=False,
- dest='all',
+ dest='optional',
action='store_true'),
dict(fs_id_opt),
+ dict(name=('--vol'),
+ metavar='<VOL_ID>',
+ help='Filter with defined volume ID'),
+ dict(name=('--ag'),
+ metavar='<AG_ID>',
+ help='Filter with defined access group ID'),
+ dict(name=('--sys'),
+ metavar='<SYS_ID>',
+ help='Filter with defined system ID'),
],
),

@@ -231,6 +241,31 @@ cmds = (
),

dict(
+ name='volume-mask',
+ help='Mask a volume to certain access group',
+ args=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ ],
+ optional=[
+ dict(access_opt),
+ ],
+ ),
+
+ dict(
+ name='volume-unmask',
+ help='Unmask a volume from certain access group via mask ID or ' +
+ 'combination of volume ID and access group ID',
+ args=[
+ ],
+ optional=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ dict(mask_id_opt),
+ ],
+ ),
+
+ dict(
name='volume-replicate',
help='Creates a new volume and peplicates provided volume to it.',
args=[
@@ -297,78 +332,19 @@ cmds = (
),

dict(
- name='volume-access-group',
- help='Lists the access group(s) that have access to volume',
- args=[
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='volumes-accessible-initiator',
- help='Lists the volumes that are accessible by the initiator',
- args=[
- dict(init_id_opt),
- ],
- ),
-
-
- dict(
- name='access-group-grant',
- help='Grants access to an access group to a volume, '
- 'like LUN Masking',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-group-revoke',
- help='Revoke the access of certain access group to a volume',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
name='access-group-create',
help='Create an access group',
args=[
dict(name='--name', metavar='<AG_NAME>',
help="Human readble name for access group"),
- # TODO: _client.py access_group_create should support multipal
- # initiators when creating.
- dict(init_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
dict(init_type_opt),
dict(sys_id_opt),
],
),

- # TODO: Merge access_group_add() and access_group_remove() into
- # access_group_mofidy(ag_id, new_inits, init_type, flags)
- dict(
- name='access-group-add',
- help='Add an initiator into existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- dict(init_type_opt),
- ],
- ),
- dict(
- name='access-group-remove',
- help='Remove an initiator from existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- ],
- ),
-
dict(
name='access-group-delete',
help='Deletes an access group',
@@ -378,42 +354,13 @@ cmds = (
),

dict(
- name='access-grant',
- help='Grants access to an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(init_type_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-revoke',
- help='Removes access for an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='access-group-volumes',
- help='Lists the volumes that the access group has'
- ' been granted access to',
+ name='access-group-edit',
+ help='Edit the initiators of an access group',
args=[
dict(ag_id_opt),
- ],
- ),
-
- dict(
- name='initiators-granted-volume',
- help='Lists the initiators that have been '
- 'granted access to specified volume',
- args=[
- dict(vol_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
],
),

@@ -720,6 +667,28 @@ class CmdLine:
"""
Command line interface class.
"""
+ @staticmethod
+ def _init_type_to_enum(init_type_str):
+ _INIT_TYPE_STR_2_TYPE = {
+ 'WWPN': AccessGroup.INIT_TYPE_WWPN,
+ 'WWNN': AccessGroup.INIT_TYPE_WWNN,
+ 'ISCSI': AccessGroup.INIT_TYPE_ISCSI_IQN,
+ 'HOSTNAME': AccessGroup.INIT_TYPE_HOSTNAME,
+ 'SAS': AccessGroup.INIT_TYPE_SAS,
+ }
+ if init_type_str in _INIT_TYPE_STR_2_TYPE.keys():
+ return _INIT_TYPE_STR_2_TYPE[init_type_str]
+ raise ArgError("Invalid init_type string %s" % init_type_str)
+
+ @staticmethod
+ def _access_type_to_enum(access_type_str):
+ _ACCESS_TYPE_2_TYPE = {
+ 'RW': MaskInfo.ACCESS_TYPE_READ_WRITE,
+ 'RO': MaskInfo.ACCESS_TYPE_READ_ONLY,
+ }
+ if access_type_str in _ACCESS_TYPE_2_TYPE.keys():
+ return _ACCESS_TYPE_2_TYPE[access_type_str]
+ raise ArgError("Invalid access_type string %s" % access_type_str)

##
# Warn of imminent data loss
@@ -817,9 +786,6 @@ class CmdLine:
if len(objects) == 0:
return

- if hasattr(self.args, 'all') and self.args.all:
- display_all = True
-
flag_with_header = True
if self.args.sep:
flag_with_header = False
@@ -830,6 +796,10 @@ class CmdLine:
if self.args.script:
display_way = DisplayData.DISPLAY_WAY_SCRIPT

+ if hasattr(self.args, 'optional') and self.args.optional:
+ display_all = True
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
flag_new_way_works = DisplayData.display_data(
objects, display_way=display_way, flag_human=self.args.human,
flag_enum=self.args.enum, extra_properties=extra_properties,
@@ -1078,10 +1048,28 @@ class CmdLine:
## Method that calls the appropriate method based on what the list type is
# @param args Argparse argument object
def list(self, args):
+ search_key = None
+ search_value = None
+ if args.vol:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'volume_id'
+ search_value = args.vol
+ if args.ag:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'access_group_id'
+ search_value = args.ag
+ if args.sys:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'system_id'
+ search_value = args.sys
+
if args.type == 'VOLUMES':
self.display_data(self.c.volumes())
elif args.type == 'POOLS':
- if args.all:
+ if args.optional:
self.display_data(
self.c.pools(Pool.RETRIEVE_FULL_INFO))
else:
@@ -1101,81 +1089,42 @@ class CmdLine:
elif args.type == 'NFS_CLIENT_AUTH':
self.display_nfs_client_authentication()
elif args.type == 'ACCESS_GROUPS':
- self.display_data(self.c.access_groups())
+ self.display_data(self.c.access_groups(
+ search_key, search_value, 0))
elif args.type == 'SYSTEMS':
self.display_data(self.c.systems())
elif args.type == 'DISKS':
- if args.all:
+ if args.optional:
self.display_data(
self.c.disks(Disk.RETRIEVE_FULL_INFO))
else:
self.display_data(self.c.disks())
elif args.type == 'PLUGINS':
self.display_available_plugins()
+ elif args.type == 'MASK_INFOS':
+ self.display_data(self.c.mask_infos(search_key, search_value, 0))
else:
raise ArgError("unsupported listing type=%s" % args.type)

- ## Converts type initiator type to enumeration type.
- # @param type String representation of type
- # @returns Enumerated value
- @staticmethod
- def _init_type_to_enum(init_type):
- if init_type == 'WWPN':
- i = Initiator.TYPE_PORT_WWN
- elif init_type == 'WWNN':
- i = Initiator.TYPE_NODE_WWN
- elif init_type == 'ISCSI':
- i = Initiator.TYPE_ISCSI
- elif init_type == 'HOSTNAME':
- i = Initiator.TYPE_HOSTNAME
- elif init_type == 'SAS':
- i = Initiator.TYPE_SAS
- else:
- raise ArgError("invalid initiator type " + init_type)
- return i
-
## Creates an access group.
def access_group_create(self, args):
- i = CmdLine._init_type_to_enum(args.init_type)
- access_group = self.c.access_group_create(args.name, args.init, i,
- args.sys)
+ init_type = CmdLine._init_type_to_enum(args.init_type)
+ access_group = self.c.access_group_create(
+ args.name, args.init, init_type, args.sys)
self.display_data([access_group])

- def _add_rm_access_grp_init(self, args, op):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
-
- if op:
- i = CmdLine._init_type_to_enum(args.init_type)
- self.c.access_group_add_initiator(
- group, args.init, i)
- else:
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- self.c.access_group_del_initiator(group, i.id)
-
- ## Adds an initiator from an access group
- def access_group_add(self, args):
- self._add_rm_access_grp_init(args, True)
-
- ## Removes an initiator from an access group
- def access_group_remove(self, args):
- self._add_rm_access_grp_init(args, False)
-
- def access_group_volumes(self, args):
+ ## Used to delete access group
+ def access_group_delete(self, args):
agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- vols = self.c.volumes_accessible_by_access_group(group)
- self.display_data(vols)
-
- def volumes_accessible_initiator(self, args):
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- volumes = self.c.volumes_accessible_by_initiator(i)
- self.display_data(volumes)
+ access_group = _get_item(agl, args.ag, "access group id")
+ return self.c.access_group_delete(access_group)

- def initiators_granted_volume(self, args):
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- initiators = self.c.initiators_granted_to_volume(vol)
- self.display_data(initiators)
+ def access_group_edit(self, args):
+ access_groups = self.c.access_groups()
+ access_group = _get_item(access_groups, args.ag, "access group id")
+ new_access_group = self.c.access_group_edit(
+ access_group, args.init)
+ self.display_data([new_access_group])

def iscsi_chap(self, args):
init = _get_item(self.c.initiators(), args.init, "initiator id")
@@ -1184,17 +1133,6 @@ class CmdLine:
self.args.out_user,
self.args.out_pass)

- def volume_access_group(self, args):
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- groups = self.c.access_groups_granted_to_volume(vol)
- self.display_data(groups)
-
- ## Used to delete access group
- def access_group_delete(self, args):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- return self.c.access_group_del(group)
-
## Used to delete a file system
def fs_delete(self, args):
fs = _get_item(self.c.fs(), args.fs, "filesystem id")
@@ -1535,53 +1473,6 @@ class CmdLine:
s = _get_item(self.c.systems(), args.sys, "system id")
out(self.c.volume_replicate_range_block_size(s))

- ## Used to grant or revoke access to a volume to an initiator.
- # @param grant bool, if True we grant, else we un-grant.
- def _access(self, grant, args):
- v = _get_item(self.c.volumes(), args.vol, "volume id")
- initiator_id = args.init
-
- if grant:
- i_type = CmdLine._init_type_to_enum(args.init_type)
- access = 'DEFAULT'
- if args.access is not None:
- access = Volume._access_string_to_type(args.access)
-
- self.c.initiator_grant(initiator_id, i_type, v, access)
- else:
- initiator = _get_item(self.c.initiators(), initiator_id,
- "initiator id")
-
- self.c.initiator_revoke(initiator, v)
-
- ## Grant access to volume to an initiator
- def access_grant(self, args):
- return self._access(True, args)
-
- ## Revoke access to volume to an initiator
- def access_revoke(self, args):
- return self._access(False, args)
-
- def _access_group(self, args, grant=True):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- v = _get_item(self.c.volumes(), args.vol, "volume id")
-
- if grant:
- access = 'RW'
- if args.access is not None:
- access = args.access
- access = Volume._access_string_to_type(args.access)
- self.c.access_group_grant(group, v, access)
- else:
- self.c.access_group_revoke(group, v)
-
- def access_group_grant(self, args):
- return self._access_group(args, grant=True)
-
- def access_group_revoke(self, args):
- return self._access_group(args, grant=False)
-
## Re-sizes a volume
def volume_resize(self, args):
v = _get_item(self.c.volumes(), args.vol, "volume id")
@@ -1592,6 +1483,33 @@ class CmdLine:
*self.c.volume_resize(v, size))
self.display_data([vol])

+ def volume_mask(self, args):
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ access_type = MaskInfo.ACCESS_TYPE_READ_WRITE
+ if args.access:
+ access_type = CmdLine._access_type_to_enum(args.access)
+ mask_info = self.c.volume_mask(volume, access_group, access_type)
+ self.display_data([mask_info])
+
+ def volume_unmask(self, args):
+ mask_info = None
+ volume = None
+ access_group = None
+ if args.mask:
+ mask_info = _get_item(self.c.mask_infos(), args.mask, "Mask ID")
+ if args.vol:
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ if args.ag:
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ if mask_info is not None or \
+ (volume is not None and access_group is not None):
+ self.c.volume_unmask(mask_info, volume, access_group, 0)
+ else:
+ raise ArgError("Need '--mask' or (--vol and --ag)")
+
## Removes a nfs export
def fs_unexport(self, args):
export = _get_item(self.c.exports(), args.export, "nfs export id")
diff --git a/lsm/lsm/lsmcli_data_display.py b/lsm/lsm/lsmcli_data_display.py
index 810f590..528e5c4 100644
--- a/lsm/lsm/lsmcli_data_display.py
+++ b/lsm/lsm/lsmcli_data_display.py
@@ -18,7 +18,7 @@
import sys
from collections import OrderedDict

-from lsm import System, size_bytes_2_size_human
+from lsm import System, AccessGroup, MaskInfo, size_bytes_2_size_human


class EnumConvert(object):
@@ -55,6 +55,34 @@ class EnumConvert(object):
return EnumConvert.SYSTEM_STATUS_CONV[System.STATUS_UNKNOWN]
return rc

+ INIT_TYPE_CONV = {
+ AccessGroup.INIT_TYPE_UNKNOWN: 'Unknown',
+ AccessGroup.INIT_TYPE_OTHER: 'Other',
+ AccessGroup.INIT_TYPE_WWPN: 'WWPN',
+ AccessGroup.INIT_TYPE_WWNN: 'WWNN',
+ AccessGroup.INIT_TYPE_HOSTNAME: 'Hostname',
+ AccessGroup.INIT_TYPE_ISCSI_IQN: 'iSCSI IQN',
+ AccessGroup.INIT_TYPE_SAS: 'SAS',
+ }
+
+ @staticmethod
+ def init_type_to_str(init_type):
+ if init_type in EnumConvert.INIT_TYPE_CONV.keys():
+ return EnumConvert.INIT_TYPE_CONV[init_type]
+ return EnumConvert.INIT_TYPE_CONV[AccessGroup.INIT_TYPE_UNKNOWN]
+
+ ACCESS_TYPE_CONV = {
+ MaskInfo.ACCESS_TYPE_UNKNOWN: 'Unknown',
+ MaskInfo.ACCESS_TYPE_READ_WRITE: 'Read Write',
+ MaskInfo.ACCESS_TYPE_READ_ONLY: 'Read Only',
+ }
+
+ @staticmethod
+ def access_type_to_str(access_type):
+ if access_type in EnumConvert.ACCESS_TYPE_CONV.keys():
+ return EnumConvert.ACCESS_TYPE_CONV[access_type]
+ return EnumConvert.INIT_TYPE_CONV[MaskInfo.ACCESS_TYPE_UNKNOWN]
+

class DisplayData(object):

@@ -77,6 +105,9 @@ class DisplayData(object):

DEFAULT_SPLITTER = ' | '

+ VALUE_CONVERT = dict()
+
+ # lsm.System
SYSTEM_MAN_HEADER = OrderedDict()
SYSTEM_MAN_HEADER['id'] = 'ID'
SYSTEM_MAN_HEADER['name'] = 'Name'
@@ -85,8 +116,14 @@ class DisplayData(object):

SYSTEM_OPT_HEADER = OrderedDict()

- SYSTEM_DSP_HEADER = SYSTEM_MAN_HEADER # SYSTEM_DSP_HEADER should be
- # subset of SYSTEM_MAN_HEADER
+ SYSTEM_COLUME_KEYS = SYSTEM_MAN_HEADER.keys()
+ # SYSTEM_COLUME_KEYS should be subset of SYSTEM_MAN_HEADER.keys()
+ # XXX_COLUME_KEYS contain a list of mandatory properties which will be
+ # displayed in column way. It was used to limit the output of properties
+ # in sure the colume display way does not exceeded the column width 78.
+ # All mandatory_headers will be displayed in script way.
+ # if '-o' define, both mandatory_headers and optional_headers will be
+ # displayed in script way.

SYSTEM_VALUE_CONV_ENUM = {
'status': EnumConvert.system_status_to_str,
@@ -94,14 +131,63 @@ class DisplayData(object):

SYSTEM_VALUE_CONV_HUMAN = []

- VALUE_CONVERT = {
- System: {
- 'mandatory_headers': SYSTEM_MAN_HEADER,
- 'display_headers': SYSTEM_DSP_HEADER,
- 'optional_headers': SYSTEM_OPT_HEADER,
- 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
- 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
- }
+ VALUE_CONVERT[System] = {
+ 'mandatory_headers': SYSTEM_MAN_HEADER,
+ 'column_keys': SYSTEM_COLUME_KEYS,
+ 'optional_headers': SYSTEM_OPT_HEADER,
+ 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
+ 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.AccessGroup
+ ACCESS_GROUP_MAN_HEADER = OrderedDict()
+ ACCESS_GROUP_MAN_HEADER['id'] = 'ID'
+ ACCESS_GROUP_MAN_HEADER['name'] = 'Name'
+ ACCESS_GROUP_MAN_HEADER['init_ids'] = 'Initiators'
+ ACCESS_GROUP_MAN_HEADER['init_type'] = 'Type'
+ ACCESS_GROUP_MAN_HEADER['system_id'] = 'System ID'
+ ACCESS_GROUP_OPT_HEADER = OrderedDict()
+ ACCESS_GROUP_COLUME_KEYS = ACCESS_GROUP_MAN_HEADER
+ ACCESS_GROUP_VALUE_CONV_ENUM = {
+ 'init_type': EnumConvert.init_type_to_str,
+ }
+
+ ACCESS_GROUP_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[AccessGroup] = {
+ 'mandatory_headers': ACCESS_GROUP_MAN_HEADER,
+ 'column_keys': ACCESS_GROUP_COLUME_KEYS,
+ 'optional_headers': ACCESS_GROUP_OPT_HEADER,
+ 'value_conv_enum': ACCESS_GROUP_VALUE_CONV_ENUM,
+ 'value_conv_human': ACCESS_GROUP_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.MaskInfo
+ MASK_INFO_MAN_HEADER = OrderedDict()
+ MASK_INFO_MAN_HEADER['id'] = 'ID'
+ MASK_INFO_MAN_HEADER['volume_id'] = 'Volume ID'
+ MASK_INFO_MAN_HEADER['access_group_id'] = 'Access Group ID'
+ MASK_INFO_MAN_HEADER['access_type'] = 'Access Type'
+ MASK_INFO_MAN_HEADER['system_id'] = 'System ID'
+
+ MASK_INFO_COLUME_KEYS = ['id', 'volume_id', 'access_group_id',
+ 'access_type']
+
+ MASK_INFO_OPT_HEADER = OrderedDict()
+
+ # Hide system_id in default display.
+ MASK_INFO_VALUE_CONV_ENUM = {
+ 'access_type': EnumConvert.access_type_to_str,
+ }
+
+ MASK_INFO_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[MaskInfo] = {
+ 'mandatory_headers': MASK_INFO_MAN_HEADER,
+ 'column_keys': MASK_INFO_COLUME_KEYS,
+ 'optional_headers': MASK_INFO_OPT_HEADER,
+ 'value_conv_enum': MASK_INFO_VALUE_CONV_ENUM,
+ 'value_conv_human': MASK_INFO_VALUE_CONV_HUMAN,
}

@staticmethod
@@ -138,33 +224,34 @@ class DisplayData(object):
return max_width

@staticmethod
- def _data_dict_gen(obj, flag_human, flag_enum, extra_properties=None,
- flag_dsp_all_data=False):
+ def _data_dict_gen(obj, flag_human, flag_enum, display_way,
+ extra_properties=None, flag_dsp_all_data=False):
data_dict = OrderedDict()
value_convert = DisplayData.VALUE_CONVERT[type(obj)]
mandatory_headers = value_convert['mandatory_headers']
- display_headers = value_convert['display_headers']
optional_headers = value_convert['optional_headers']
value_conv_enum = value_convert['value_conv_enum']
value_conv_human = value_convert['value_conv_human']

- for key in display_headers.keys():
- key_str = display_headers[key]
+ if flag_dsp_all_data:
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
+ display_keys = []
+
+ if display_way == DisplayData.DISPLAY_WAY_COLUMN:
+ display_keys = value_convert['column_keys']
+ elif display_way == DisplayData.DISPLAY_WAY_SCRIPT:
+ display_keys = mandatory_headers.keys()
+
+ for key in display_keys:
+ key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human, flag_human,
flag_enum)
data_dict[key_str] = value

- if flag_dsp_all_data:
- for key in mandatory_headers.keys():
- if key in display_headers.keys():
- continue
- key_str = mandatory_headers[key]
- value = DisplayData._get_man_pro_value(
- obj, key, value_conv_enum, value_conv_human, flag_human,
- flag_enum)
- data_dict[key_str] = value

+ if flag_dsp_all_data:
for key in optional_headers.keys():
key_str = optional_headers[key]
value = DisplayData._get_opt_pro_value(
@@ -172,11 +259,12 @@ class DisplayData(object):
flag_enum)
data_dict[key_str] = value

- elif extra_properties:
+ if extra_properties:
for key in extra_properties:
+ if key in data_dict.keys():
+ # already contained
+ continue
if key in mandatory_headers.keys():
- if key in display_headers.keys():
- continue
key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human,
@@ -188,7 +276,6 @@ class DisplayData(object):
obj, key, value_conv_enum, value_conv_human,
flag_human, flag_enum)
data_dict[key_str] = value
-
return data_dict

@staticmethod
@@ -211,8 +298,8 @@ class DisplayData(object):
if type(objs[0]) in DisplayData.VALUE_CONVERT.keys():
for obj in objs:
data_dict = DisplayData._data_dict_gen(
- obj, flag_human, flag_enum, extra_properties,
- flag_dsp_all_data)
+ obj, flag_human, flag_enum, display_way,
+ extra_properties, flag_dsp_all_data)
data_dict_list.extend([data_dict])
else:
return None
@@ -304,7 +391,7 @@ class DisplayData(object):
for raw in range(0, row_width):
new = []
for column in range(0, item_count):
- new.append([''])
+ new.append('')
two_d_list.append(new)

# header
--
1.8.3.1
Tony Asleson
2014-04-21 16:26:36 UTC
Permalink
The capabilities call, the following constants were removed or modified,
thus the following lines need to be removed or updated too:

self._cp("INITIATORS", cap.get(Capabilities.INITIATORS))
self._cp("INITIATORS_GRANTED_TO_VOLUME",
cap.get(Capabilities.INITIATORS_GRANTED_TO_VOLUME))

self._cp("VOLUME_INITIATOR_GRANT",
cap.get(Capabilities.VOLUME_INITIATOR_GRANT))
self._cp("VOLUME_INITIATOR_REVOKE",
cap.get(Capabilities.VOLUME_INITIATOR_REVOKE))


self._cp("ACCESS_GROUP_GRANT",
cap.get(Capabilities.ACCESS_GROUP_GRANT))
self._cp("ACCESS_GROUP_REVOKE",
cap.get(Capabilities.ACCESS_GROUP_REVOKE))
self._cp("ACCESS_GROUP_LIST",
cap.get(Capabilities.ACCESS_GROUP_LIST))

self._cp("ACCESS_GROUP_ADD_INITIATOR",
cap.get(Capabilities.ACCESS_GROUP_ADD_INITIATOR))
self._cp("ACCESS_GROUP_DEL_INITIATOR",
cap.get(Capabilities.ACCESS_GROUP_DEL_INITIATOR))
self._cp("VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP",
cap.get(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP))
self._cp("VOLUME_ACCESSIBLE_BY_INITIATOR",
cap.get(Capabilities.VOLUME_ACCESSIBLE_BY_INITIATOR))
self._cp("ACCESS_GROUPS_GRANTED_TO_VOLUME",
cap.get(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME))


Thanks!

Regards,
Tony
Post by Gris Ge
access-grant
access-group-grant
access-revoke
access-group-revoke
volumes_accessible_by_access_group
volumes-accessible-initiator
access-group-volumes
initiators-granted-volume
volume-mask
volume-unmask
MASK_INFOS
--vol
--ag
--sys
# Currently only supported by list type: MASK_INFOS and ACCESS_GROUPS
* When '-o, --optional' define, the display method will be forced to script
way.
---
doc/man/lsmcli.1.in | 131 +++++++--------
lsm/lsm/_cmdline.py | 362 ++++++++++++++++-------------------------
lsm/lsm/lsmcli_data_display.py | 151 +++++++++++++----
3 files changed, 315 insertions(+), 329 deletions(-)
diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..0db7670 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -117,7 +117,7 @@ circumstance, \fb-b\fR will be effective, command will wait until finished.
\fB-s\fR, \fB--script\fR
Displaying data in script friendly way.
.br
ID | Name | Member IDs
-------------------------------------+-------+-------------
aa0ffc70-3dba-11df-b8cf-00a0980ad71b | aggr1 | ID_C
| | ID_D
-With this option, data is displayed in this manner.
+With this option, data is displayed in this script way.
--------------------------------------------------
ID | 87720e90-3a83-11df-b8bf-00a0980ad71b
@@ -139,6 +139,11 @@ With this option, data is displayed in this manner.
Member IDs | ID_C
| ID_D
--------------------------------------------------
+Not all mandatory properties will be displayed in column way considering the
+user's console text width. All mandatory properties will be displayed in
+script way. When with "-o, --optional", both mandatory properties and optional
+properties will be displayed.
.SH COMMANDS
.SS list
@@ -147,11 +152,25 @@ List information on LSM objects
\fB--type\fR \fI<TYPE>\fR
.br
-\fBVOLUMES\fR, \fBINITIATORS\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR,
+\fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR, \fBMASK_INFOS\fR,
\fBEXPORTS\fR, \fBDISKS\fR,
.br
\fBNFS_CLIENT_AUTH\fR, \fBACCESS_GROUPS\fR,
\fBSYSTEMS\fR, \fBPLUGINS.
+.br
+The \fBVOLUMES\fR well-known as LUN by many storage array vendors. It is a
+virtual storage space which host operation system treated as a or many block
+device(s).
+.br
+The \fBACCESS_GROUPS\fR define a group of initiators to access volume.
+For example, most FC/FCoE HBA cards has two ports, each of two ports has
+one(or more with NPIV) WWPN(s). When performing volume masking, a volume is
+often mask to a access group containing all of these WWPNs. On EMC devices,
+access group is refered as "Host Group". On NetApp ONTAP devices, access group
+is refered as "Initiator Group(igroup)".
+.br
+The \fBMASK_INFOS\fR reprent a relationship between access group and volume.
+The volume masking is well-known as "LUN Masking".
.TP
\fB--fs\fR \fI<FS_ID>\fR
Required for \fB--type\fR=\fBSNAPSHOTS\fR. List the snapshots of certain
@@ -160,7 +179,18 @@ PLUGINS will list all supported plugins of LSM, not only the current one.
.TP
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
-available.
+available. When define, will use '--script' display method.
+By default(without this argument), only mandatory properties will be
+retrieved.
+.TP
+\fB--vol\fR \fI<VOL_ID>\fR
+Optional. Filter with volume ID: <VOL_ID>
+.TP
+\fB--ag\fR \fI<AG_ID>\fR
+Optional. Filter with access group ID: <AG_ID>
+.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Optional. Filter with system ID: <SYS_ID>
.SS job-status
Retrieve information about a job.
@@ -281,43 +311,35 @@ Removes volume dependencies(like replication).
\fB--vol\fR \fI<VOL_ID>\fR
Required. The ID of volume to remove dependency.
-.SS volume-access-group
-Lists the access group(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS volumes-accessible-initiator
-Lists the initiator(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS access-group-grant
+.SS volume-mask
.TP 15
-Grant a access group the RO or RW access to certain volume. Like LUN masking
-or NFS export.
+Mask a volume to certain access group. When defined volume is already masked
+to defined access group, the new '--access' will overide the old one.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to access.
+Required. The ID of volume to mask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to grant.
-
+Required. ID of access group to mask.
.TP
\fB--access\fR \fI<ACCESS>\fR
-Optional. Permission of access, valid values are \fBRO\fR, \fBRW\fR. Default
-value is \fBRW\fR.
+Optional. Define the access type, valid values are \fBRW\fR and \fBRO\fR,
+stand for "Read and Write" and "Read Only". Default value is RW.
-.SS access-group-revoke
+.SS volume-unmask
.TP 15
-Revoke an access group the RO or RW access to certain volume.
+ * \fBlsmcli volume-unmask --mask <MASK_ID>\fR
+ * \fBlsmcli volume-unmask --vol <VOL_ID> --ag <AG_ID>\fR
+.TP
+\fB--mask\fR \fI<MASK_ID>\fR
+Optional. The ID of mask to unmask.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
+Optional. The ID of volume to unmask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to revoke.
+Optional. ID of access group to unmask.
.SS access-group-create
.TP 15
@@ -327,77 +349,36 @@ Create an access group.
Required. The human friendly name for new access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. The first initiator ID of new access group.
+Required. Repeatable. The initiator ID of new access group. For more than one
+initiators, use this: '\fB--init INIT_ID_1 --init INIT_ID_2\fR'.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
+\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR. All initiators defined should be in
+same initiator type.
.TP
\fB--sys\fR \fI<SYS_ID>\fR
Required. The ID of system where this access group should reside on.
-.SS access-group-add
+.SS access-group-edit
Adds an initiator to an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to add.
+Required. Repeatable. New initiator ID.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
-.SS access-group-remove
-Removes an initiator from an access group.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. ID of access group.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to remove.
-
.SS access-group-delete
Delete an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group to delete.
-.SS access-grant
-Grants access to an initiator to a volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to grant access.
-.TP
-\fB--init-type\fR \fI<INIT_TYPE>\fR
-Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
-.TP
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to grant access.
-
-.SS access-revoke
-Removes access for an initiator to a volume
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to revoke.
-
-.SS access-group-volumes
-Lists the volumes that the access group has been granted access to.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to query.
-
-.SS initiators-granted-volume
-Lists the initiators that have been granted access to specified volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. The ID of initiator to query.
-
.SS iscsi-chap
Configures ISCSI inbound/outbound CHAP authentication
.TP 15
diff --git a/lsm/lsm/_cmdline.py b/lsm/lsm/_cmdline.py
index 778647e..821e5f0 100644
--- a/lsm/lsm/_cmdline.py
+++ b/lsm/lsm/_cmdline.py
@@ -23,8 +23,8 @@ from argparse import ArgumentParser
from argparse import RawTextHelpFormatter
from lsm import (Client, Pool, VERSION, LsmError, Capabilities, Disk,
- Initiator, Volume, JobStatus, ErrorNumber, BlockRange,
- uri_parse)
+ Volume, JobStatus, ErrorNumber, BlockRange, uri_parse,
+ AccessGroup, MaskInfo)
from _data import PlugData
from _common import getch, size_human_2_size_bytes, Proxy
return i
raise ArgError('%s with id %s not found!' % (friendly_name, the_id))
-list_choices = ['VOLUMES', 'INITIATORS', 'POOLS', 'FS', 'SNAPSHOTS',
+list_choices = ['VOLUMES', 'MASK_INFOS', 'POOLS', 'FS', 'SNAPSHOTS',
'EXPORTS', "NFS_CLIENT_AUTH", 'ACCESS_GROUPS',
'SYSTEMS', 'DISKS', 'PLUGINS']
@@ -142,6 +142,7 @@ ag_id_opt = dict(name='--ag', metavar='<AG_ID>', help='Access group ID')
init_id_opt = dict(name='--init', metavar='<INIT_ID>', help='Initiator ID')
snap_id_opt = dict(name='--snap', metavar='<SNAP_ID>', help='Snapshot ID')
export_id_opt = dict(name='--export', metavar='<EXPORT_ID>', help='Export ID')
+mask_id_opt = dict(name='--mask', metavar='<MASK_ID>', help='Mask ID')
size_opt = dict(name='--size', metavar='<SIZE>', help=size_help)
access_opt = dict(name='--access', metavar='<ACCESS>', help=access_help,
@@ -165,13 +166,22 @@ cmds = (
type=str.upper),
],
optional=[
- dict(name=('-a', '--all'),
- help='Retrieve and display in script friendly way with ' +
- 'all information including optional data if available',
+ dict(name=('-o', '--optional'),
+ help='Retrieve both mandatory and optional properties.\n'
+ 'Once define, will use "-s, --script" display way',
default=False,
- dest='all',
+ dest='optional',
action='store_true'),
dict(fs_id_opt),
+ dict(name=('--vol'),
+ metavar='<VOL_ID>',
+ help='Filter with defined volume ID'),
+ dict(name=('--ag'),
+ metavar='<AG_ID>',
+ help='Filter with defined access group ID'),
+ dict(name=('--sys'),
+ metavar='<SYS_ID>',
+ help='Filter with defined system ID'),
],
),
@@ -231,6 +241,31 @@ cmds = (
),
dict(
+ name='volume-mask',
+ help='Mask a volume to certain access group',
+ args=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ ],
+ optional=[
+ dict(access_opt),
+ ],
+ ),
+
+ dict(
+ name='volume-unmask',
+ help='Unmask a volume from certain access group via mask ID or ' +
+ 'combination of volume ID and access group ID',
+ args=[
+ ],
+ optional=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ dict(mask_id_opt),
+ ],
+ ),
+
+ dict(
name='volume-replicate',
help='Creates a new volume and peplicates provided volume to it.',
args=[
@@ -297,78 +332,19 @@ cmds = (
),
dict(
- name='volume-access-group',
- help='Lists the access group(s) that have access to volume',
- args=[
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='volumes-accessible-initiator',
- help='Lists the volumes that are accessible by the initiator',
- args=[
- dict(init_id_opt),
- ],
- ),
-
-
- dict(
- name='access-group-grant',
- help='Grants access to an access group to a volume, '
- 'like LUN Masking',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-group-revoke',
- help='Revoke the access of certain access group to a volume',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
name='access-group-create',
help='Create an access group',
args=[
dict(name='--name', metavar='<AG_NAME>',
help="Human readble name for access group"),
- # TODO: _client.py access_group_create should support multipal
- # initiators when creating.
- dict(init_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
dict(init_type_opt),
dict(sys_id_opt),
],
),
- # TODO: Merge access_group_add() and access_group_remove() into
- # access_group_mofidy(ag_id, new_inits, init_type, flags)
- dict(
- name='access-group-add',
- help='Add an initiator into existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- dict(init_type_opt),
- ],
- ),
- dict(
- name='access-group-remove',
- help='Remove an initiator from existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- ],
- ),
-
dict(
name='access-group-delete',
help='Deletes an access group',
@@ -378,42 +354,13 @@ cmds = (
),
dict(
- name='access-grant',
- help='Grants access to an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(init_type_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-revoke',
- help='Removes access for an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='access-group-volumes',
- help='Lists the volumes that the access group has'
- ' been granted access to',
+ name='access-group-edit',
+ help='Edit the initiators of an access group',
args=[
dict(ag_id_opt),
- ],
- ),
-
- dict(
- name='initiators-granted-volume',
- help='Lists the initiators that have been '
- 'granted access to specified volume',
- args=[
- dict(vol_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
],
),
"""
Command line interface class.
"""
+ _INIT_TYPE_STR_2_TYPE = {
+ 'WWPN': AccessGroup.INIT_TYPE_WWPN,
+ 'WWNN': AccessGroup.INIT_TYPE_WWNN,
+ 'ISCSI': AccessGroup.INIT_TYPE_ISCSI_IQN,
+ 'HOSTNAME': AccessGroup.INIT_TYPE_HOSTNAME,
+ 'SAS': AccessGroup.INIT_TYPE_SAS,
+ }
+ return _INIT_TYPE_STR_2_TYPE[init_type_str]
+ raise ArgError("Invalid init_type string %s" % init_type_str)
+
+ _ACCESS_TYPE_2_TYPE = {
+ 'RW': MaskInfo.ACCESS_TYPE_READ_WRITE,
+ 'RO': MaskInfo.ACCESS_TYPE_READ_ONLY,
+ }
+ return _ACCESS_TYPE_2_TYPE[access_type_str]
+ raise ArgError("Invalid access_type string %s" % access_type_str)
##
# Warn of imminent data loss
return
- display_all = True
-
flag_with_header = True
flag_with_header = False
display_way = DisplayData.DISPLAY_WAY_SCRIPT
+ display_all = True
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
flag_new_way_works = DisplayData.display_data(
objects, display_way=display_way, flag_human=self.args.human,
flag_enum=self.args.enum, extra_properties=extra_properties,
## Method that calls the appropriate method based on what the list type is
+ search_key = None
+ search_value = None
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'volume_id'
+ search_value = args.vol
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'access_group_id'
+ search_value = args.ag
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'system_id'
+ search_value = args.sys
+
self.display_data(self.c.volumes())
self.display_data(
self.c.pools(Pool.RETRIEVE_FULL_INFO))
self.display_nfs_client_authentication()
- self.display_data(self.c.access_groups())
+ self.display_data(self.c.access_groups(
+ search_key, search_value, 0))
self.display_data(self.c.systems())
self.display_data(
self.c.disks(Disk.RETRIEVE_FULL_INFO))
self.display_data(self.c.disks())
self.display_available_plugins()
+ self.display_data(self.c.mask_infos(search_key, search_value, 0))
raise ArgError("unsupported listing type=%s" % args.type)
- ## Converts type initiator type to enumeration type.
- i = Initiator.TYPE_PORT_WWN
- i = Initiator.TYPE_NODE_WWN
- i = Initiator.TYPE_ISCSI
- i = Initiator.TYPE_HOSTNAME
- i = Initiator.TYPE_SAS
- raise ArgError("invalid initiator type " + init_type)
- return i
-
## Creates an access group.
- i = CmdLine._init_type_to_enum(args.init_type)
- access_group = self.c.access_group_create(args.name, args.init, i,
- args.sys)
+ init_type = CmdLine._init_type_to_enum(args.init_type)
+ access_group = self.c.access_group_create(
+ args.name, args.init, init_type, args.sys)
self.display_data([access_group])
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
-
- i = CmdLine._init_type_to_enum(args.init_type)
- self.c.access_group_add_initiator(
- group, args.init, i)
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- self.c.access_group_del_initiator(group, i.id)
-
- ## Adds an initiator from an access group
- self._add_rm_access_grp_init(args, True)
-
- ## Removes an initiator from an access group
- self._add_rm_access_grp_init(args, False)
-
+ ## Used to delete access group
agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- vols = self.c.volumes_accessible_by_access_group(group)
- self.display_data(vols)
-
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- volumes = self.c.volumes_accessible_by_initiator(i)
- self.display_data(volumes)
+ access_group = _get_item(agl, args.ag, "access group id")
+ return self.c.access_group_delete(access_group)
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- initiators = self.c.initiators_granted_to_volume(vol)
- self.display_data(initiators)
+ access_groups = self.c.access_groups()
+ access_group = _get_item(access_groups, args.ag, "access group id")
+ new_access_group = self.c.access_group_edit(
+ access_group, args.init)
+ self.display_data([new_access_group])
init = _get_item(self.c.initiators(), args.init, "initiator id")
self.args.out_user,
self.args.out_pass)
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- groups = self.c.access_groups_granted_to_volume(vol)
- self.display_data(groups)
-
- ## Used to delete access group
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- return self.c.access_group_del(group)
-
## Used to delete a file system
fs = _get_item(self.c.fs(), args.fs, "filesystem id")
s = _get_item(self.c.systems(), args.sys, "system id")
out(self.c.volume_replicate_range_block_size(s))
- ## Used to grant or revoke access to a volume to an initiator.
- v = _get_item(self.c.volumes(), args.vol, "volume id")
- initiator_id = args.init
-
- i_type = CmdLine._init_type_to_enum(args.init_type)
- access = 'DEFAULT'
- access = Volume._access_string_to_type(args.access)
-
- self.c.initiator_grant(initiator_id, i_type, v, access)
- initiator = _get_item(self.c.initiators(), initiator_id,
- "initiator id")
-
- self.c.initiator_revoke(initiator, v)
-
- ## Grant access to volume to an initiator
- return self._access(True, args)
-
- ## Revoke access to volume to an initiator
- return self._access(False, args)
-
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- v = _get_item(self.c.volumes(), args.vol, "volume id")
-
- access = 'RW'
- access = args.access
- access = Volume._access_string_to_type(args.access)
- self.c.access_group_grant(group, v, access)
- self.c.access_group_revoke(group, v)
-
- return self._access_group(args, grant=True)
-
- return self._access_group(args, grant=False)
-
## Re-sizes a volume
v = _get_item(self.c.volumes(), args.vol, "volume id")
*self.c.volume_resize(v, size))
self.display_data([vol])
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ access_type = MaskInfo.ACCESS_TYPE_READ_WRITE
+ access_type = CmdLine._access_type_to_enum(args.access)
+ mask_info = self.c.volume_mask(volume, access_group, access_type)
+ self.display_data([mask_info])
+
+ mask_info = None
+ volume = None
+ access_group = None
+ mask_info = _get_item(self.c.mask_infos(), args.mask, "Mask ID")
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ if mask_info is not None or \
+ self.c.volume_unmask(mask_info, volume, access_group, 0)
+ raise ArgError("Need '--mask' or (--vol and --ag)")
+
## Removes a nfs export
export = _get_item(self.c.exports(), args.export, "nfs export id")
diff --git a/lsm/lsm/lsmcli_data_display.py b/lsm/lsm/lsmcli_data_display.py
index 810f590..528e5c4 100644
--- a/lsm/lsm/lsmcli_data_display.py
+++ b/lsm/lsm/lsmcli_data_display.py
@@ -18,7 +18,7 @@
import sys
from collections import OrderedDict
-from lsm import System, size_bytes_2_size_human
+from lsm import System, AccessGroup, MaskInfo, size_bytes_2_size_human
return EnumConvert.SYSTEM_STATUS_CONV[System.STATUS_UNKNOWN]
return rc
+ INIT_TYPE_CONV = {
+ AccessGroup.INIT_TYPE_UNKNOWN: 'Unknown',
+ AccessGroup.INIT_TYPE_OTHER: 'Other',
+ AccessGroup.INIT_TYPE_WWPN: 'WWPN',
+ AccessGroup.INIT_TYPE_WWNN: 'WWNN',
+ AccessGroup.INIT_TYPE_HOSTNAME: 'Hostname',
+ AccessGroup.INIT_TYPE_ISCSI_IQN: 'iSCSI IQN',
+ AccessGroup.INIT_TYPE_SAS: 'SAS',
+ }
+
+ return EnumConvert.INIT_TYPE_CONV[init_type]
+ return EnumConvert.INIT_TYPE_CONV[AccessGroup.INIT_TYPE_UNKNOWN]
+
+ ACCESS_TYPE_CONV = {
+ MaskInfo.ACCESS_TYPE_UNKNOWN: 'Unknown',
+ MaskInfo.ACCESS_TYPE_READ_WRITE: 'Read Write',
+ MaskInfo.ACCESS_TYPE_READ_ONLY: 'Read Only',
+ }
+
+ return EnumConvert.ACCESS_TYPE_CONV[access_type]
+ return EnumConvert.INIT_TYPE_CONV[MaskInfo.ACCESS_TYPE_UNKNOWN]
+
DEFAULT_SPLITTER = ' | '
+ VALUE_CONVERT = dict()
+
+ # lsm.System
SYSTEM_MAN_HEADER = OrderedDict()
SYSTEM_MAN_HEADER['id'] = 'ID'
SYSTEM_MAN_HEADER['name'] = 'Name'
SYSTEM_OPT_HEADER = OrderedDict()
- SYSTEM_DSP_HEADER = SYSTEM_MAN_HEADER # SYSTEM_DSP_HEADER should be
- # subset of SYSTEM_MAN_HEADER
+ SYSTEM_COLUME_KEYS = SYSTEM_MAN_HEADER.keys()
+ # SYSTEM_COLUME_KEYS should be subset of SYSTEM_MAN_HEADER.keys()
+ # XXX_COLUME_KEYS contain a list of mandatory properties which will be
+ # displayed in column way. It was used to limit the output of properties
+ # in sure the colume display way does not exceeded the column width 78.
+ # All mandatory_headers will be displayed in script way.
+ # if '-o' define, both mandatory_headers and optional_headers will be
+ # displayed in script way.
SYSTEM_VALUE_CONV_ENUM = {
'status': EnumConvert.system_status_to_str,
SYSTEM_VALUE_CONV_HUMAN = []
- VALUE_CONVERT = {
- System: {
- 'mandatory_headers': SYSTEM_MAN_HEADER,
- 'display_headers': SYSTEM_DSP_HEADER,
- 'optional_headers': SYSTEM_OPT_HEADER,
- 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
- 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
- }
+ VALUE_CONVERT[System] = {
+ 'mandatory_headers': SYSTEM_MAN_HEADER,
+ 'column_keys': SYSTEM_COLUME_KEYS,
+ 'optional_headers': SYSTEM_OPT_HEADER,
+ 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
+ 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.AccessGroup
+ ACCESS_GROUP_MAN_HEADER = OrderedDict()
+ ACCESS_GROUP_MAN_HEADER['id'] = 'ID'
+ ACCESS_GROUP_MAN_HEADER['name'] = 'Name'
+ ACCESS_GROUP_MAN_HEADER['init_ids'] = 'Initiators'
+ ACCESS_GROUP_MAN_HEADER['init_type'] = 'Type'
+ ACCESS_GROUP_MAN_HEADER['system_id'] = 'System ID'
+ ACCESS_GROUP_OPT_HEADER = OrderedDict()
+ ACCESS_GROUP_COLUME_KEYS = ACCESS_GROUP_MAN_HEADER
+ ACCESS_GROUP_VALUE_CONV_ENUM = {
+ 'init_type': EnumConvert.init_type_to_str,
+ }
+
+ ACCESS_GROUP_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[AccessGroup] = {
+ 'mandatory_headers': ACCESS_GROUP_MAN_HEADER,
+ 'column_keys': ACCESS_GROUP_COLUME_KEYS,
+ 'optional_headers': ACCESS_GROUP_OPT_HEADER,
+ 'value_conv_enum': ACCESS_GROUP_VALUE_CONV_ENUM,
+ 'value_conv_human': ACCESS_GROUP_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.MaskInfo
+ MASK_INFO_MAN_HEADER = OrderedDict()
+ MASK_INFO_MAN_HEADER['id'] = 'ID'
+ MASK_INFO_MAN_HEADER['volume_id'] = 'Volume ID'
+ MASK_INFO_MAN_HEADER['access_group_id'] = 'Access Group ID'
+ MASK_INFO_MAN_HEADER['access_type'] = 'Access Type'
+ MASK_INFO_MAN_HEADER['system_id'] = 'System ID'
+
+ MASK_INFO_COLUME_KEYS = ['id', 'volume_id', 'access_group_id',
+ 'access_type']
+
+ MASK_INFO_OPT_HEADER = OrderedDict()
+
+ # Hide system_id in default display.
+ MASK_INFO_VALUE_CONV_ENUM = {
+ 'access_type': EnumConvert.access_type_to_str,
+ }
+
+ MASK_INFO_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[MaskInfo] = {
+ 'mandatory_headers': MASK_INFO_MAN_HEADER,
+ 'column_keys': MASK_INFO_COLUME_KEYS,
+ 'optional_headers': MASK_INFO_OPT_HEADER,
+ 'value_conv_enum': MASK_INFO_VALUE_CONV_ENUM,
+ 'value_conv_human': MASK_INFO_VALUE_CONV_HUMAN,
}
@staticmethod
return max_width
@staticmethod
- def _data_dict_gen(obj, flag_human, flag_enum, extra_properties=None,
+ def _data_dict_gen(obj, flag_human, flag_enum, display_way,
data_dict = OrderedDict()
value_convert = DisplayData.VALUE_CONVERT[type(obj)]
mandatory_headers = value_convert['mandatory_headers']
- display_headers = value_convert['display_headers']
optional_headers = value_convert['optional_headers']
value_conv_enum = value_convert['value_conv_enum']
value_conv_human = value_convert['value_conv_human']
- key_str = display_headers[key]
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
+ display_keys = []
+
+ display_keys = value_convert['column_keys']
+ display_keys = mandatory_headers.keys()
+
+ key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human, flag_human,
flag_enum)
data_dict[key_str] = value
- continue
- key_str = mandatory_headers[key]
- value = DisplayData._get_man_pro_value(
- obj, key, value_conv_enum, value_conv_human, flag_human,
- flag_enum)
- data_dict[key_str] = value
key_str = optional_headers[key]
value = DisplayData._get_opt_pro_value(
flag_enum)
data_dict[key_str] = value
+ # already contained
+ continue
- continue
key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human,
obj, key, value_conv_enum, value_conv_human,
flag_human, flag_enum)
data_dict[key_str] = value
-
return data_dict
@staticmethod
data_dict = DisplayData._data_dict_gen(
- obj, flag_human, flag_enum, extra_properties,
- flag_dsp_all_data)
+ obj, flag_human, flag_enum, display_way,
+ extra_properties, flag_dsp_all_data)
data_dict_list.extend([data_dict])
return None
new = []
- new.append([''])
+ new.append('')
two_d_list.append(new)
# header
Gris Ge
2014-04-21 14:44:48 UTC
Permalink
* Just sync with API changes on volume masking.
* Use plugin_helper.py(class PluginHelper) for searching/filter support on
lsm.Client.access_groups() and lsm.Client.mask_infos()
# This could the example when other plugin want to use this routine but
# resource consuming searching methods.

Signed-off-by: Gris Ge <***@redhat.com>
---
lsm/lsm/plugin_helper.py | 72 +++++++++
lsm/lsm/simarray.py | 380 +++++++++++++++--------------------------------
lsm/lsm/simulator.py | 91 +++++-------
3 files changed, 233 insertions(+), 310 deletions(-)
create mode 100644 lsm/lsm/plugin_helper.py

diff --git a/lsm/lsm/plugin_helper.py b/lsm/lsm/plugin_helper.py
new file mode 100644
index 0000000..b7bbf02
--- /dev/null
+++ b/lsm/lsm/plugin_helper.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2011-2013 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from lsm import LsmError, ErrorNumber
+
+class PluginHelper():
+ """
+ PluginHelper just containing the routine methods could be used by LSM
+ plugins. Some of these methods are having a very bad performance since
+ should be the last choice.
+ """
+
+ @staticmethod
+ def access_group_search(plugin_self, search_key, search_value, flags=0):
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
+ Conflict with lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ """
+ all_access_groups = plugin_self.access_groups(flags=flags)
+ if search_key in ['id', 'name', 'system_id']:
+ return list(a for a in all_access_groups
+ if getattr(a, search_key) == search_value)
+ if search_key == 'volume_id':
+ rc = []
+ ag_ids = []
+ all_mask_infos = plugin_self.mask_infos()
+ for mask_info in all_mask_infos:
+ if mask_info.volume_id == search_value:
+ ag_ids.extend([mask_info.access_group_id])
+ if ag_ids:
+ for access_group in all_access_groups:
+ if access_group.id in ag_ids:
+ rc.extend([access_group])
+ return rc
+ else:
+ return []
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
+
+ @staticmethod
+ def mask_info_search(plugin_self, search_key, search_value, flags=0):
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
+ Conflict with lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ """
+ all_mask_infos = plugin_self.mask_infos(flags=flags)
+ if search_key in ['id', 'volume_id', 'access_group_id', 'system_id']:
+ return list(m for m in all_mask_infos
+ if getattr(m, search_key) == search_value)
+
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
diff --git a/lsm/lsm/simarray.py b/lsm/lsm/simarray.py
index 29cab42..9c84b43 100644
--- a/lsm/lsm/simarray.py
+++ b/lsm/lsm/simarray.py
@@ -27,8 +27,8 @@ import time

from _common import (size_human_2_size_bytes, size_bytes_2_size_human)
from lsm import (System, Volume, Disk, Pool, FileSystem, AccessGroup,
- Initiator, FsSnapshot, NfsExport, OptionalData, md5,
- LsmError, ErrorNumber, JobStatus)
+ FsSnapshot, NfsExport, OptionalData, md5, LsmError,
+ ErrorNumber, JobStatus, MaskInfo)

# Used for format width for disks
D_FMT = 5
@@ -350,71 +350,51 @@ class SimArray(object):

@staticmethod
def _sim_ag_2_lsm(sim_ag):
- return AccessGroup(sim_ag['ag_id'], sim_ag['name'],
- sim_ag['init_ids'], sim_ag['sys_id'])
+ return AccessGroup(sim_ag['id'], sim_ag['name'],
+ sim_ag['init_ids'], sim_ag['init_type'],
+ sim_ag['system_id'], OptionalData())

- def ags(self):
- sim_ags = self.data.ags()
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ sim_ags = self.data.access_groups(flags=flags)
+ return SimArray._sort_by_id(
+ [SimArray._sim_ag_2_lsm(a) for a in sim_ags])

- def access_group_create(self, name, init_id, init_type, sys_id, flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = self.data.access_group_create(
- name, init_id, init_type, sys_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimArray._sim_ag_2_lsm(sim_ag)

- def access_group_del(self, ag_id, flags=0):
- return self.data.access_group_del(ag_id, flags)
-
- def access_group_add_initiator(self, ag_id, init_id, init_type, flags=0):
- return self.data.access_group_add_initiator(
- ag_id, init_id, init_type, flags)
-
- def access_group_del_initiator(self, ag_id, init_id, flags=0):
- return self.data.access_group_del_initiator(ag_id, init_id, flags)
-
- def access_group_grant(self, ag_id, vol_id, access, flags=0):
- return self.data.access_group_grant(ag_id, vol_id, access, flags)
-
- def access_group_revoke(self, ag_id, vol_id, flags=0):
- return self.data.access_group_revoke(ag_id, vol_id, flags)
-
- def volumes_accessible_by_access_group(self, ag_id, flags=0):
- sim_vols = self.data.volumes_accessible_by_access_group(ag_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
-
- def access_groups_granted_to_volume(self, vol_id, flags=0):
- sim_ags = self.data.access_groups_granted_to_volume(vol_id, flags)
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
+ def access_group_delete(self, ag_id, flags=0):
+ return self.data.access_group_delete(ag_id, flags)

- @staticmethod
- def _sim_init_2_lsm(sim_init):
- return Initiator(sim_init['init_id'], sim_init['init_type'],
- sim_init['name'])
-
- def inits(self, flags=0):
- sim_inits = self.data.inits()
- return [SimArray._sim_init_2_lsm(a) for a in sim_inits]
-
- def initiator_grant(self, init_id, init_type, vol_id, access, flags=0):
- return self.data.initiator_grant(
- init_id, init_type, vol_id, access, flags)
-
- def initiator_revoke(self, init_id, vol_id, flags=0):
- return self.data.initiator_revoke(init_id, vol_id, flags)
-
- def volumes_accessible_by_initiator(self, init_id, flags=0):
- sim_vols = self.data.volumes_accessible_by_initiator(init_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
-
- def initiators_granted_to_volume(self, vol_id, flags=0):
- sim_inits = self.data.initiators_granted_to_volume(vol_id, flags)
- return [SimArray._sim_init_2_lsm(i) for i in sim_inits]
+ def access_group_edit(self, ag_id, new_init_ids, flags=0):
+ sim_ag = self.data.access_group_edit(ag_id, new_init_ids, flags)
+ return SimArray._sim_ag_2_lsm(sim_ag)

def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
flags=0):
return self.data.iscsi_chap_auth(init_id, in_user, in_pass, out_user,
out_pass, flags)

+ @staticmethod
+ def _sim_mask_2_lsm(sim_mask):
+ return MaskInfo(sim_mask['id'], sim_mask['volume_id'],
+ sim_mask['access_group_id'], sim_mask['access_type'],
+ sim_mask['system_id'], OptionalData())
+
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ # search was handled by PlugHelper in simulator.py
+ sim_masks = self.data.mask_infos(flags=flags)
+ return SimArray._sort_by_id(
+ [SimArray._sim_mask_2_lsm(m) for m in sim_masks])
+
+ def volume_mask(self, vol_id, ag_id, access_type, flags=0):
+ sim_mask = self.data.volume_mask(vol_id, ag_id, access_type, flags)
+ return SimArray._sim_mask_2_lsm(sim_mask)
+
+ def volume_unmask(self, mask_id=None, vol_id=None, ag_id=None, flags=0):
+ return self.data.volume_unmask(mask_id, vol_id, ag_id, flags)

class SimData(object):
"""
@@ -444,22 +424,6 @@ class SimData(object):
},
],
},
- 'mask': {
- ag_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- },
- 'mask_init': {
- init_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- }
- }
-
- self.init_dict = {
- Initiator.id = sim_init,
- }
- sim_init = {
- 'init_id': Initiator.id,
- 'init_type': Initiator.TYPE_XXXX,
- 'name': SimData.SIM_DATA_INIT_NAME,
- 'sys_id': SimData.SIM_DATA_SYS_ID,
}

self.ag_dict ={
@@ -467,9 +431,10 @@ class SimData(object):
}
sim_ag = {
'init_ids': [init_id,],
- 'sys_id': SimData.SIM_DATA_SYS_ID,
+ 'init_type': AccessGroup.INIT_TYPE_XXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
'name': name,
- 'ag_id': self._next_ag_id()
+ 'id': self._next_ag_id()
}

self.fs_dict = {
@@ -524,7 +489,7 @@ class SimData(object):
'name': pool_name,
'pool_id': Pool.id,
'raid_type': Pool.RAID_TYPE_XXXX,
- 'member_ids': [ disk_id or pool_id or volume_id ],
+ 'member_ids': [ disk_id or pool_id or vol_id ],
'member_type': Pool.MEMBER_TYPE_XXXX,
'member_size': size_bytes # space allocated from each member pool.
# only for MEMBER_TYPE_POOL
@@ -532,9 +497,21 @@ class SimData(object):
'sys_id': SimData.SIM_DATA_SYS_ID,
'element_type': SimData.SIM_DATA_POOL_ELEMENT_TYPE,
}
+
+ self.mask_dict= {
+ MaskInfo.id: sim_mask,
+ }
+
+ sim_mask = {
+ 'id': MaskInfo.id,
+ 'access_group_id': AccessGroup.id,
+ 'volume_id': Volume.id,
+ 'access_type': MaskInfo.ACCESS_TYPE_XXXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
+ }
"""
SIM_DATA_BLK_SIZE = 512
- SIM_DATA_VERSION = "2.2"
+ SIM_DATA_VERSION = "2.3"
SIM_DATA_SYS_ID = 'sim-01'
SIM_DATA_INIT_NAME = 'NULL'
SIM_DATA_TMO = 30000 # ms
@@ -555,6 +532,7 @@ class SimData(object):
SIM_DATA_CUR_AG_ID = 0
SIM_DATA_CUR_SNAP_ID = 0
SIM_DATA_CUR_EXP_ID = 0
+ SIM_DATA_CUR_MASK_ID = 0

def _next_pool_id(self):
self.SIM_DATA_CUR_POOL_ID += 1
@@ -580,6 +558,10 @@ class SimData(object):
self.SIM_DATA_CUR_EXP_ID += 1
return "EXP_ID_%08d" % self.SIM_DATA_CUR_EXP_ID

+ def _next_mask_id(self):
+ self.SIM_DATA_CUR_MASK_ID += 1
+ return "MASK_ID_%08d" % self.SIM_DATA_CUR_MASK_ID
+
@staticmethod
def state_signature():
return 'LSM_SIMULATOR_DATA_%s' % md5(SimData.SIM_DATA_VERSION)
@@ -674,8 +656,7 @@ class SimData(object):

self.ag_dict = {
}
- self.init_dict = {
- }
+ self.mask_dict = dict()
# Create some volumes, fs and etc
self.volume_create(
'POO1', 'Volume 000', size_human_2_size_bytes('200GiB'),
@@ -864,7 +845,8 @@ class SimData(object):
def disks(self):
return self.disk_dict.values()

- def access_group_list(self):
+ def access_groups(self, flags=0):
+ # search was handled by simulator.py
return self.ag_dict.values()

def volume_create(self, pool_id, vol_name, size_bytes, thinp, flags=0):
@@ -1029,207 +1011,89 @@ class SimData(object):
del sim_vol['replicate'][vol_id]
return None

- def ags(self, flags=0):
- return self.ag_dict.values()
-
- def access_group_create(self, name, init_id, init_type, sys_id, flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = dict()
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
-
- sim_ag['init_ids'] = [init_id]
- sim_ag['sys_id'] = SimData.SIM_DATA_SYS_ID
- sim_ag['name'] = name
- sim_ag['ag_id'] = self._next_ag_id()
- self.ag_dict[sim_ag['ag_id']] = sim_ag
+ sim_ag['init_ids'] = init_ids
+ sim_ag['init_type'] = init_type
+ sim_ag['system_id'] = SimData.SIM_DATA_SYS_ID
+ sim_ag['name'] = access_group_name
+ sim_ag['id'] = self._next_ag_id()
+ self.ag_dict[sim_ag['id']] = sim_ag
return sim_ag

- def access_group_del(self, ag_id, flags=0):
+ def access_group_delete(self, ag_id, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
del(self.ag_dict[ag_id])
return None

- def access_group_add_initiator(self, ag_id, init_id, init_type, flags=0):
+ def access_group_edit(self, ag_id, new_init_ids, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- if init_id in self.ag_dict[ag_id]['init_ids']:
- return self.ag_dict[ag_id]
-
- self.ag_dict[ag_id]['init_ids'].extend([init_id])
-
- return None
-
- def access_group_del_initiator(self, ag_id, init_id, flags=0):
- if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- if init_id not in self.init_dict.keys():
- return None
+ sim_ag = self.ag_dict[ag_id]
+ del(self.ag_dict[ag_id])
+ sim_ag['init_ids'] = new_init_ids
+ self.ag_dict[ag_id] = sim_ag
+ return sim_ag

- if init_id in self.ag_dict[ag_id]['init_ids']:
- new_init_ids = []
- for cur_init_id in self.ag_dict[ag_id]['init_ids']:
- if cur_init_id != init_id:
- new_init_ids.extend([cur_init_id])
- del(self.ag_dict[ag_id]['init_ids'])
- self.ag_dict[ag_id]['init_ids'] = new_init_ids
+ def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
+ flags=0):
+ # No iscsi chap query API yet
return None

- def access_group_grant(self, ag_id, vol_id, access, flags=0):
- if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if 'mask' not in self.vol_dict[vol_id].keys():
- self.vol_dict[vol_id]['mask'] = dict()
-
- self.vol_dict[vol_id]['mask'][ag_id] = access
- return None
+ def mask_infos(self, flags=0):
+ # search was handled by PlugHelper in simulator.py
+ return self.mask_dict.values()

- def access_group_revoke(self, ag_id, vol_id, flags=0):
+ def volume_mask(self, vol_id, ag_id, access_type, flags=0):
if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
+ raise LsmError(ErrorNumber.VOLUME_MASK_INVALID_ACCESS_GROUP,
+ "Access group %s not found" % ag_id)
if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
+ raise LsmError(ErrorNumber.VOLUME_MASK_INVALID_VOLUME,
"No such Volume: %s" % vol_id)
- if 'mask' not in self.vol_dict[vol_id].keys():
+ mask_id = None
+ # Check whether we are updating existing mask.
+ for sim_mask in self.mask_dict.values():
+ if sim_mask['volume_id'] == vol_id and \
+ sim_mask['access_group_id'] == ag_id:
+ mask_id = sim_mask['id']
+ del(self.mask_dict[mask_id])
+ break
+ if mask_id is None:
+ mask_id = self._next_mask_id()
+ sim_mask = dict()
+ sim_mask['id'] = mask_id
+ sim_mask['volume_id'] = vol_id
+ sim_mask['access_group_id'] = ag_id
+ sim_mask['access_type'] = access_type
+ sim_mask['system_id'] = SimData.SIM_DATA_SYS_ID
+ self.mask_dict[mask_id] = sim_mask
+ return sim_mask
+
+ def volume_unmask(self, mask_id=None, vol_id=None, ag_id=None, flags=0):
+ if mask_id is not None:
+ if mask_id not in self.mask_dict.keys():
+ raise LsmError(ErrorNumber.VOLUME_UNMASK_INVALID_MASK_INFO,
+ "Invalid mask info ID: %s" % mask_id)
+ del(self.mask_dict[mask_id])
return None

- if ag_id not in self.vol_dict[vol_id]['mask'].keys():
- return None
-
- del(self.vol_dict[vol_id]['mask'][ag_id])
- return None
-
- def volumes_accessible_by_access_group(self, ag_id, flags=0):
- if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- rc = []
- for sim_vol in self.vol_dict.values():
- if 'mask' not in sim_vol:
- continue
- if ag_id in sim_vol['mask'].keys():
- rc.extend([sim_vol])
- return rc
-
- def access_groups_granted_to_volume(self, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- sim_ags = []
- if 'mask' in self.vol_dict[vol_id].keys():
- ag_ids = self.vol_dict[vol_id]['mask'].keys()
- for ag_id in ag_ids:
- sim_ags.extend([self.ag_dict[ag_id]])
- return sim_ags
-
- def inits(self, flags=0):
- return self.init_dict.values()
-
- def initiator_grant(self, init_id, init_type, vol_id, access, flags):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- if 'mask_init' not in self.vol_dict[vol_id].keys():
- self.vol_dict[vol_id]['mask_init'] = dict()
-
- self.vol_dict[vol_id]['mask_init'][init_id] = access
- return None
-
- def initiator_revoke(self, init_id, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
-
- if 'mask_init' in self.vol_dict[vol_id].keys():
- if init_id in self.vol_dict[vol_id]['mask_init'].keys():
- del self.vol_dict[vol_id]['mask_init'][init_id]
-
- return None
-
- def _ag_ids_of_init(self, init_id):
- """
- Find out the access groups defined initiator belong to.
- Will return a list of access group id or []
- """
- rc = []
- for sim_ag in self.ag_dict.values():
- if init_id in sim_ag['init_ids']:
- rc.extend([sim_ag['ag_id']])
- return rc
-
- def volumes_accessible_by_initiator(self, init_id, flags=0):
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- rc_dedup_dict = dict()
- ag_ids = self._ag_ids_of_init(init_id)
- for ag_id in ag_ids:
- sim_vols = self.volumes_accessible_by_access_group(ag_id)
- for sim_vol in sim_vols:
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
-
- for sim_vol in self.vol_dict.values():
- if 'mask_init' in sim_vol:
- if init_id in sim_vol['mask_init'].keys():
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
- return rc_dedup_dict.values()
-
- def initiators_granted_to_volume(self, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- rc_dedup_dict = dict()
- sim_ags = self.access_groups_granted_to_volume(vol_id, flags)
- for sim_ag in sim_ags:
- for init_id in sim_ag['init_ids']:
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- if 'mask_init' in self.vol_dict[vol_id].keys():
- for init_id in self.vol_dict[vol_id]['mask_init']:
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- return rc_dedup_dict.values()
-
- def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
- flags=0):
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- if self.init_dict[init_id]['init_type'] != Initiator.TYPE_ISCSI:
- raise LsmError(ErrorNumber.UNSUPPORTED_INITIATOR_TYPE,
- "Initiator %s is not an iSCSI IQN" % init_id)
- # No iscsi chap query API yet
- return None
+ if vol_id is not None:
+ if vol_id not in self.vol_dict.keys():
+ raise LsmError(ErrorNumber.VOLUME_UNMASK_INVALID_VOLUME,
+ "Invalid volume ID: %s" % vol_id)
+ if ag_id not in self.ag_dict.keys():
+ raise LsmError(ErrorNumber.VOLUME_UNMASK_INVALID_ACCESS_GROUP,
+ "Invalid access group ID: %s" % ag_id)
+ for sim_mask in self.mask_dict.values():
+ if sim_mask['volume_id'] == vol_id and \
+ sim_mask['access_group_id'] == ag_id:
+ del(self.mask_dict[sim_mask['id']])
+ return None

def fs(self):
return self.fs_dict.values()
diff --git a/lsm/lsm/simulator.py b/lsm/lsm/simulator.py
index 1ea1a45..0c716f8 100644
--- a/lsm/lsm/simulator.py
+++ b/lsm/lsm/simulator.py
@@ -20,6 +20,8 @@ from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,

from simarray import SimArray

+from plugin_helper import PluginHelper
+

class SimPlugin(INfs, IStorageAreaNetwork):
"""
@@ -162,67 +164,52 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_offline(self, volume, flags=0):
return self.sim_array.volume_online(volume.id, flags)

- def access_group_list(self, flags=0):
- sim_ags = self.sim_array.ags()
- return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key is None:
+ sim_ags = self.sim_array.access_groups(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ else:
+ return PluginHelper.access_group_search(
+ self, search_key, search_value, flags)

- def access_group_create(self, name, initiator_id, id_type, system_id,
- flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = self.sim_array.access_group_create(
- name, initiator_id, id_type, system_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)

- def access_group_del(self, group, flags=0):
- return self.sim_array.access_group_del(group.id, flags)
+ def access_group_delete(self, access_group, flags=0):
+ return self.sim_array.access_group_delete(access_group.id, flags)

- def access_group_add_initiator(self, group, initiator_id, id_type,
- flags=0):
- sim_ag = self.sim_array.access_group_add_initiator(
- group.id, initiator_id, id_type, flags)
+ def access_group_edit(self, access_group, new_init_ids, flags=0):
+ sim_ag = self.sim_array.access_group_edit(
+ access_group.id, new_init_ids, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)

- def access_group_del_initiator(self, group, initiator_id, flags=0):
- return self.sim_array.access_group_del_initiator(
- group.id, initiator_id, flags)
-
- def access_group_grant(self, group, volume, access, flags=0):
- return self.sim_array.access_group_grant(
- group.id, volume.id, access, flags)
-
- def access_group_revoke(self, group, volume, flags=0):
- return self.sim_array.access_group_revoke(
- group.id, volume.id, flags)
-
- def volumes_accessible_by_access_group(self, group, flags=0):
- sim_vols = self.sim_array.volumes_accessible_by_access_group(
- group.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- def access_groups_granted_to_volume(self, volume, flags=0):
- sim_vols = self.sim_array.access_groups_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- def initiators(self, flags=0):
- return self.sim_array.inits(flags)
-
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- flags=0):
- return self.sim_array.initiator_grant(
- initiator_id, initiator_type, volume.id, access, flags)
-
- def initiator_revoke(self, initiator, volume, flags=0):
- return self.sim_array.initiator_revoke(initiator.id, volume.id, flags)
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ if search_key is None:
+ sim_masks = self.sim_array.mask_infos(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(m) for m in sim_masks]
+ else:
+ return PluginHelper.mask_info_search(
+ self, search_key, search_value, flags)

- def volumes_accessible_by_initiator(self, initiator, flags=0):
- sim_vols = self.sim_array.volumes_accessible_by_initiator(
- initiator.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
+ def volume_mask(self, volume, access_group, access_type, flags=0):
+ sim_mask = self.sim_array.volume_mask(
+ volume.id, access_group.id, access_type, flags)
+ return SimPlugin._sim_data_2_lsm(sim_mask)

- def initiators_granted_to_volume(self, volume, flags=0):
- sim_inits = self.sim_array.initiators_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(i) for i in sim_inits]
+ def volume_unmask(self, mask_info=None, volume=None, access_group=None,
+ flags=0):
+ if mask_info is not None:
+ return self.sim_array.volume_unmask(
+ mask_id=mask_info.id, vol_id=None, ag_id=None, flags=flags)
+ if volume is not None:
+ return self.sim_array.volume_unmask(
+ mask_id=None, vol_id=volume.id, ag_id=access_group.id,
+ flags=flags)

def iscsi_chap_auth(self, initiator, in_user, in_password,
out_user, out_password, flags=0):
--
1.8.3.1
Tony Asleson
2014-04-21 16:26:51 UTC
Permalink
Add new file to appropriate auto make and rpm spec files.

Addl. comments below.
Post by Gris Ge
* Just sync with API changes on volume masking.
* Use plugin_helper.py(class PluginHelper) for searching/filter support on
lsm.Client.access_groups() and lsm.Client.mask_infos()
# This could the example when other plugin want to use this routine but
# resource consuming searching methods.
---
lsm/lsm/plugin_helper.py | 72 +++++++++
lsm/lsm/simarray.py | 380 +++++++++++++++--------------------------------
lsm/lsm/simulator.py | 91 +++++-------
3 files changed, 233 insertions(+), 310 deletions(-)
create mode 100644 lsm/lsm/plugin_helper.py
diff --git a/lsm/lsm/plugin_helper.py b/lsm/lsm/plugin_helper.py
new file mode 100644
index 0000000..b7bbf02
--- /dev/null
+++ b/lsm/lsm/plugin_helper.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2011-2013 Red Hat, Inc.
Just 2014 as this file is new.
Post by Gris Ge
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from lsm import LsmError, ErrorNumber
+
+ """
+ PluginHelper just containing the routine methods could be used by LSM
+ plugins. Some of these methods are having a very bad performance since
+ should be the last choice.
+ """
+
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
Implement
Post by Gris Ge
+ Conflict with lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ """
+ all_access_groups = plugin_self.access_groups(flags=flags)
+ return list(a for a in all_access_groups
+ if getattr(a, search_key) == search_value)
+ rc = []
+ ag_ids = []
+ all_mask_infos = plugin_self.mask_infos()
+ ag_ids.extend([mask_info.access_group_id])
+ rc.extend([access_group])
+ return rc
+ return []
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
+
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
+ Conflict with lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ """
+ all_mask_infos = plugin_self.mask_infos(flags=flags)
+ return list(m for m in all_mask_infos
+ if getattr(m, search_key) == search_value)
+
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
diff --git a/lsm/lsm/simarray.py b/lsm/lsm/simarray.py
index 29cab42..9c84b43 100644
--- a/lsm/lsm/simarray.py
+++ b/lsm/lsm/simarray.py
@@ -27,8 +27,8 @@ import time
from _common import (size_human_2_size_bytes, size_bytes_2_size_human)
from lsm import (System, Volume, Disk, Pool, FileSystem, AccessGroup,
- Initiator, FsSnapshot, NfsExport, OptionalData, md5,
- LsmError, ErrorNumber, JobStatus)
+ FsSnapshot, NfsExport, OptionalData, md5, LsmError,
+ ErrorNumber, JobStatus, MaskInfo)
# Used for format width for disks
D_FMT = 5
@staticmethod
- return AccessGroup(sim_ag['ag_id'], sim_ag['name'],
- sim_ag['init_ids'], sim_ag['sys_id'])
+ return AccessGroup(sim_ag['id'], sim_ag['name'],
+ sim_ag['init_ids'], sim_ag['init_type'],
+ sim_ag['system_id'], OptionalData())
- sim_ags = self.data.ags()
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
+ sim_ags = self.data.access_groups(flags=flags)
+ return SimArray._sort_by_id(
+ [SimArray._sim_ag_2_lsm(a) for a in sim_ags])
+ def access_group_create(self, access_group_name, init_ids, init_type,
sim_ag = self.data.access_group_create(
- name, init_id, init_type, sys_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimArray._sim_ag_2_lsm(sim_ag)
- return self.data.access_group_del(ag_id, flags)
-
- return self.data.access_group_add_initiator(
- ag_id, init_id, init_type, flags)
-
- return self.data.access_group_del_initiator(ag_id, init_id, flags)
-
- return self.data.access_group_grant(ag_id, vol_id, access, flags)
-
- return self.data.access_group_revoke(ag_id, vol_id, flags)
-
- sim_vols = self.data.volumes_accessible_by_access_group(ag_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
-
- sim_ags = self.data.access_groups_granted_to_volume(vol_id, flags)
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
+ return self.data.access_group_delete(ag_id, flags)
- return Initiator(sim_init['init_id'], sim_init['init_type'],
- sim_init['name'])
-
- sim_inits = self.data.inits()
- return [SimArray._sim_init_2_lsm(a) for a in sim_inits]
-
- return self.data.initiator_grant(
- init_id, init_type, vol_id, access, flags)
-
- return self.data.initiator_revoke(init_id, vol_id, flags)
-
- sim_vols = self.data.volumes_accessible_by_initiator(init_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
-
- sim_inits = self.data.initiators_granted_to_volume(vol_id, flags)
- return [SimArray._sim_init_2_lsm(i) for i in sim_inits]
+ sim_ag = self.data.access_group_edit(ag_id, new_init_ids, flags)
+ return SimArray._sim_ag_2_lsm(sim_ag)
def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
return self.data.iscsi_chap_auth(init_id, in_user, in_pass, out_user,
out_pass, flags)
+ return MaskInfo(sim_mask['id'], sim_mask['volume_id'],
+ sim_mask['access_group_id'], sim_mask['access_type'],
+ sim_mask['system_id'], OptionalData())
+
+ # search was handled by PlugHelper in simulator.py
+ sim_masks = self.data.mask_infos(flags=flags)
+ return SimArray._sort_by_id(
+ [SimArray._sim_mask_2_lsm(m) for m in sim_masks])
+
+ sim_mask = self.data.volume_mask(vol_id, ag_id, access_type, flags)
+ return SimArray._sim_mask_2_lsm(sim_mask)
+
+ return self.data.volume_unmask(mask_id, vol_id, ag_id, flags)
"""
},
],
},
- 'mask': {
- ag_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- },
- 'mask_init': {
- init_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- }
- }
-
- self.init_dict = {
- Initiator.id = sim_init,
- }
- sim_init = {
- 'init_id': Initiator.id,
- 'init_type': Initiator.TYPE_XXXX,
- 'name': SimData.SIM_DATA_INIT_NAME,
- 'sys_id': SimData.SIM_DATA_SYS_ID,
}
self.ag_dict ={
}
sim_ag = {
'init_ids': [init_id,],
- 'sys_id': SimData.SIM_DATA_SYS_ID,
+ 'init_type': AccessGroup.INIT_TYPE_XXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
'name': name,
- 'ag_id': self._next_ag_id()
+ 'id': self._next_ag_id()
}
self.fs_dict = {
'name': pool_name,
'pool_id': Pool.id,
'raid_type': Pool.RAID_TYPE_XXXX,
- 'member_ids': [ disk_id or pool_id or volume_id ],
+ 'member_ids': [ disk_id or pool_id or vol_id ],
'member_type': Pool.MEMBER_TYPE_XXXX,
'member_size': size_bytes # space allocated from each member pool.
# only for MEMBER_TYPE_POOL
'sys_id': SimData.SIM_DATA_SYS_ID,
'element_type': SimData.SIM_DATA_POOL_ELEMENT_TYPE,
}
+
+ self.mask_dict= {
+ MaskInfo.id: sim_mask,
+ }
+
+ sim_mask = {
+ 'id': MaskInfo.id,
+ 'access_group_id': AccessGroup.id,
+ 'volume_id': Volume.id,
+ 'access_type': MaskInfo.ACCESS_TYPE_XXXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
+ }
"""
SIM_DATA_BLK_SIZE = 512
- SIM_DATA_VERSION = "2.2"
+ SIM_DATA_VERSION = "2.3"
SIM_DATA_SYS_ID = 'sim-01'
SIM_DATA_INIT_NAME = 'NULL'
SIM_DATA_TMO = 30000 # ms
SIM_DATA_CUR_AG_ID = 0
SIM_DATA_CUR_SNAP_ID = 0
SIM_DATA_CUR_EXP_ID = 0
+ SIM_DATA_CUR_MASK_ID = 0
self.SIM_DATA_CUR_POOL_ID += 1
self.SIM_DATA_CUR_EXP_ID += 1
return "EXP_ID_%08d" % self.SIM_DATA_CUR_EXP_ID
+ self.SIM_DATA_CUR_MASK_ID += 1
+ return "MASK_ID_%08d" % self.SIM_DATA_CUR_MASK_ID
+
@staticmethod
return 'LSM_SIMULATOR_DATA_%s' % md5(SimData.SIM_DATA_VERSION)
self.ag_dict = {
}
- self.init_dict = {
- }
+ self.mask_dict = dict()
# Create some volumes, fs and etc
self.volume_create(
'POO1', 'Volume 000', size_human_2_size_bytes('200GiB'),
return self.disk_dict.values()
+ # search was handled by simulator.py
return self.ag_dict.values()
del sim_vol['replicate'][vol_id]
return None
- return self.ag_dict.values()
-
+ def access_group_create(self, access_group_name, init_ids, init_type,
sim_ag = dict()
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
-
- sim_ag['init_ids'] = [init_id]
- sim_ag['sys_id'] = SimData.SIM_DATA_SYS_ID
- sim_ag['name'] = name
- sim_ag['ag_id'] = self._next_ag_id()
- self.ag_dict[sim_ag['ag_id']] = sim_ag
+ sim_ag['init_ids'] = init_ids
+ sim_ag['init_type'] = init_type
+ sim_ag['system_id'] = SimData.SIM_DATA_SYS_ID
+ sim_ag['name'] = access_group_name
+ sim_ag['id'] = self._next_ag_id()
+ self.ag_dict[sim_ag['id']] = sim_ag
return sim_ag
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
del(self.ag_dict[ag_id])
return None
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- return self.ag_dict[ag_id]
-
- self.ag_dict[ag_id]['init_ids'].extend([init_id])
-
- return None
-
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- return None
+ sim_ag = self.ag_dict[ag_id]
+ del(self.ag_dict[ag_id])
+ sim_ag['init_ids'] = new_init_ids
+ self.ag_dict[ag_id] = sim_ag
+ return sim_ag
- new_init_ids = []
- new_init_ids.extend([cur_init_id])
- del(self.ag_dict[ag_id]['init_ids'])
- self.ag_dict[ag_id]['init_ids'] = new_init_ids
+ def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
+ # No iscsi chap query API yet
return None
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- self.vol_dict[vol_id]['mask'] = dict()
-
- self.vol_dict[vol_id]['mask'][ag_id] = access
- return None
+ # search was handled by PlugHelper in simulator.py
+ return self.mask_dict.values()
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
This error constant does not exist, however better would be something to
the effect of not found instead of INVALID.

If you look in ErrorNumber class we have constants for not found when
user specifies something to search for that is not found.

NOT_FOUND_ACCESS_GROUP = 200
NOT_FOUND_FS = 201
NOT_FOUND_JOB = 202
NOT_FOUND_POOL = 203
NOT_FOUND_SS = 204
NOT_FOUND_VOLUME = 205
NOT_FOUND_NFS_EXPORT = 206
NOT_FOUND_INITIATOR = 207
NOT_FOUND_SYSTEM = 208
NOT_FOUND_DISK = 209
Post by Gris Ge
+ raise LsmError(ErrorNumber.VOLUME_MASK_INVALID_ACCESS_GROUP,
+ "Access group %s not found" % ag_id)
This error constant does not exist, however better would be something to
the effect of not found instead of INVALID.
Post by Gris Ge
- raise LsmError(ErrorNumber.INVALID_VOLUME,
+ raise LsmError(ErrorNumber.VOLUME_MASK_INVALID_VOLUME,
"No such Volume: %s" % vol_id)
+ mask_id = None
+ # Check whether we are updating existing mask.
+ if sim_mask['volume_id'] == vol_id and \
+ mask_id = sim_mask['id']
+ del(self.mask_dict[mask_id])
+ break
+ mask_id = self._next_mask_id()
+ sim_mask = dict()
+ sim_mask['id'] = mask_id
+ sim_mask['volume_id'] = vol_id
+ sim_mask['access_group_id'] = ag_id
+ sim_mask['access_type'] = access_type
+ sim_mask['system_id'] = SimData.SIM_DATA_SYS_ID
+ self.mask_dict[mask_id] = sim_mask
+ return sim_mask
+
+ raise LsmError(ErrorNumber.VOLUME_UNMASK_INVALID_MASK_INFO,
+ "Invalid mask info ID: %s" % mask_id)
This error constant does not exist, however better would be something to
the effect of not found instead of INVALID.
Post by Gris Ge
+ del(self.mask_dict[mask_id])
return None
- return None
-
- del(self.vol_dict[vol_id]['mask'][ag_id])
- return None
-
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- rc = []
- continue
- rc.extend([sim_vol])
- return rc
-
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- sim_ags = []
- ag_ids = self.vol_dict[vol_id]['mask'].keys()
- sim_ags.extend([self.ag_dict[ag_id]])
- return sim_ags
-
- return self.init_dict.values()
-
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- self.vol_dict[vol_id]['mask_init'] = dict()
-
- self.vol_dict[vol_id]['mask_init'][init_id] = access
- return None
-
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
-
- del self.vol_dict[vol_id]['mask_init'][init_id]
-
- return None
-
- """
- Find out the access groups defined initiator belong to.
- Will return a list of access group id or []
- """
- rc = []
- rc.extend([sim_ag['ag_id']])
- return rc
-
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- rc_dedup_dict = dict()
- ag_ids = self._ag_ids_of_init(init_id)
- sim_vols = self.volumes_accessible_by_access_group(ag_id)
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
-
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
- return rc_dedup_dict.values()
-
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- rc_dedup_dict = dict()
- sim_ags = self.access_groups_granted_to_volume(vol_id, flags)
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- return rc_dedup_dict.values()
-
- def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- raise LsmError(ErrorNumber.UNSUPPORTED_INITIATOR_TYPE,
- "Initiator %s is not an iSCSI IQN" % init_id)
- # No iscsi chap query API yet
- return None
+ raise LsmError(ErrorNumber.VOLUME_UNMASK_INVALID_VOLUME,
+ "Invalid volume ID: %s" % vol_id)
This error constant does not exist, better error would be not found
Post by Gris Ge
+ raise LsmError(ErrorNumber.VOLUME_UNMASK_INVALID_ACCESS_GROUP,
+ "Invalid access group ID: %s" % ag_id)
This error constant does not exist, better error would be not found
Post by Gris Ge
+ if sim_mask['volume_id'] == vol_id and \
+ del(self.mask_dict[sim_mask['id']])
+ return None
return self.fs_dict.values()
diff --git a/lsm/lsm/simulator.py b/lsm/lsm/simulator.py
index 1ea1a45..0c716f8 100644
--- a/lsm/lsm/simulator.py
+++ b/lsm/lsm/simulator.py
@@ -20,6 +20,8 @@ from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,
from simarray import SimArray
+from plugin_helper import PluginHelper
+
"""
return self.sim_array.volume_online(volume.id, flags)
- sim_ags = self.sim_array.ags()
- return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ sim_ags = self.sim_array.access_groups(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ return PluginHelper.access_group_search(
+ self, search_key, search_value, flags)
- def access_group_create(self, name, initiator_id, id_type, system_id,
+ def access_group_create(self, access_group_name, init_ids, init_type,
sim_ag = self.sim_array.access_group_create(
- name, initiator_id, id_type, system_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)
- return self.sim_array.access_group_del(group.id, flags)
+ return self.sim_array.access_group_delete(access_group.id, flags)
- def access_group_add_initiator(self, group, initiator_id, id_type,
- sim_ag = self.sim_array.access_group_add_initiator(
- group.id, initiator_id, id_type, flags)
+ sim_ag = self.sim_array.access_group_edit(
+ access_group.id, new_init_ids, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)
- return self.sim_array.access_group_del_initiator(
- group.id, initiator_id, flags)
-
- return self.sim_array.access_group_grant(
- group.id, volume.id, access, flags)
-
- return self.sim_array.access_group_revoke(
- group.id, volume.id, flags)
-
- sim_vols = self.sim_array.volumes_accessible_by_access_group(
- group.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- sim_vols = self.sim_array.access_groups_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- return self.sim_array.inits(flags)
-
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- return self.sim_array.initiator_grant(
- initiator_id, initiator_type, volume.id, access, flags)
-
- return self.sim_array.initiator_revoke(initiator.id, volume.id, flags)
+ sim_masks = self.sim_array.mask_infos(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(m) for m in sim_masks]
+ return PluginHelper.mask_info_search(
+ self, search_key, search_value, flags)
- sim_vols = self.sim_array.volumes_accessible_by_initiator(
- initiator.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
+ sim_mask = self.sim_array.volume_mask(
+ volume.id, access_group.id, access_type, flags)
+ return SimPlugin._sim_data_2_lsm(sim_mask)
- sim_inits = self.sim_array.initiators_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(i) for i in sim_inits]
+ def volume_unmask(self, mask_info=None, volume=None, access_group=None,
+ return self.sim_array.volume_unmask(
+ mask_id=mask_info.id, vol_id=None, ag_id=None, flags=flags)
+ return self.sim_array.volume_unmask(
+ mask_id=None, vol_id=volume.id, ag_id=access_group.id,
+ flags=flags)
def iscsi_chap_auth(self, initiator, in_user, in_password,
Thanks,
Tony
Gris Ge
2014-04-23 12:40:57 UTC
Permalink
Sorry for big patch. It's pretty hard to split them into pieces.
* Update Python API codes to compliant with API documents for
volume masking part:
* Update lsm.AccessGroup
* Remove lsm.Initiator
* Add lsm.MaskInfo
* simulator plugin update for this change.
* Introduce LsmPluginHelper for searching/filtering.
* lsmcli update for support this change.

I just retype this patch set in three days after a data lose.
It surely has pretty errors. Please kindly let me know if you found anything
wrong or suspicious.

TODO:
1. C/C++ codes.
2. Test codes. # I can do the python test codes part.
3. iSCSI CHAP.
# I will add a section in API document and provide patch before end of
# this week.
4. Query on lsm.Client.volumes()
# This will come alone with code-doc sync on lsm.Volume patch set.
5. Update these plugins for this change:
* SMI-S -- smis.py # I will take this
* Simulator C -- simc_lsmplugin.c
* ONTAP -- ontap.py
* targetd -- targetd.py # I will take this
* IBM V7K -- ibmv7k.py
* NStor -- nstor.py
6. Add more search/filter support to other query methods.
Goal is allowing the same set of search_keys on every query methods.

Changes since V1:
1. Use shared ErrorNumber.
2. Removed lsm.MaskInfo.id property.
3. Change lsm.Client.volume_unmask() to take volume and access group only.
4. Renamed plugin_helper.py to lsm_plugin_helper.py.
# As it will not added into "lsm" namespace, we'd better give it a prefix.
5. Update rpm spec and automake files for new file lsm_plugin_helper.py.

Gris Ge (3):
Python API: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator
lsmcli: update volume masking related commands
simulator.py: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator

doc/man/lsmcli.1.in | 126 ++++------
libstoragemgmt.spec.in | 1 +
lsm/Makefile.am | 3 +-
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 555 ++++++++++++++++++++++++++++-------------
lsm/lsm/_cmdline.py | 398 +++++++++++------------------
lsm/lsm/_common.py | 30 ++-
lsm/lsm/_data.py | 166 ++++++------
lsm/lsm/lsm_plugin_helper.py | 72 ++++++
lsm/lsm/lsmcli_data_display.py | 149 ++++++++---
lsm/lsm/simarray.py | 367 +++++++++------------------
lsm/lsm/simulator.py | 89 +++----
12 files changed, 1045 insertions(+), 917 deletions(-)
create mode 100644 lsm/lsm/lsm_plugin_helper.py
--
1.8.3.1
Gris Ge
2014-04-23 12:40:58 UTC
Permalink
* Removed these methods:
access_group_add_initiator() # replaced by access_group_edit()
access_group_del_initiator() # replaced by access_group_edit()
access_group_grant() # replaced by volume_mask()
access_group_revoke() # replaced by volume_unmask()
access_groups_granted_to_volume # replaced by access_groups()
initiators_granted_to_volume() # No need.
* Renamed these methods:
access_group_del() -> access_group_delete()
access_group_list() -> access_groups()
* Added these methods:
volume_mask()
volume_unmask()
mask_infos()
* Removed this class:
lsm.Initiator
* Added this class:
lsm.MaskInfo
* Updated this class:
lsm.AccessGroup
* Add property 'optional_data', 'init_type'.
* Renam property 'initiators' to 'init_ids'.
* No doxygen document included as we have better one.
* Method document copied from API document.
* Purged private constants and methods of lsm.AccessGroup about value
converting. They have been moved to lsmcli_data_display.py. And API user
should use their own value converting codes.

Signed-off-by: Gris Ge <***@redhat.com>
---
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 555 ++++++++++++++++++++++++++++++++++++----------------
lsm/lsm/_common.py | 30 ++-
lsm/lsm/_data.py | 166 ++++++++--------
4 files changed, 503 insertions(+), 254 deletions(-)

diff --git a/lsm/lsm/__init__.py b/lsm/lsm/__init__.py
index 0635b7e..836ab75 100644
--- a/lsm/lsm/__init__.py
+++ b/lsm/lsm/__init__.py
@@ -4,9 +4,9 @@ from version import VERSION

from _common import Error, Info, LsmError, ErrorLevel, ErrorNumber, \
JobStatus, uri_parse, md5, Proxy, size_bytes_2_size_human
-from _data import Initiator, Disk, \
- Volume, Pool, System, FileSystem, FsSnapshot, NfsExport, BlockRange, \
- AccessGroup, OptionalData, Capabilities, txt_a
+from _data import Disk, Volume, Pool, System, FileSystem, FsSnapshot, \
+ NfsExport, BlockRange, AccessGroup, OptionalData, Capabilities, MaskInfo, \
+ txt_a
from _iplugin import IPlugin, IStorageAreaNetwork, INetworkAttachedStorage, \
INfs

diff --git a/lsm/lsm/_client.py b/lsm/lsm/_client.py
index a8a30c2..aa2aa6d 100644
--- a/lsm/lsm/_client.py
+++ b/lsm/lsm/_client.py
@@ -19,9 +19,9 @@ import time
import os
import unittest
from lsm import (Volume, NfsExport, Capabilities, Pool, System,
- Initiator, Disk, AccessGroup, FileSystem, FsSnapshot,
+ Disk, AccessGroup, FileSystem, FsSnapshot,
uri_parse, LsmError, JobStatus, ErrorNumber,
- INetworkAttachedStorage)
+ INetworkAttachedStorage, MaskInfo)

from _common import return_requires as _return_requires
from _common import UDS_PATH as _UDS_PATH
@@ -427,17 +427,6 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('systems', _del_self(locals()))

- ## Returns an array of initiator objects
- # @param self The this pointer
- # @param flags Reserved for future use, must be zero.
- # @returns An array of initiator objects.
- @_return_requires([Initiator])
- def initiators(self, flags=0):
- """
- Return an array of initiator objects
- """
- return self._tp.rpc('initiators', _del_self(locals()))
-
## Register a user/password for the specified initiator for CHAP
# authentication.
# Note: If you pass an empty user and password the expected behavior is to
@@ -459,57 +448,6 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('iscsi_chap_auth', _del_self(locals()))

- ## Grants access so an initiator can read/write the specified volume.
- # @param self The this pointer
- # @param initiator_id The iqn, WWID etc.
- # @param initiator_type Enumerated initiator type
- # @param volume Volume to grant access to
- # @param access Enumerated access type
- # @param flags Reserved for future use, must be zero
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- flags=0):
- """
- Allows an initiator to access a volume.
- """
- return self._tp.rpc('initiator_grant', _del_self(locals()))
-
- ## Revokes access for a volume for the specified initiator.
- # @param self The this pointer
- # @param initiator The iqn, WWID etc.
- # @param volume The volume to revoke access for
- # @param flags Reserved for future use, must be zero
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def initiator_revoke(self, initiator, volume, flags=0):
- """
- Revokes access to a volume for the specified initiator
- """
- return self._tp.rpc('initiator_revoke', _del_self(locals()))
-
- ## Returns a list of volumes that are accessible from the specified
- # initiator.
- # @param self The this pointer
- # @param initiator The initiator object
- # @param flags Reserved for future use, must be zero
- @_return_requires([Volume])
- def volumes_accessible_by_initiator(self, initiator, flags=0):
- """
- Returns a list of volumes that the initiator has access to.
- """
- return self._tp.rpc('volumes_accessible_by_initiator',
- _del_self(locals()))
-
- ## Returns a list of initiators that have access to the specified volume.
- # @param self The this pointer
- # @param volume The volume in question
- # @param flags Reserved for future use, must be zero
- # @returns List of initiators
- @_return_requires([Initiator])
- def initiators_granted_to_volume(self, volume, flags=0):
- return self._tp.rpc('initiators_granted_to_volume', _del_self(locals()))
-
## Returns an array of volume objects
# @param self The this pointer
# @param flags Reserved for future use, must be zero.
@@ -658,6 +596,159 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('volume_offline', _del_self(locals()))

+ @_return_requires([MaskInfo])
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ """
+ lsm.Client.mask_infos(self, search_key=None, search_value=None,
+ flags=0)
+
+ Usage:
+ Change the initiators of certain access group.
+ If 'search_key' and 'search_value' is defined, only system matches
+ will be contained in return list.
+ Valid search keys are stored in lsm.MaskInfo.SUPPORTED_SEARCH_KEYS:
+ volume_id # Search lsm.AccessGroup.volume_id property
+ access_group_id # Search lsm.AccessGroup.access_group_id
+ # property
+ system_id # Search mask info on given system.
+ This method does not check whether provided resources ID is valid
+ or not, if resource ID is not exist at all, the search result will
+ be a empty list.
+ When no matches found, return a empty list
+ Parameters:
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # Valid value:
+ # lsm.MaskInfo.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.MaskInfo object will contain
+ # # optional properties. But currently,
+ # # there is no optional property for
+ # # lsm.MaskInfo yet, this will make no
+ # # difference.
+ Returns:
+ [lsm.MaskInfo,] # A list of lsm.MaskInfo objects.
+ or
+ [] # Nothing found.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
+ # Got invalid search key.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.MASK_INFOS
+ # Indicate current system support query mask info via
+ # lsm.Client.mask_infos() method.
+ lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all mask info
+ # without 'search_key' parameter, then filter in user's code
+ # space.
+ """
+ if search_key and search_key not in MaskInfo.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search key %s" % search_key)
+
+ return self._tp.rpc('mask_infos', _del_self(locals()))
+
+ @_return_requires(MaskInfo)
+ def volume_mask(self, volume, access_group, access_type, flags=0):
+ """
+ lsm.Client.volume_mask(self, volume, access_group, access_type,
+ flags=0)
+
+ Usage:
+ Mask certain volume to defined access group.
+ To change access_type of existing mask info, just invoke this
+ method with new access_type.
+ Storage will never mask single volume to single access group with
+ two or more LUN IDs, duplicated call of this method will not
+ resulting mulitpal masking for single voluem to single access
+ group.
+ For those storage array which does not support changing existing
+ masking relationship, they will deleting existing and creating new
+ one.
+ Parameters:
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ access_type # Integer. Check lsm.MaskInfo.access_type property.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ lsm.MaskInfo # Object of lsm.MaskInfo.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support volume_mask()
+ lsm.ErrorNumber.NOT_FOUND_VOLUME
+ # Defined lsm.Volume does not exists.
+ lsm.ErrorNumber.NOT_FOUND_ACCESS_GROUP
+ # Defined lsm.AccessGroup does not exists.
+ lsm.ErrorNumber.UNSUPPORTED_ACCESS_TYPE
+ # Defined access type is not support by storage system
+ lsm.ErrorNumber.UNALLOWED_ACCESS_TYPE
+ # Defined volume does not allow defined access type.
+ # Currently, we have no capability to allow pre-invoke
+ # check on this.
+ # Example: DR(Disaster Recover) site volumes does not
+ # allow RW access, but other normal volume can still be
+ # masked with RW access.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.VOLUME_MASK
+ # Indicate current system support volume masking via
+ # lsm.Client.volume_mask() method.
+ lsm.Capabilities.VOLUME_MASK_RO
+ # Indicate current system support masking a volume to
+ # access group with read only access.
+ """
+ return self._tp.rpc('volume_mask', _del_self(locals()))
+
+ @_return_requires(None)
+ def volume_unmask(self, volume=None, access_group=None, flags=0):
+ """
+ lsm.Client.volume_unmask(self, volume=None, access_group=None, flags=0)
+
+ Usage:
+ Unmask the volume. If define volume is not masked to access group,
+ this method will do nothing but return.
+ Parameters:
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Unmask done without error.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support volume unmask.
+ lsm.ErrorNumber.NOT_FOUND_VOLUME
+ # Defined lsm.Volume does not exist.
+ lsm.ErrorNumber.NOT_FOUND_ACCESS_GROUP
+ # Defined lsm.AccessGroup does not exist.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.VOLUME_UNMASK
+ # Indicate current system support volume unmasking via
+ # lsm.Client.volume_unmask() method.
+ """
+ return self._tp.rpc('volume_unmask', _del_self(locals()))
+
## Returns an array of disk objects
# @param self The this pointer
# @param flags When equal to DISK.RETRIEVE_FULL_INFO
@@ -672,128 +763,248 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('disks', _del_self(locals()))

- ## Access control for allowing an access group to access a volume
- # @param self The this pointer
- # @param group The access group
- # @param volume The volume to grant access to
- # @param access The desired access
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_grant(self, group, volume, access, flags=0):
- """
- Allows an access group to access a volume.
- """
- return self._tp.rpc('access_group_grant', _del_self(locals()))
-
- ## Revokes access to a volume to initiators in an access group
- # @param self The this pointer
- # @param group The access group
- # @param volume The volume to grant access to
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_revoke(self, group, volume, flags=0):
- """
- Revokes access for an access group for a volume
- """
- return self._tp.rpc('access_group_revoke', _del_self(locals()))
-
- ## Returns a list of access group objects
- # @param self The this pointer
- # @param flags Reserved for future use, must be zero.
- # @returns List of access groups
@_return_requires([AccessGroup])
- def access_groups(self, flags=0):
- """
- Returns a list of access groups
- """
- return self._tp.rpc('access_group_list', _del_self(locals()))
-
- ## Creates an access a group with the specified initiator in it.
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ """
+ lsm.Client.access_groups(self, search_key=None, search_value=None,
+ flags=0)
+
+ Usage:
+ Return a list of objects of lsm.AccessGroup.
+ If 'search_key' and 'search_value' is defined, only access group
+ matches will be contained in return list.
+ Valid search keys are stored in
+ lsm.AccessGroup.SUPPORTED_SEARCH_KEYS:
+ access_group_id # Search lsm.AccessGroup.id property
+ name # Search lsm.AccessGroup.name property
+ init_id # If given init_id is contained in
+ # lsm.AccessGroup.init_ids
+ volume_id # If access group is masked to given volume.
+ system_id # Return access group for given system only.
+ This method does not check whether provided resources ID is valid
+ or not, if resource ID is not exist at all, the search result will
+ be a empty list.
+ When no matches found, return a empty list
+ Parameters:
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # Valid value:
+ # lsm.AccessGroup.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.AccessGroup object will
+ # # contain optional properties.
+ # # But currently, there is no optional
+ # # property for lsm.AccessGroup yet, this
+ # # will make no difference.
+ Returns:
+ [lsm.AccessGroup,] # A list of lsm.AccessGroup objects.
+ or
+ [] # Nothing found.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
+ # Got unsupported search key. Should be one of
+ # lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ access_groups = lsm_cli_obj.access_groups(
+ search_key=None, search_value=None, flags=0)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUPS
+ # Indicate current system support access group query via
+ # lsm.Client.access_groups() method.
+ lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all access
+ # groups without 'search_key' parameter, then filter in user's
+ # code space.
+ """
+ if search_key and search_key not in AccessGroup.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search key %s" % search_key)
+
+ if search_key == 'access_group_id':
+ search_key = 'id'
+
+ return self._tp.rpc('access_groups', _del_self(locals()))
+
+ ## Creates an access group with the specified initiators in it.
# @param self The this pointer
- # @param name The initiator group name
- # @param initiator_id Initiator id
- # @param id_type Type of initiator (Enumeration)
+ # @param access_group_name The access group name
+ # @param init_ids List, Initiator ids
+ # @param init_type Type of initiator (Enumeration)
# @param system_id Which system to create this group on
# @param flags Reserved for future use, must be zero.
# @returns AccessGroup on success, else raises LsmError
@_return_requires(AccessGroup)
- def access_group_create(self, name, initiator_id, id_type, system_id,
- flags=0):
- """
- Creates an access group and add the specified initiator id, id_type and
- desired access.
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
+ """
+ lsm.Client.access_group_create(self, access_group_name, init_ids,
+ init_type, system_ids, flags=0)
+
+ Usage:
+ Create a access group.
+ Parameters:
+ access_group_name # String. The name for new access group.
+ # If provided access_group_name does not meet
+ # the requirement of storage system, storage
+ # system will chose a valid one in stead of
+ # raise a error.
+ init_ids # List of string. The initiator IDs.
+ init_type # Integer. The type of initiator, could be one
+ # of these values:
+ # * lsm.AccessGroup.INIT_TYPE_WWPN
+ # * lsm.AccessGroup.INIT_TYPE_WWNN
+ # * lsm.AccessGroup.INIT_TYPE_HOSTNAME
+ # * lsm.AccessGroup.INIT_TYPE_ISCSI_IQN
+ # * lsm.AccessGroup.INIT_TYPE_SAS
+ system_id # The id of lsm.System where this access group
+ # should create on.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ lsm.AccessGroup # Object of lsm.AccessGroup
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support creating access group.
+ lsm.ErrorNumber.INVALID_INIT
+ # Provide init_ids contain ilegal string. Exmaple:
+ # WWPN does not in (?:[0-9a-f]{2}:){7}[0-9a-f]{2} format.
+ # Its user's responsibity for convering init_ids into lsm
+ # requested format.
+ lsm.ErrorNumber.INVALID_INIT_TYPE
+ # Got one of these ilegal init_type:
+ # * lsm.AccessGroup.INIT_TYPE_OTHER
+ # * lsm.AccessGroup.INIT_TYPE_UNKNOWN
+ lsm.ErrorNumber.UNSUPPORTED_INIT_TYPE
+ # System does not support requested init_type.
+ # For example, on FC only storage array, when user trying
+ # to create a iSCSI access group, this error could be
+ # used.
+ lsm.ErrorNumber.EXCEED_LIMITATION
+ # Storage system refuse to create new access group due to
+ # internal limitations. For example:
+ # * System only allow max 10 initiators in one access
+ # group. When creating access group with 11 or more
+ # initiators, this error will be used.
+ # * System only allow 100 access groups, when created new
+ access group after that, this error will be used.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_CREATE
+ # Indicate current system support create access group via
+ # lsm.Client.access_group_create() method.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWPN
+ # Support creating WWPN access group.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWNN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_HOSTNAME
+ lsm.Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_SAS
"""
return self._tp.rpc('access_group_create', _del_self(locals()))

## Deletes an access group.
- # @param self The this pointer
- # @param group The access group to delete
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_del(self, group, flags=0):
- """
- Deletes an access group
- """
- return self._tp.rpc('access_group_del', _del_self(locals()))
-
- ## Adds an initiator to an access group
- # @param self The this pointer
- # @param group Group to add initiator to
- # @param initiator_id Initiators id
- # @param id_type Initiator id type (enumeration)
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_add_initiator(self, group, initiator_id, id_type,
- flags=0):
- """
- Adds an initiator to an access group
- """
- return self._tp.rpc('access_group_add_initiator', _del_self(locals()))
-
- ## Deletes an initiator from an access group
# @param self The this pointer
- # @param group The access group to remove initiator from
- # @param initiator_id The initiator to remove from the group
+ # @param access_group The lsm.AccessGroup to delete
# @param flags Reserved for future use, must be zero.
# @returns None on success, throws LsmError on errors.
@_return_requires(None)
- def access_group_del_initiator(self, group, initiator_id, flags=0):
- """
- Deletes an initiator from an access group
- """
- return self._tp.rpc('access_group_del_initiator', _del_self(locals()))
-
- ## Returns the list of volumes that access group has access to.
- # @param self The this pointer
- # @param group The access group to list volumes for
- # @param flags Reserved for future use, must be zero.
- # @returns list of volumes
- @_return_requires([Volume])
- def volumes_accessible_by_access_group(self, group, flags=0):
- """
- Returns the list of volumes that access group has access to.
- """
- return self._tp.rpc('volumes_accessible_by_access_group',
- _del_self(locals()))
-
- ##Returns the list of access groups that have access to the specified
- #volume.
- # @param self The this pointer
- # @param volume The volume to list access groups for
- # @param flags Reserved for future use, must be zero.
- # @returns list of access groups
- @_return_requires([AccessGroup])
- def access_groups_granted_to_volume(self, volume, flags=0):
- """
- Returns the list of access groups that have access to the specified
- volume.
- """
- return self._tp.rpc('access_groups_granted_to_volume',
- _del_self(locals()))
+ def access_group_delete(self, access_group, flags=0):
+ """
+ lsm.Client.access_group_delete(self, access_group, flags=0)
+
+ Usage:
+ Delete a access group. Only access group has no volume masked to
+ can be deleted.
+ User can use this method to query volumes masked to give access
+ group:
+ lsm.Client.volumes(self, search_key='access_group_id',
+ search_value=access_group.id)
+ Parameters:
+ access_group # Object of lsm.AccessGroup to delete.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Access group deleted without errors.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support deleting a access group.
+ lsm.ErrorNumber.IS_MASKED
+ # Access group has volume masked to.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ lsm_cli_obj.access_group_delete(new_ag)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_DELETE
+ # Indicate current system support delete access group via
+ # lsm.Client.access_group_delete() method.
+ """
+ return self._tp.rpc('access_group_delete', _del_self(locals()))
+
+ ## Update an existing access group with new initiators.
+ # @param self The this pointer
+ # @param access_group The lsm.AccessGroup object
+ # @param new_init_ids New initiator IDs
+ # @param flags Reserved for future use, must be zero.
+ # @returns AccessGroup on success, else raises LsmError
+ @_return_requires(AccessGroup)
+ def access_group_edit(self, access_group, new_init_ids, flags=0):
+ """
+ lsm.Client.access_group_edit(self, access_group, new_init_ids, flags=0)
+
+ Usage:
+ Change the initiators of certain access group.
+ Will use initiators IDs defined in 'new_init_ids' to overide the
+ old initiators.
+ Parameters:
+ access_group # Object of lsm.AccessGroup to edit.
+ new_init_ids # List of string. New initiator IDs. Old one will
+ # be overided.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Access group edited without errors.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support deleting a access group.
+ lsm.ErrorNumber.INVALID_INIT
+ # Provide new_init_ids contain ilegal string.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ # Remove one WWPN out.
+ lsm_cli_obj.access_group_edit(new_ag, ['20:00:00:1b:21:67:65:d7'])
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_EDIT
+ # Indicate current system support edit access group member
+ # via lsm.Client.access_group_edit() method.
+ """
+ return self._tp.rpc('access_group_edit', _del_self(locals()))

## Checks to see if a volume has child dependencies.
# @param self The this pointer
diff --git a/lsm/lsm/_common.py b/lsm/lsm/_common.py
index 65a6cce..4b494f2 100644
--- a/lsm/lsm/_common.py
+++ b/lsm/lsm/_common.py
@@ -422,7 +422,7 @@ class ErrorNumber(object):
EXISTS_INITIATOR = 52
EXISTS_NAME = 53
FS_NOT_EXPORTED = 54
- INITIATOR_NOT_IN_ACCESS_GROUP = 55
+
EXISTS_POOL = 56
EXISTS_VOLUME = 57

@@ -432,6 +432,9 @@ class ErrorNumber(object):
INVALID_ERR = 103
INVALID_FS = 104
INVALID_INIT = 105
+ # Provided initiator ID is ilegal.
+ # For example, when 'init_type' is WWPN, 'INVALID_INIT' will be raised
+ # if you provide a iSCSI IQN.
INVALID_JOB = 106
INVALID_NAME = 107
INVALID_NFS = 108
@@ -449,7 +452,9 @@ class ErrorNumber(object):
INVALID_OPTIONAL_DATA = 120
INVALID_BLOCK_RANGE = 121

- IS_MAPPED = 125
+ IS_MASKED = 125
+ # volume is masked to certain access group.
+ # this error could be used when deleting a volume or access group.

NO_CONNECT = 150
NO_MAPPING = 151
@@ -503,6 +508,27 @@ class ErrorNumber(object):
DISK_BUSY = 500
VOLUME_BUSY = 501

+ UNSUPPORTED_SEARCH_KEY = 510
+ # Got unsupported search key when querying resources.
+
+ INVALID_INIT_TYPE = 511
+ # Got one of these ilegal init type:
+ # * lsm.AccessGroup.INIT_TYPE_OTHER
+ # * lsm.AccessGroup.INIT_TYPE_UNKNOWN
+
+ EXCEED_LIMITATION = 512
+ # Action was refused by storage system due to design limiation.
+ # For example, EMC VNX only allow 512 volumes in one pool.
+
+ UNSUPPORTED_ACCESS_TYPE = 513
+ # defined 'access_type' is not supported by storage system.
+ # For example, some array does not allow masking volume to a access group
+ # with read only access.
+
+ UNALLOWED_ACCESS_TYPE = 514
+ # Defined volume does not allow defined access type.
+ # Example: DR(Disaster Recover) site volumes does not allow RW access, but
+ # other normal volume can still be masked with RW access.

class JobStatus(object):
INPROGRESS = 1
diff --git a/lsm/lsm/_data.py b/lsm/lsm/_data.py
index 603bf16..c268ccd 100644
--- a/lsm/lsm/_data.py
+++ b/lsm/lsm/_data.py
@@ -263,48 +263,6 @@ class IData(object):


@default_property('id', doc="Unique identifier")
-@default_property('type', doc="Enumerated initiator type")
-@default_property('name', doc="User supplied name")
-class Initiator(IData):
- """
- Represents an initiator.
- """
- (TYPE_OTHER, TYPE_PORT_WWN, TYPE_NODE_WWN, TYPE_HOSTNAME, TYPE_ISCSI,
- TYPE_SAS) = (1, 2, 3, 4, 5, 7)
-
- _type_map = {1: 'Other', 2: 'Port WWN', 3: 'Node WWN', 4: 'Hostname',
- 5: 'iSCSI', 7: "SAS"}
-
- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'type': 'Type',
- }
-
- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'type']
-
- def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
- if not enum_as_number:
- if key_name == 'type':
- value = Initiator._type_to_str(value)
- return value
-
- @staticmethod
- def _type_to_str(init_type):
- return Initiator._type_map[init_type]
-
- def __init__(self, _id, _type, _name):
-
- if not _name or not len(_name):
- name = "Unsupported"
-
- self._id = _id # Identifier
- self._type = _type # Initiator type id
- self._name = _name # Initiator name
-
-
-@default_property('id', doc="Unique identifier")
@default_property('name', doc="Disk name (aka. vendor)")
@default_property('disk_type', doc="Enumerated type of disk")
@default_property('block_size', doc="Size of each block")
@@ -1404,31 +1362,48 @@ class BlockRange(IData):

@default_property('id', doc="Unique instance identifier")
@default_property('name', doc="Access group name")
-@default_property('initiators', doc="List of initiators")
+@default_property('init_ids', doc="List of initiators IDs")
+@default_property('init_type', doc="Initiator type")
@default_property('system_id', doc="System identifier")
+@default_property("optional_data", doc="Optional data")
class AccessGroup(IData):
- def __init__(self, _id, _name, _initiators, _system_id='NA'):
- self._id = _id
- self._name = _name # AccessGroup name
- self._initiators = _initiators # List of initiators
- self._system_id = _system_id # System id this group belongs

- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'initiators': 'Initiator IDs',
- 'system_id': 'System ID',
- }
+ FLAG_RETRIEVE_FULL_INFO = 1 << 0

- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'initiators', 'system_id']
- _OPT_PROPERTIES_SEQUENCE = []
+ SUPPORTED_SEARCH_KEYS = ['id', 'access_group_id', 'name', 'init_ids',
+ 'volume_id', 'system_id']

- def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
- if list_convert:
- if key_name == 'initiators':
- value = ','.join(str(x) for x in value)
- return value
+ INIT_TYPE_UNKNOWN = 0
+ INIT_TYPE_OTHER = 1
+ INIT_TYPE_WWPN = 2
+ INIT_TYPE_WWNN = 3
+ INIT_TYPE_HOSTNAME = 4
+ INIT_TYPE_ISCSI_IQN = 5
+ INIT_TYPE_SAS = 6
+
+ OPT_PROPERTIES = []
+
+ def __init__(self, _id, _name, _init_ids, _init_type, _system_id,
+ _optional_data=None):
+ self._id = _id
+ self._name = _name # AccessGroup name
+ self._init_ids = _init_ids # List of initiators
+ self._init_type = _init_type # Initiator type
+ self._system_id = _system_id # System id this group belongs
+
+ if _optional_data is None:
+ self._optional_data = OptionalData()
+ else:
+ #Make sure the properties only contain ones we permit
+ allowed = set(AccessGroup.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ if actual <= allowed:
+ self._optional_data = _optional_data
+ else:
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))


class OptionalData(IData):
@@ -1492,30 +1467,31 @@ class Capabilities(IData):
VOLUME_ONLINE = 34
VOLUME_OFFLINE = 35

- ACCESS_GROUP_GRANT = 36
- ACCESS_GROUP_REVOKE = 37
- ACCESS_GROUP_LIST = 38
- ACCESS_GROUP_CREATE = 39
- ACCESS_GROUP_DELETE = 40
- ACCESS_GROUP_ADD_INITIATOR = 41
- ACCESS_GROUP_DEL_INITIATOR = 42
+ ACCESS_GROUPS = 36
+ ACCESS_GROUPS_QUICK_SEARCH = 37
+ ACCESS_GROUP_CREATE = 38
+ ACCESS_GROUP_DELETE = 39
+ ACCESS_GROUP_EDIT = 39

- VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
- ACCESS_GROUPS_GRANTED_TO_VOLUME = 44
+ ACCESS_GROUP_CREATE_WWPN = 40
+ ACCESS_GROUP_CREATE_WWNN = 41
+ ACCESS_GROUP_CREATE_HOSTNAME = 42
+ ACCESS_GROUP_CREATE_ISCSI_IQN = 43
+ ACCESS_GROUP_CREATE_SAS = 44

VOLUME_CHILD_DEPENDENCY = 45
VOLUME_CHILD_DEPENDENCY_RM = 46

- INITIATORS = 47
- INITIATORS_GRANTED_TO_VOLUME = 48
-
- VOLUME_INITIATOR_GRANT = 50
- VOLUME_INITIATOR_REVOKE = 51
- VOLUME_ACCESSIBLE_BY_INITIATOR = 52
VOLUME_ISCSI_CHAP_AUTHENTICATION = 53

VOLUME_THIN = 55

+ MASK_INFOS = 60
+ MASK_INFOS_QUICK_SEARCH = 61
+ VOLUME_MASK = 62
+ VOLUME_MASK_RO = 63
+ VOLUME_UNMASK = 64
+
#File system
FS = 100
FS_DELETE = 101
@@ -1622,6 +1598,42 @@ class PlugData(IData):
self.desc = description
self.version = plugin_version

+@default_property('volume_id', doc="Volume ID")
+@default_property('access_group_id', doc="Access Group ID")
+@default_property('access_type', doc="Access Type")
+@default_property('system_id', doc="System ID")
+@default_property("optional_data", doc="Optional data")
+class MaskInfo(IData):
+
+ ACCESS_TYPE_UNKNOWN = 0
+ ACCESS_TYPE_READ_WRITE = 1
+ ACCESS_TYPE_READ_ONLY = 2
+
+ OPT_PROPERTIES = []
+
+ SUPPORTED_SEARCH_KEYS = ['volume_id', 'access_group_id', 'system_id']
+
+ def __init__(self, _volume_id, _access_group_id, _access_type,
+ _system_id, _optional_data=None):
+ self._volume_id = _volume_id
+ self._access_group_id = _access_group_id
+ self._access_type = _access_type
+ self._system_id = _system_id
+
+ if _optional_data is None:
+ self._optional_data = OptionalData()
+ else:
+ #Make sure the properties only contain ones we permit
+ allowed = set(MaskInfo.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ if actual <= allowed:
+ self._optional_data = _optional_data
+ else:
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))
+

if __name__ == '__main__':
#TODO Need some unit tests that encode/decode all the types with nested
--
1.8.3.1
Gris Ge
2014-04-23 12:40:59 UTC
Permalink
* Removed old volume masking commands:
access-grant
access-group-grant
access-revoke
access-group-revoke
volumes_accessible_by_access_group
volumes-accessible-initiator
access-group-volumes
initiators-granted-volume
* Added new commands:
volume-mask
volume-unmask
* New list type:
MASK_INFOS
* New options for list command on filtering:
--vol
--ag
--sys
# Currently only supported by list type: MASK_INFOS and ACCESS_GROUPS
* When '-o, --optional' define, the display method will be forced to script
way.

Signed-off-by: Gris Ge <***@redhat.com>
---
doc/man/lsmcli.1.in | 126 ++++++-------
lsm/lsm/_cmdline.py | 398 ++++++++++++++++-------------------------
lsm/lsm/lsmcli_data_display.py | 149 +++++++++++----
3 files changed, 319 insertions(+), 354 deletions(-)

diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..f7b908a 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -117,7 +117,7 @@ circumstance, \fb-b\fR will be effective, command will wait until finished.
\fB-s\fR, \fB--script\fR
Displaying data in script friendly way.
.br
-Without this option, data is displayed in this manner:
+Without this option, data is displayed in this column way:

ID | Name | Member IDs
-------------------------------------+-------+-------------
@@ -126,7 +126,7 @@ Without this option, data is displayed in this manner:
aa0ffc70-3dba-11df-b8cf-00a0980ad71b | aggr1 | ID_C
| | ID_D

-With this option, data is displayed in this manner.
+With this option, data is displayed in this script way.

--------------------------------------------------
ID | 87720e90-3a83-11df-b8bf-00a0980ad71b
@@ -139,6 +139,11 @@ With this option, data is displayed in this manner.
Member IDs | ID_C
| ID_D
--------------------------------------------------
+Please note:
+Not all mandatory properties will be displayed in column way considering the
+user's console text width. All mandatory properties will be displayed in
+script way. When with "-o, --optional", both mandatory properties and optional
+properties will be displayed.

.SH COMMANDS
.SS list
@@ -147,11 +152,25 @@ List information on LSM objects
\fB--type\fR \fI<TYPE>\fR
Required. Valid values are:
.br
-\fBVOLUMES\fR, \fBINITIATORS\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR,
+\fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR, \fBMASK_INFOS\fR,
\fBEXPORTS\fR, \fBDISKS\fR,
.br
\fBNFS_CLIENT_AUTH\fR, \fBACCESS_GROUPS\fR,
\fBSYSTEMS\fR, \fBPLUGINS.
+.br
+The \fBVOLUMES\fR well-known as LUN by many storage array vendors. It is a
+virtual storage space which host operation system treated as a or many block
+device(s).
+.br
+The \fBACCESS_GROUPS\fR define a group of initiators to access volume.
+For example, most FC/FCoE HBA cards has two ports, each of two ports has
+one(or more with NPIV) WWPN(s). When performing volume masking, a volume is
+often mask to a access group containing all of these WWPNs. On EMC devices,
+access group is refered as "Host Group". On NetApp ONTAP devices, access group
+is refered as "Initiator Group(igroup)".
+.br
+The \fBMASK_INFOS\fR reprent a relationship between access group and volume.
+The volume masking is well-known as "LUN Masking".
.TP
\fB--fs\fR \fI<FS_ID>\fR
Required for \fB--type\fR=\fBSNAPSHOTS\fR. List the snapshots of certain
@@ -160,7 +179,18 @@ PLUGINS will list all supported plugins of LSM, not only the current one.
.TP
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
-available.
+available. When define, will use '--script' display method.
+By default(without this argument), only mandatory properties will be
+retrieved.
+.TP
+\fB--vol\fR \fI<VOL_ID>\fR
+Optional. Filter with volume ID: <VOL_ID>
+.TP
+\fB--ag\fR \fI<AG_ID>\fR
+Optional. Filter with access group ID: <AG_ID>
+.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Optional. Filter with system ID: <SYS_ID>

.SS job-status
Retrieve information about a job.
@@ -281,43 +311,30 @@ Removes volume dependencies(like replication).
\fB--vol\fR \fI<VOL_ID>\fR
Required. The ID of volume to remove dependency.

-.SS volume-access-group
-Lists the access group(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS volumes-accessible-initiator
-Lists the initiator(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS access-group-grant
+.SS volume-mask
.TP 15
-Grant a access group the RO or RW access to certain volume. Like LUN masking
-or NFS export.
+Mask a volume to certain access group. When defined volume is already masked
+to defined access group, the new '--access' will overide the old one.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to access.
+Required. The ID of volume to mask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to grant.
-
+Required. ID of access group to mask.
.TP
\fB--access\fR \fI<ACCESS>\fR
-Optional. Permission of access, valid values are \fBRO\fR, \fBRW\fR. Default
-value is \fBRW\fR.
+Optional. Define the access type, valid values are \fBRW\fR and \fBRO\fR,
+stand for "Read and Write" and "Read Only". Default value is RW.

-.SS access-group-revoke
+.SS volume-unmask
.TP 15
-Revoke an access group the RO or RW access to certain volume.
+Unmask a volume from certain access group.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
+Required. The ID of volume to unmask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to revoke.
+Required. ID of access group to unmask.

.SS access-group-create
.TP 15
@@ -327,77 +344,36 @@ Create an access group.
Required. The human friendly name for new access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. The first initiator ID of new access group.
+Required. Repeatable. The initiator ID of new access group. For more than one
+initiators, use this: '\fB--init INIT_ID_1 --init INIT_ID_2\fR'.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
+\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR. All initiators defined should be in
+same initiator type.
.TP
\fB--sys\fR \fI<SYS_ID>\fR
Required. The ID of system where this access group should reside on.

-.SS access-group-add
+.SS access-group-edit
Adds an initiator to an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to add.
+Required. Repeatable. New initiator ID.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.

-.SS access-group-remove
-Removes an initiator from an access group.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. ID of access group.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to remove.
-
.SS access-group-delete
Delete an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group to delete.

-.SS access-grant
-Grants access to an initiator to a volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to grant access.
-.TP
-\fB--init-type\fR \fI<INIT_TYPE>\fR
-Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
-.TP
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to grant access.
-
-.SS access-revoke
-Removes access for an initiator to a volume
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to revoke.
-
-.SS access-group-volumes
-Lists the volumes that the access group has been granted access to.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to query.
-
-.SS initiators-granted-volume
-Lists the initiators that have been granted access to specified volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. The ID of initiator to query.
-
.SS iscsi-chap
Configures ISCSI inbound/outbound CHAP authentication
.TP 15
diff --git a/lsm/lsm/_cmdline.py b/lsm/lsm/_cmdline.py
index 778647e..e174674 100644
--- a/lsm/lsm/_cmdline.py
+++ b/lsm/lsm/_cmdline.py
@@ -23,8 +23,8 @@ from argparse import ArgumentParser
from argparse import RawTextHelpFormatter

from lsm import (Client, Pool, VERSION, LsmError, Capabilities, Disk,
- Initiator, Volume, JobStatus, ErrorNumber, BlockRange,
- uri_parse)
+ Volume, JobStatus, ErrorNumber, BlockRange, uri_parse,
+ AccessGroup, MaskInfo)

from _data import PlugData
from _common import getch, size_human_2_size_bytes, Proxy
@@ -92,7 +92,7 @@ def _get_item(l, the_id, friendly_name='item'):
return i
raise ArgError('%s with id %s not found!' % (friendly_name, the_id))

-list_choices = ['VOLUMES', 'INITIATORS', 'POOLS', 'FS', 'SNAPSHOTS',
+list_choices = ['VOLUMES', 'MASK_INFOS', 'POOLS', 'FS', 'SNAPSHOTS',
'EXPORTS', "NFS_CLIENT_AUTH", 'ACCESS_GROUPS',
'SYSTEMS', 'DISKS', 'PLUGINS']

@@ -165,13 +165,22 @@ cmds = (
type=str.upper),
],
optional=[
- dict(name=('-a', '--all'),
- help='Retrieve and display in script friendly way with ' +
- 'all information including optional data if available',
+ dict(name=('-o', '--optional'),
+ help='Retrieve both mandatory and optional properties.\n'
+ 'Once define, will use "-s, --script" display way',
default=False,
- dest='all',
+ dest='optional',
action='store_true'),
dict(fs_id_opt),
+ dict(name=('--vol'),
+ metavar='<VOL_ID>',
+ help='Filter with defined volume ID'),
+ dict(name=('--ag'),
+ metavar='<AG_ID>',
+ help='Filter with defined access group ID'),
+ dict(name=('--sys'),
+ metavar='<SYS_ID>',
+ help='Filter with defined system ID'),
],
),

@@ -231,6 +240,28 @@ cmds = (
),

dict(
+ name='volume-mask',
+ help='Mask a volume to certain access group',
+ args=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ ],
+ optional=[
+ dict(access_opt),
+ ],
+ ),
+
+ dict(
+ name='volume-unmask',
+ help='Unmask a volume from certain access group via mask ID or ' +
+ 'combination of volume ID and access group ID',
+ args=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ ],
+ ),
+
+ dict(
name='volume-replicate',
help='Creates a new volume and peplicates provided volume to it.',
args=[
@@ -297,78 +328,19 @@ cmds = (
),

dict(
- name='volume-access-group',
- help='Lists the access group(s) that have access to volume',
- args=[
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='volumes-accessible-initiator',
- help='Lists the volumes that are accessible by the initiator',
- args=[
- dict(init_id_opt),
- ],
- ),
-
-
- dict(
- name='access-group-grant',
- help='Grants access to an access group to a volume, '
- 'like LUN Masking',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-group-revoke',
- help='Revoke the access of certain access group to a volume',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
name='access-group-create',
help='Create an access group',
args=[
dict(name='--name', metavar='<AG_NAME>',
help="Human readble name for access group"),
- # TODO: _client.py access_group_create should support multipal
- # initiators when creating.
- dict(init_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
dict(init_type_opt),
dict(sys_id_opt),
],
),

- # TODO: Merge access_group_add() and access_group_remove() into
- # access_group_mofidy(ag_id, new_inits, init_type, flags)
- dict(
- name='access-group-add',
- help='Add an initiator into existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- dict(init_type_opt),
- ],
- ),
- dict(
- name='access-group-remove',
- help='Remove an initiator from existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- ],
- ),
-
dict(
name='access-group-delete',
help='Deletes an access group',
@@ -378,42 +350,13 @@ cmds = (
),

dict(
- name='access-grant',
- help='Grants access to an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(init_type_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-revoke',
- help='Removes access for an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='access-group-volumes',
- help='Lists the volumes that the access group has'
- ' been granted access to',
+ name='access-group-edit',
+ help='Edit the initiators of an access group',
args=[
dict(ag_id_opt),
- ],
- ),
-
- dict(
- name='initiators-granted-volume',
- help='Lists the initiators that have been '
- 'granted access to specified volume',
- args=[
- dict(vol_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
],
),

@@ -720,6 +663,28 @@ class CmdLine:
"""
Command line interface class.
"""
+ @staticmethod
+ def _init_type_to_enum(init_type_str):
+ _INIT_TYPE_STR_2_TYPE = {
+ 'WWPN': AccessGroup.INIT_TYPE_WWPN,
+ 'WWNN': AccessGroup.INIT_TYPE_WWNN,
+ 'ISCSI': AccessGroup.INIT_TYPE_ISCSI_IQN,
+ 'HOSTNAME': AccessGroup.INIT_TYPE_HOSTNAME,
+ 'SAS': AccessGroup.INIT_TYPE_SAS,
+ }
+ if init_type_str in _INIT_TYPE_STR_2_TYPE.keys():
+ return _INIT_TYPE_STR_2_TYPE[init_type_str]
+ raise ArgError("Invalid init_type string %s" % init_type_str)
+
+ @staticmethod
+ def _access_type_to_enum(access_type_str):
+ _ACCESS_TYPE_2_TYPE = {
+ 'RW': MaskInfo.ACCESS_TYPE_READ_WRITE,
+ 'RO': MaskInfo.ACCESS_TYPE_READ_ONLY,
+ }
+ if access_type_str in _ACCESS_TYPE_2_TYPE.keys():
+ return _ACCESS_TYPE_2_TYPE[access_type_str]
+ raise ArgError("Invalid access_type string %s" % access_type_str)

##
# Warn of imminent data loss
@@ -817,9 +782,6 @@ class CmdLine:
if len(objects) == 0:
return

- if hasattr(self.args, 'all') and self.args.all:
- display_all = True
-
flag_with_header = True
if self.args.sep:
flag_with_header = False
@@ -830,6 +792,10 @@ class CmdLine:
if self.args.script:
display_way = DisplayData.DISPLAY_WAY_SCRIPT

+ if hasattr(self.args, 'optional') and self.args.optional:
+ display_all = True
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
flag_new_way_works = DisplayData.display_data(
objects, display_way=display_way, flag_human=self.args.human,
flag_enum=self.args.enum, extra_properties=extra_properties,
@@ -1078,10 +1044,28 @@ class CmdLine:
## Method that calls the appropriate method based on what the list type is
# @param args Argparse argument object
def list(self, args):
+ search_key = None
+ search_value = None
+ if args.vol:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'volume_id'
+ search_value = args.vol
+ if args.ag:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'access_group_id'
+ search_value = args.ag
+ if args.sys:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'system_id'
+ search_value = args.sys
+
if args.type == 'VOLUMES':
self.display_data(self.c.volumes())
elif args.type == 'POOLS':
- if args.all:
+ if args.optional:
self.display_data(
self.c.pools(Pool.RETRIEVE_FULL_INFO))
else:
@@ -1094,88 +1078,47 @@ class CmdLine:

fs = _get_item(self.c.fs(), args.fs, 'filesystem')
self.display_data(self.c.fs_snapshots(fs))
- elif args.type == 'INITIATORS':
- self.display_data(self.c.initiators())
elif args.type == 'EXPORTS':
self.display_data(self.c.exports())
elif args.type == 'NFS_CLIENT_AUTH':
self.display_nfs_client_authentication()
elif args.type == 'ACCESS_GROUPS':
- self.display_data(self.c.access_groups())
+ self.display_data(self.c.access_groups(
+ search_key, search_value, 0))
elif args.type == 'SYSTEMS':
self.display_data(self.c.systems())
elif args.type == 'DISKS':
- if args.all:
+ if args.optional:
self.display_data(
self.c.disks(Disk.RETRIEVE_FULL_INFO))
else:
self.display_data(self.c.disks())
elif args.type == 'PLUGINS':
self.display_available_plugins()
+ elif args.type == 'MASK_INFOS':
+ self.display_data(self.c.mask_infos(search_key, search_value, 0))
else:
raise ArgError("unsupported listing type=%s" % args.type)

- ## Converts type initiator type to enumeration type.
- # @param type String representation of type
- # @returns Enumerated value
- @staticmethod
- def _init_type_to_enum(init_type):
- if init_type == 'WWPN':
- i = Initiator.TYPE_PORT_WWN
- elif init_type == 'WWNN':
- i = Initiator.TYPE_NODE_WWN
- elif init_type == 'ISCSI':
- i = Initiator.TYPE_ISCSI
- elif init_type == 'HOSTNAME':
- i = Initiator.TYPE_HOSTNAME
- elif init_type == 'SAS':
- i = Initiator.TYPE_SAS
- else:
- raise ArgError("invalid initiator type " + init_type)
- return i
-
## Creates an access group.
def access_group_create(self, args):
- i = CmdLine._init_type_to_enum(args.init_type)
- access_group = self.c.access_group_create(args.name, args.init, i,
- args.sys)
+ init_type = CmdLine._init_type_to_enum(args.init_type)
+ access_group = self.c.access_group_create(
+ args.name, args.init, init_type, args.sys)
self.display_data([access_group])

- def _add_rm_access_grp_init(self, args, op):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
-
- if op:
- i = CmdLine._init_type_to_enum(args.init_type)
- self.c.access_group_add_initiator(
- group, args.init, i)
- else:
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- self.c.access_group_del_initiator(group, i.id)
-
- ## Adds an initiator from an access group
- def access_group_add(self, args):
- self._add_rm_access_grp_init(args, True)
-
- ## Removes an initiator from an access group
- def access_group_remove(self, args):
- self._add_rm_access_grp_init(args, False)
-
- def access_group_volumes(self, args):
+ ## Used to delete access group
+ def access_group_delete(self, args):
agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- vols = self.c.volumes_accessible_by_access_group(group)
- self.display_data(vols)
+ access_group = _get_item(agl, args.ag, "access group id")
+ return self.c.access_group_delete(access_group)

- def volumes_accessible_initiator(self, args):
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- volumes = self.c.volumes_accessible_by_initiator(i)
- self.display_data(volumes)
-
- def initiators_granted_volume(self, args):
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- initiators = self.c.initiators_granted_to_volume(vol)
- self.display_data(initiators)
+ def access_group_edit(self, args):
+ access_groups = self.c.access_groups()
+ access_group = _get_item(access_groups, args.ag, "access group id")
+ new_access_group = self.c.access_group_edit(
+ access_group, args.init)
+ self.display_data([new_access_group])

def iscsi_chap(self, args):
init = _get_item(self.c.initiators(), args.init, "initiator id")
@@ -1184,17 +1127,6 @@ class CmdLine:
self.args.out_user,
self.args.out_pass)

- def volume_access_group(self, args):
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- groups = self.c.access_groups_granted_to_volume(vol)
- self.display_data(groups)
-
- ## Used to delete access group
- def access_group_delete(self, args):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- return self.c.access_group_del(group)
-
## Used to delete a file system
def fs_delete(self, args):
fs = _get_item(self.c.fs(), args.fs, "filesystem id")
@@ -1283,9 +1215,6 @@ class CmdLine:
cap = self.c.capabilities(s)
self._cp("BLOCK_SUPPORT", cap.get(Capabilities.BLOCK_SUPPORT))
self._cp("FS_SUPPORT", cap.get(Capabilities.FS_SUPPORT))
- self._cp("INITIATORS", cap.get(Capabilities.INITIATORS))
- self._cp("INITIATORS_GRANTED_TO_VOLUME",
- cap.get(Capabilities.INITIATORS_GRANTED_TO_VOLUME))
self._cp("VOLUMES", cap.get(Capabilities.VOLUMES))
self._cp("VOLUME_CREATE", cap.get(Capabilities.VOLUME_CREATE))
self._cp("VOLUME_RESIZE", cap.get(Capabilities.VOLUME_RESIZE))
@@ -1310,34 +1239,40 @@ class CmdLine:
self._cp("VOLUME_DELETE", cap.get(Capabilities.VOLUME_DELETE))
self._cp("VOLUME_ONLINE", cap.get(Capabilities.VOLUME_ONLINE))
self._cp("VOLUME_OFFLINE", cap.get(Capabilities.VOLUME_OFFLINE))
- self._cp("VOLUME_INITIATOR_GRANT",
- cap.get(Capabilities.VOLUME_INITIATOR_GRANT))
- self._cp("VOLUME_INITIATOR_REVOKE",
- cap.get(Capabilities.VOLUME_INITIATOR_REVOKE))
self._cp("VOLUME_THIN",
cap.get(Capabilities.VOLUME_THIN))
self._cp("VOLUME_ISCSI_CHAP_AUTHENTICATION",
cap.get(Capabilities.VOLUME_ISCSI_CHAP_AUTHENTICATION))
- self._cp("ACCESS_GROUP_GRANT",
- cap.get(Capabilities.ACCESS_GROUP_GRANT))
- self._cp("ACCESS_GROUP_REVOKE",
- cap.get(Capabilities.ACCESS_GROUP_REVOKE))
- self._cp("ACCESS_GROUP_LIST",
- cap.get(Capabilities.ACCESS_GROUP_LIST))
+ self._cp("ACCESS_GROUPS",
+ cap.get(Capabilities.ACCESS_GROUPS))
+ self._cp("ACCESS_GROUPS_QUICK_SEARCH",
+ cap.get(Capabilities.ACCESS_GROUPS_QUICK_SEARCH))
self._cp("ACCESS_GROUP_CREATE",
cap.get(Capabilities.ACCESS_GROUP_CREATE))
+ self._cp("ACCESS_GROUP_CREATE_WWPN",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_WWPN))
+ self._cp("ACCESS_GROUP_CREATE_WWNN",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_WWNN))
+ self._cp("ACCESS_GROUP_CREATE_HOSTNAME",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_HOSTNAME))
+ self._cp("ACCESS_GROUP_CREATE_ISCSI_IQN",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN))
+ self._cp("ACCESS_GROUP_CREATE_SAS",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_SAS))
self._cp("ACCESS_GROUP_DELETE",
cap.get(Capabilities.ACCESS_GROUP_DELETE))
- self._cp("ACCESS_GROUP_ADD_INITIATOR",
- cap.get(Capabilities.ACCESS_GROUP_ADD_INITIATOR))
- self._cp("ACCESS_GROUP_DEL_INITIATOR",
- cap.get(Capabilities.ACCESS_GROUP_DEL_INITIATOR))
- self._cp("VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP",
- cap.get(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP))
- self._cp("VOLUME_ACCESSIBLE_BY_INITIATOR",
- cap.get(Capabilities.VOLUME_ACCESSIBLE_BY_INITIATOR))
- self._cp("ACCESS_GROUPS_GRANTED_TO_VOLUME",
- cap.get(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME))
+ self._cp("ACCESS_GROUP_EDIT",
+ cap.get(Capabilities.ACCESS_GROUP_EDIT))
+ self._cp("MASK_INFOS",
+ cap.get(Capabilities.MASK_INFOS))
+ self._cp("MASK_INFOS_QUICK_SEARCH",
+ cap.get(Capabilities.MASK_INFOS_QUICK_SEARCH))
+ self._cp("VOLUME_MASK",
+ cap.get(Capabilities.VOLUME_MASK))
+ self._cp("VOLUME_MASK_RO",
+ cap.get(Capabilities.VOLUME_MASK_RO))
+ self._cp("VOLUME_UNMASK",
+ cap.get(Capabilities.VOLUME_UNMASK))
self._cp("VOLUME_CHILD_DEPENDENCY",
cap.get(Capabilities.VOLUME_CHILD_DEPENDENCY))
self._cp("VOLUME_CHILD_DEPENDENCY_RM",
@@ -1535,53 +1470,6 @@ class CmdLine:
s = _get_item(self.c.systems(), args.sys, "system id")
out(self.c.volume_replicate_range_block_size(s))

- ## Used to grant or revoke access to a volume to an initiator.
- # @param grant bool, if True we grant, else we un-grant.
- def _access(self, grant, args):
- v = _get_item(self.c.volumes(), args.vol, "volume id")
- initiator_id = args.init
-
- if grant:
- i_type = CmdLine._init_type_to_enum(args.init_type)
- access = 'DEFAULT'
- if args.access is not None:
- access = Volume._access_string_to_type(args.access)
-
- self.c.initiator_grant(initiator_id, i_type, v, access)
- else:
- initiator = _get_item(self.c.initiators(), initiator_id,
- "initiator id")
-
- self.c.initiator_revoke(initiator, v)
-
- ## Grant access to volume to an initiator
- def access_grant(self, args):
- return self._access(True, args)
-
- ## Revoke access to volume to an initiator
- def access_revoke(self, args):
- return self._access(False, args)
-
- def _access_group(self, args, grant=True):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- v = _get_item(self.c.volumes(), args.vol, "volume id")
-
- if grant:
- access = 'RW'
- if args.access is not None:
- access = args.access
- access = Volume._access_string_to_type(args.access)
- self.c.access_group_grant(group, v, access)
- else:
- self.c.access_group_revoke(group, v)
-
- def access_group_grant(self, args):
- return self._access_group(args, grant=True)
-
- def access_group_revoke(self, args):
- return self._access_group(args, grant=False)
-
## Re-sizes a volume
def volume_resize(self, args):
v = _get_item(self.c.volumes(), args.vol, "volume id")
@@ -1592,6 +1480,22 @@ class CmdLine:
*self.c.volume_resize(v, size))
self.display_data([vol])

+ def volume_mask(self, args):
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ access_type = MaskInfo.ACCESS_TYPE_READ_WRITE
+ if args.access:
+ access_type = CmdLine._access_type_to_enum(args.access)
+ mask_info = self.c.volume_mask(volume, access_group, access_type)
+ self.display_data([mask_info])
+
+ def volume_unmask(self, args):
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ self.c.volume_unmask(volume, access_group, 0)
+
## Removes a nfs export
def fs_unexport(self, args):
export = _get_item(self.c.exports(), args.export, "nfs export id")
diff --git a/lsm/lsm/lsmcli_data_display.py b/lsm/lsm/lsmcli_data_display.py
index 810f590..da835d5 100644
--- a/lsm/lsm/lsmcli_data_display.py
+++ b/lsm/lsm/lsmcli_data_display.py
@@ -18,7 +18,7 @@
import sys
from collections import OrderedDict

-from lsm import System, size_bytes_2_size_human
+from lsm import System, AccessGroup, MaskInfo, size_bytes_2_size_human


class EnumConvert(object):
@@ -55,6 +55,34 @@ class EnumConvert(object):
return EnumConvert.SYSTEM_STATUS_CONV[System.STATUS_UNKNOWN]
return rc

+ INIT_TYPE_CONV = {
+ AccessGroup.INIT_TYPE_UNKNOWN: 'Unknown',
+ AccessGroup.INIT_TYPE_OTHER: 'Other',
+ AccessGroup.INIT_TYPE_WWPN: 'WWPN',
+ AccessGroup.INIT_TYPE_WWNN: 'WWNN',
+ AccessGroup.INIT_TYPE_HOSTNAME: 'Hostname',
+ AccessGroup.INIT_TYPE_ISCSI_IQN: 'iSCSI IQN',
+ AccessGroup.INIT_TYPE_SAS: 'SAS',
+ }
+
+ @staticmethod
+ def init_type_to_str(init_type):
+ if init_type in EnumConvert.INIT_TYPE_CONV.keys():
+ return EnumConvert.INIT_TYPE_CONV[init_type]
+ return EnumConvert.INIT_TYPE_CONV[AccessGroup.INIT_TYPE_UNKNOWN]
+
+ ACCESS_TYPE_CONV = {
+ MaskInfo.ACCESS_TYPE_UNKNOWN: 'Unknown',
+ MaskInfo.ACCESS_TYPE_READ_WRITE: 'Read Write',
+ MaskInfo.ACCESS_TYPE_READ_ONLY: 'Read Only',
+ }
+
+ @staticmethod
+ def access_type_to_str(access_type):
+ if access_type in EnumConvert.ACCESS_TYPE_CONV.keys():
+ return EnumConvert.ACCESS_TYPE_CONV[access_type]
+ return EnumConvert.INIT_TYPE_CONV[MaskInfo.ACCESS_TYPE_UNKNOWN]
+

class DisplayData(object):

@@ -77,6 +105,9 @@ class DisplayData(object):

DEFAULT_SPLITTER = ' | '

+ VALUE_CONVERT = dict()
+
+ # lsm.System
SYSTEM_MAN_HEADER = OrderedDict()
SYSTEM_MAN_HEADER['id'] = 'ID'
SYSTEM_MAN_HEADER['name'] = 'Name'
@@ -85,8 +116,14 @@ class DisplayData(object):

SYSTEM_OPT_HEADER = OrderedDict()

- SYSTEM_DSP_HEADER = SYSTEM_MAN_HEADER # SYSTEM_DSP_HEADER should be
- # subset of SYSTEM_MAN_HEADER
+ SYSTEM_COLUME_KEYS = SYSTEM_MAN_HEADER.keys()
+ # SYSTEM_COLUME_KEYS should be subset of SYSTEM_MAN_HEADER.keys()
+ # XXX_COLUME_KEYS contain a list of mandatory properties which will be
+ # displayed in column way. It was used to limit the output of properties
+ # in sure the colume display way does not exceeded the column width 78.
+ # All mandatory_headers will be displayed in script way.
+ # if '-o' define, both mandatory_headers and optional_headers will be
+ # displayed in script way.

SYSTEM_VALUE_CONV_ENUM = {
'status': EnumConvert.system_status_to_str,
@@ -94,14 +131,61 @@ class DisplayData(object):

SYSTEM_VALUE_CONV_HUMAN = []

- VALUE_CONVERT = {
- System: {
- 'mandatory_headers': SYSTEM_MAN_HEADER,
- 'display_headers': SYSTEM_DSP_HEADER,
- 'optional_headers': SYSTEM_OPT_HEADER,
- 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
- 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
- }
+ VALUE_CONVERT[System] = {
+ 'mandatory_headers': SYSTEM_MAN_HEADER,
+ 'column_keys': SYSTEM_COLUME_KEYS,
+ 'optional_headers': SYSTEM_OPT_HEADER,
+ 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
+ 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.AccessGroup
+ ACCESS_GROUP_MAN_HEADER = OrderedDict()
+ ACCESS_GROUP_MAN_HEADER['id'] = 'ID'
+ ACCESS_GROUP_MAN_HEADER['name'] = 'Name'
+ ACCESS_GROUP_MAN_HEADER['init_ids'] = 'Initiators'
+ ACCESS_GROUP_MAN_HEADER['init_type'] = 'Type'
+ ACCESS_GROUP_MAN_HEADER['system_id'] = 'System ID'
+ ACCESS_GROUP_OPT_HEADER = OrderedDict()
+ ACCESS_GROUP_COLUME_KEYS = ACCESS_GROUP_MAN_HEADER
+ ACCESS_GROUP_VALUE_CONV_ENUM = {
+ 'init_type': EnumConvert.init_type_to_str,
+ }
+
+ ACCESS_GROUP_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[AccessGroup] = {
+ 'mandatory_headers': ACCESS_GROUP_MAN_HEADER,
+ 'column_keys': ACCESS_GROUP_COLUME_KEYS,
+ 'optional_headers': ACCESS_GROUP_OPT_HEADER,
+ 'value_conv_enum': ACCESS_GROUP_VALUE_CONV_ENUM,
+ 'value_conv_human': ACCESS_GROUP_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.MaskInfo
+ MASK_INFO_MAN_HEADER = OrderedDict()
+ MASK_INFO_MAN_HEADER['volume_id'] = 'Volume ID'
+ MASK_INFO_MAN_HEADER['access_group_id'] = 'Access Group ID'
+ MASK_INFO_MAN_HEADER['access_type'] = 'Access Type'
+ MASK_INFO_MAN_HEADER['system_id'] = 'System ID'
+
+ MASK_INFO_COLUME_KEYS = ['volume_id', 'access_group_id', 'access_type']
+
+ MASK_INFO_OPT_HEADER = OrderedDict()
+
+ # Hide system_id in default display.
+ MASK_INFO_VALUE_CONV_ENUM = {
+ 'access_type': EnumConvert.access_type_to_str,
+ }
+
+ MASK_INFO_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[MaskInfo] = {
+ 'mandatory_headers': MASK_INFO_MAN_HEADER,
+ 'column_keys': MASK_INFO_COLUME_KEYS,
+ 'optional_headers': MASK_INFO_OPT_HEADER,
+ 'value_conv_enum': MASK_INFO_VALUE_CONV_ENUM,
+ 'value_conv_human': MASK_INFO_VALUE_CONV_HUMAN,
}

@staticmethod
@@ -138,33 +222,34 @@ class DisplayData(object):
return max_width

@staticmethod
- def _data_dict_gen(obj, flag_human, flag_enum, extra_properties=None,
- flag_dsp_all_data=False):
+ def _data_dict_gen(obj, flag_human, flag_enum, display_way,
+ extra_properties=None, flag_dsp_all_data=False):
data_dict = OrderedDict()
value_convert = DisplayData.VALUE_CONVERT[type(obj)]
mandatory_headers = value_convert['mandatory_headers']
- display_headers = value_convert['display_headers']
optional_headers = value_convert['optional_headers']
value_conv_enum = value_convert['value_conv_enum']
value_conv_human = value_convert['value_conv_human']

- for key in display_headers.keys():
- key_str = display_headers[key]
+ if flag_dsp_all_data:
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
+ display_keys = []
+
+ if display_way == DisplayData.DISPLAY_WAY_COLUMN:
+ display_keys = value_convert['column_keys']
+ elif display_way == DisplayData.DISPLAY_WAY_SCRIPT:
+ display_keys = mandatory_headers.keys()
+
+ for key in display_keys:
+ key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human, flag_human,
flag_enum)
data_dict[key_str] = value

- if flag_dsp_all_data:
- for key in mandatory_headers.keys():
- if key in display_headers.keys():
- continue
- key_str = mandatory_headers[key]
- value = DisplayData._get_man_pro_value(
- obj, key, value_conv_enum, value_conv_human, flag_human,
- flag_enum)
- data_dict[key_str] = value

+ if flag_dsp_all_data:
for key in optional_headers.keys():
key_str = optional_headers[key]
value = DisplayData._get_opt_pro_value(
@@ -172,11 +257,12 @@ class DisplayData(object):
flag_enum)
data_dict[key_str] = value

- elif extra_properties:
+ if extra_properties:
for key in extra_properties:
+ if key in data_dict.keys():
+ # already contained
+ continue
if key in mandatory_headers.keys():
- if key in display_headers.keys():
- continue
key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human,
@@ -188,7 +274,6 @@ class DisplayData(object):
obj, key, value_conv_enum, value_conv_human,
flag_human, flag_enum)
data_dict[key_str] = value
-
return data_dict

@staticmethod
@@ -211,8 +296,8 @@ class DisplayData(object):
if type(objs[0]) in DisplayData.VALUE_CONVERT.keys():
for obj in objs:
data_dict = DisplayData._data_dict_gen(
- obj, flag_human, flag_enum, extra_properties,
- flag_dsp_all_data)
+ obj, flag_human, flag_enum, display_way,
+ extra_properties, flag_dsp_all_data)
data_dict_list.extend([data_dict])
else:
return None
@@ -304,7 +389,7 @@ class DisplayData(object):
for raw in range(0, row_width):
new = []
for column in range(0, item_count):
- new.append([''])
+ new.append('')
two_d_list.append(new)

# header
--
1.8.3.1
Gris Ge
2014-04-23 12:41:00 UTC
Permalink
* Just sync with API changes on volume masking.
* Use lsm_plugin_helper.py(class LsmPluginHelper) for searching/filter
support on lsm.Client.access_groups() and lsm.Client.mask_infos()
# This could a example when other plugin want to use this routine but
# resource consuming searching methods.
* The simulator plugin does not enable all capabilities now. It will unset
these two as using LsmPluginHelper:
MASK_INFOS_QUICK_SEARCH
ACCESS_GROUPS_QUICK_SEARCH

Signed-off-by: Gris Ge <***@redhat.com>
---
libstoragemgmt.spec.in | 1 +
lsm/Makefile.am | 3 +-
lsm/lsm/lsm_plugin_helper.py | 72 +++++++++
lsm/lsm/simarray.py | 367 +++++++++++++------------------------------
lsm/lsm/simulator.py | 89 +++++------
5 files changed, 223 insertions(+), 309 deletions(-)
create mode 100644 lsm/lsm/lsm_plugin_helper.py

diff --git a/libstoragemgmt.spec.in b/libstoragemgmt.spec.in
index 57ee743..6fdedef 100644
--- a/libstoragemgmt.spec.in
+++ b/libstoragemgmt.spec.in
@@ -332,6 +332,7 @@ fi
%{python_sitelib}/lsm/simarray.*
%{python_sitelib}/lsm/_transport.*
%{python_sitelib}/lsm/version.*
+%{python_sitelib}/lsm/lsm_plugin_helper.*
%{_bindir}/sim_lsmplugin

%files smis-plugin
diff --git a/lsm/Makefile.am b/lsm/Makefile.am
index 029e87b..4f47f96 100644
--- a/lsm/Makefile.am
+++ b/lsm/Makefile.am
@@ -30,7 +30,8 @@ lsm_PYTHON = lsm/__init__.py \
lsm/smisproxy.py \
lsm/_transport.py \
lsm/version.py \
- lsm/targetd.py
+ lsm/targetd.py \
+ lsm/lsm_plugin_helper.py


dist_bin_SCRIPTS += $(IBM_PLUG)
diff --git a/lsm/lsm/lsm_plugin_helper.py b/lsm/lsm/lsm_plugin_helper.py
new file mode 100644
index 0000000..2d96d20
--- /dev/null
+++ b/lsm/lsm/lsm_plugin_helper.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from lsm import LsmError, ErrorNumber
+
+class LsmPluginHelper():
+ """
+ PluginHelper just containing the routine methods could be used by LSM
+ plugins. Some of these methods are having a very bad performance since
+ should be the last choice.
+ """
+
+ @staticmethod
+ def access_group_search(plugin_self, search_key, search_value, flags=0):
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
+ Conflict with lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ """
+ all_access_groups = plugin_self.access_groups(flags=flags)
+ if search_key in ['id', 'name', 'system_id']:
+ return list(a for a in all_access_groups
+ if getattr(a, search_key) == search_value)
+ if search_key == 'volume_id':
+ rc = []
+ ag_ids = []
+ all_mask_infos = plugin_self.mask_infos()
+ for mask_info in all_mask_infos:
+ if mask_info.volume_id == search_value:
+ ag_ids.extend([mask_info.access_group_id])
+ if ag_ids:
+ for access_group in all_access_groups:
+ if access_group.id in ag_ids:
+ rc.extend([access_group])
+ return rc
+ else:
+ return []
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
+
+ @staticmethod
+ def mask_info_search(plugin_self, search_key, search_value, flags=0):
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
+ Conflict with lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ """
+ all_mask_infos = plugin_self.mask_infos(flags=flags)
+ if search_key in ['id', 'volume_id', 'access_group_id', 'system_id']:
+ return list(m for m in all_mask_infos
+ if getattr(m, search_key) == search_value)
+
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
diff --git a/lsm/lsm/simarray.py b/lsm/lsm/simarray.py
index 29cab42..c868846 100644
--- a/lsm/lsm/simarray.py
+++ b/lsm/lsm/simarray.py
@@ -27,8 +27,8 @@ import time

from _common import (size_human_2_size_bytes, size_bytes_2_size_human)
from lsm import (System, Volume, Disk, Pool, FileSystem, AccessGroup,
- Initiator, FsSnapshot, NfsExport, OptionalData, md5,
- LsmError, ErrorNumber, JobStatus)
+ FsSnapshot, NfsExport, OptionalData, md5, LsmError,
+ ErrorNumber, JobStatus, MaskInfo)

# Used for format width for disks
D_FMT = 5
@@ -350,71 +350,50 @@ class SimArray(object):

@staticmethod
def _sim_ag_2_lsm(sim_ag):
- return AccessGroup(sim_ag['ag_id'], sim_ag['name'],
- sim_ag['init_ids'], sim_ag['sys_id'])
+ return AccessGroup(sim_ag['id'], sim_ag['name'],
+ sim_ag['init_ids'], sim_ag['init_type'],
+ sim_ag['system_id'], OptionalData())

- def ags(self):
- sim_ags = self.data.ags()
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ sim_ags = self.data.access_groups(flags=flags)
+ return SimArray._sort_by_id(
+ [SimArray._sim_ag_2_lsm(a) for a in sim_ags])

- def access_group_create(self, name, init_id, init_type, sys_id, flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = self.data.access_group_create(
- name, init_id, init_type, sys_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimArray._sim_ag_2_lsm(sim_ag)

- def access_group_del(self, ag_id, flags=0):
- return self.data.access_group_del(ag_id, flags)
-
- def access_group_add_initiator(self, ag_id, init_id, init_type, flags=0):
- return self.data.access_group_add_initiator(
- ag_id, init_id, init_type, flags)
-
- def access_group_del_initiator(self, ag_id, init_id, flags=0):
- return self.data.access_group_del_initiator(ag_id, init_id, flags)
-
- def access_group_grant(self, ag_id, vol_id, access, flags=0):
- return self.data.access_group_grant(ag_id, vol_id, access, flags)
-
- def access_group_revoke(self, ag_id, vol_id, flags=0):
- return self.data.access_group_revoke(ag_id, vol_id, flags)
-
- def volumes_accessible_by_access_group(self, ag_id, flags=0):
- sim_vols = self.data.volumes_accessible_by_access_group(ag_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
-
- def access_groups_granted_to_volume(self, vol_id, flags=0):
- sim_ags = self.data.access_groups_granted_to_volume(vol_id, flags)
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
-
- @staticmethod
- def _sim_init_2_lsm(sim_init):
- return Initiator(sim_init['init_id'], sim_init['init_type'],
- sim_init['name'])
-
- def inits(self, flags=0):
- sim_inits = self.data.inits()
- return [SimArray._sim_init_2_lsm(a) for a in sim_inits]
-
- def initiator_grant(self, init_id, init_type, vol_id, access, flags=0):
- return self.data.initiator_grant(
- init_id, init_type, vol_id, access, flags)
-
- def initiator_revoke(self, init_id, vol_id, flags=0):
- return self.data.initiator_revoke(init_id, vol_id, flags)
-
- def volumes_accessible_by_initiator(self, init_id, flags=0):
- sim_vols = self.data.volumes_accessible_by_initiator(init_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
+ def access_group_delete(self, ag_id, flags=0):
+ return self.data.access_group_delete(ag_id, flags)

- def initiators_granted_to_volume(self, vol_id, flags=0):
- sim_inits = self.data.initiators_granted_to_volume(vol_id, flags)
- return [SimArray._sim_init_2_lsm(i) for i in sim_inits]
+ def access_group_edit(self, ag_id, new_init_ids, flags=0):
+ sim_ag = self.data.access_group_edit(ag_id, new_init_ids, flags)
+ return SimArray._sim_ag_2_lsm(sim_ag)

def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
flags=0):
return self.data.iscsi_chap_auth(init_id, in_user, in_pass, out_user,
out_pass, flags)

+ @staticmethod
+ def _sim_mask_2_lsm(sim_mask):
+ return MaskInfo(sim_mask['volume_id'], sim_mask['access_group_id'],
+ sim_mask['access_type'], sim_mask['system_id'],
+ OptionalData())
+
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ # search was handled by PlugHelper in simulator.py
+ sim_masks = self.data.mask_infos(flags=flags)
+ return [SimArray._sim_mask_2_lsm(m) for m in sim_masks]
+
+ def volume_mask(self, vol_id, ag_id, access_type, flags=0):
+ sim_mask = self.data.volume_mask(vol_id, ag_id, access_type, flags)
+ return SimArray._sim_mask_2_lsm(sim_mask)
+
+ def volume_unmask(self, vol_id=None, ag_id=None, flags=0):
+ return self.data.volume_unmask(vol_id, ag_id, flags)

class SimData(object):
"""
@@ -444,22 +423,6 @@ class SimData(object):
},
],
},
- 'mask': {
- ag_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- },
- 'mask_init': {
- init_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- }
- }
-
- self.init_dict = {
- Initiator.id = sim_init,
- }
- sim_init = {
- 'init_id': Initiator.id,
- 'init_type': Initiator.TYPE_XXXX,
- 'name': SimData.SIM_DATA_INIT_NAME,
- 'sys_id': SimData.SIM_DATA_SYS_ID,
}

self.ag_dict ={
@@ -467,9 +430,10 @@ class SimData(object):
}
sim_ag = {
'init_ids': [init_id,],
- 'sys_id': SimData.SIM_DATA_SYS_ID,
+ 'init_type': AccessGroup.INIT_TYPE_XXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
'name': name,
- 'ag_id': self._next_ag_id()
+ 'id': self._next_ag_id()
}

self.fs_dict = {
@@ -524,7 +488,7 @@ class SimData(object):
'name': pool_name,
'pool_id': Pool.id,
'raid_type': Pool.RAID_TYPE_XXXX,
- 'member_ids': [ disk_id or pool_id or volume_id ],
+ 'member_ids': [ disk_id or pool_id or vol_id ],
'member_type': Pool.MEMBER_TYPE_XXXX,
'member_size': size_bytes # space allocated from each member pool.
# only for MEMBER_TYPE_POOL
@@ -532,9 +496,21 @@ class SimData(object):
'sys_id': SimData.SIM_DATA_SYS_ID,
'element_type': SimData.SIM_DATA_POOL_ELEMENT_TYPE,
}
+
+ self.mask_dict= {
+ MaskInfo.id: sim_mask,
+ }
+
+ sim_mask = {
+ 'id': self._next_mask_id() # Internal use only.
+ 'access_group_id': AccessGroup.id,
+ 'volume_id': Volume.id,
+ 'access_type': MaskInfo.ACCESS_TYPE_XXXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
+ }
"""
SIM_DATA_BLK_SIZE = 512
- SIM_DATA_VERSION = "2.2"
+ SIM_DATA_VERSION = "2.3"
SIM_DATA_SYS_ID = 'sim-01'
SIM_DATA_INIT_NAME = 'NULL'
SIM_DATA_TMO = 30000 # ms
@@ -555,6 +531,7 @@ class SimData(object):
SIM_DATA_CUR_AG_ID = 0
SIM_DATA_CUR_SNAP_ID = 0
SIM_DATA_CUR_EXP_ID = 0
+ SIM_DATA_CUR_MASK_ID = 0

def _next_pool_id(self):
self.SIM_DATA_CUR_POOL_ID += 1
@@ -580,6 +557,10 @@ class SimData(object):
self.SIM_DATA_CUR_EXP_ID += 1
return "EXP_ID_%08d" % self.SIM_DATA_CUR_EXP_ID

+ def _next_mask_id(self):
+ self.SIM_DATA_CUR_MASK_ID += 1
+ return "MASK_ID_%08d" % self.SIM_DATA_CUR_MASK_ID
+
@staticmethod
def state_signature():
return 'LSM_SIMULATOR_DATA_%s' % md5(SimData.SIM_DATA_VERSION)
@@ -674,8 +655,7 @@ class SimData(object):

self.ag_dict = {
}
- self.init_dict = {
- }
+ self.mask_dict = dict()
# Create some volumes, fs and etc
self.volume_create(
'POO1', 'Volume 000', size_human_2_size_bytes('200GiB'),
@@ -864,7 +844,8 @@ class SimData(object):
def disks(self):
return self.disk_dict.values()

- def access_group_list(self):
+ def access_groups(self, flags=0):
+ # search was handled by simulator.py
return self.ag_dict.values()

def volume_create(self, pool_id, vol_name, size_bytes, thinp, flags=0):
@@ -1029,207 +1010,81 @@ class SimData(object):
del sim_vol['replicate'][vol_id]
return None

- def ags(self, flags=0):
- return self.ag_dict.values()
-
- def access_group_create(self, name, init_id, init_type, sys_id, flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = dict()
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
-
- sim_ag['init_ids'] = [init_id]
- sim_ag['sys_id'] = SimData.SIM_DATA_SYS_ID
- sim_ag['name'] = name
- sim_ag['ag_id'] = self._next_ag_id()
- self.ag_dict[sim_ag['ag_id']] = sim_ag
+ sim_ag['init_ids'] = init_ids
+ sim_ag['init_type'] = init_type
+ sim_ag['system_id'] = SimData.SIM_DATA_SYS_ID
+ sim_ag['name'] = access_group_name
+ sim_ag['id'] = self._next_ag_id()
+ self.ag_dict[sim_ag['id']] = sim_ag
return sim_ag

- def access_group_del(self, ag_id, flags=0):
+ def access_group_delete(self, ag_id, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
del(self.ag_dict[ag_id])
return None

- def access_group_add_initiator(self, ag_id, init_id, init_type, flags=0):
+ def access_group_edit(self, ag_id, new_init_ids, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- if init_id in self.ag_dict[ag_id]['init_ids']:
- return self.ag_dict[ag_id]
-
- self.ag_dict[ag_id]['init_ids'].extend([init_id])
+ sim_ag = self.ag_dict[ag_id]
+ del(self.ag_dict[ag_id])
+ sim_ag['init_ids'] = new_init_ids
+ self.ag_dict[ag_id] = sim_ag
+ return sim_ag

+ def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
+ flags=0):
+ # No iscsi chap query API yet
return None

- def access_group_del_initiator(self, ag_id, init_id, flags=0):
- if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- if init_id not in self.init_dict.keys():
- return None
-
- if init_id in self.ag_dict[ag_id]['init_ids']:
- new_init_ids = []
- for cur_init_id in self.ag_dict[ag_id]['init_ids']:
- if cur_init_id != init_id:
- new_init_ids.extend([cur_init_id])
- del(self.ag_dict[ag_id]['init_ids'])
- self.ag_dict[ag_id]['init_ids'] = new_init_ids
- return None
+ def mask_infos(self, flags=0):
+ # search was handled by PlugHelper in simulator.py
+ return self.mask_dict.values()

- def access_group_grant(self, ag_id, vol_id, access, flags=0):
+ def volume_mask(self, vol_id, ag_id, access_type, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
+ "Access group %s not found" % ag_id)
if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
+ raise LsmError(ErrorNumber.NOT_FOUND_VOLUME,
"No such Volume: %s" % vol_id)
- if 'mask' not in self.vol_dict[vol_id].keys():
- self.vol_dict[vol_id]['mask'] = dict()
-
- self.vol_dict[vol_id]['mask'][ag_id] = access
- return None
-
- def access_group_revoke(self, ag_id, vol_id, flags=0):
- if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
+ mask_id = None
+ # Check whether we are updating existing mask.
+ for sim_mask in self.mask_dict.values():
+ if sim_mask['volume_id'] == vol_id and \
+ sim_mask['access_group_id'] == ag_id:
+ mask_id = sim_mask['id']
+ del(self.mask_dict[mask_id])
+ break
+ if mask_id is None:
+ mask_id = self._next_mask_id()
+ sim_mask = dict()
+ sim_mask['id'] = mask_id
+ sim_mask['volume_id'] = vol_id
+ sim_mask['access_group_id'] = ag_id
+ sim_mask['access_type'] = access_type
+ sim_mask['system_id'] = SimData.SIM_DATA_SYS_ID
+ self.mask_dict[mask_id] = sim_mask
+ return sim_mask
+
+ def volume_unmask(self, vol_id=None, ag_id=None, flags=0):
if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if 'mask' not in self.vol_dict[vol_id].keys():
- return None
-
- if ag_id not in self.vol_dict[vol_id]['mask'].keys():
- return None
-
- del(self.vol_dict[vol_id]['mask'][ag_id])
- return None
-
- def volumes_accessible_by_access_group(self, ag_id, flags=0):
+ raise LsmError(ErrorNumber.NOT_FOUND_VOLUME,
+ "Invalid volume ID: %s" % vol_id)
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- rc = []
- for sim_vol in self.vol_dict.values():
- if 'mask' not in sim_vol:
- continue
- if ag_id in sim_vol['mask'].keys():
- rc.extend([sim_vol])
- return rc
-
- def access_groups_granted_to_volume(self, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- sim_ags = []
- if 'mask' in self.vol_dict[vol_id].keys():
- ag_ids = self.vol_dict[vol_id]['mask'].keys()
- for ag_id in ag_ids:
- sim_ags.extend([self.ag_dict[ag_id]])
- return sim_ags
-
- def inits(self, flags=0):
- return self.init_dict.values()
-
- def initiator_grant(self, init_id, init_type, vol_id, access, flags):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- if 'mask_init' not in self.vol_dict[vol_id].keys():
- self.vol_dict[vol_id]['mask_init'] = dict()
-
- self.vol_dict[vol_id]['mask_init'][init_id] = access
- return None
-
- def initiator_revoke(self, init_id, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
-
- if 'mask_init' in self.vol_dict[vol_id].keys():
- if init_id in self.vol_dict[vol_id]['mask_init'].keys():
- del self.vol_dict[vol_id]['mask_init'][init_id]
-
- return None
-
- def _ag_ids_of_init(self, init_id):
- """
- Find out the access groups defined initiator belong to.
- Will return a list of access group id or []
- """
- rc = []
- for sim_ag in self.ag_dict.values():
- if init_id in sim_ag['init_ids']:
- rc.extend([sim_ag['ag_id']])
- return rc
-
- def volumes_accessible_by_initiator(self, init_id, flags=0):
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- rc_dedup_dict = dict()
- ag_ids = self._ag_ids_of_init(init_id)
- for ag_id in ag_ids:
- sim_vols = self.volumes_accessible_by_access_group(ag_id)
- for sim_vol in sim_vols:
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
-
- for sim_vol in self.vol_dict.values():
- if 'mask_init' in sim_vol:
- if init_id in sim_vol['mask_init'].keys():
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
- return rc_dedup_dict.values()
-
- def initiators_granted_to_volume(self, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- rc_dedup_dict = dict()
- sim_ags = self.access_groups_granted_to_volume(vol_id, flags)
- for sim_ag in sim_ags:
- for init_id in sim_ag['init_ids']:
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- if 'mask_init' in self.vol_dict[vol_id].keys():
- for init_id in self.vol_dict[vol_id]['mask_init']:
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- return rc_dedup_dict.values()
-
- def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
- flags=0):
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- if self.init_dict[init_id]['init_type'] != Initiator.TYPE_ISCSI:
- raise LsmError(ErrorNumber.UNSUPPORTED_INITIATOR_TYPE,
- "Initiator %s is not an iSCSI IQN" % init_id)
- # No iscsi chap query API yet
- return None
+ "Invalid access group ID: %s" % ag_id)
+ for sim_mask in self.mask_dict.values():
+ if sim_mask['volume_id'] == vol_id and \
+ sim_mask['access_group_id'] == ag_id:
+ del(self.mask_dict[sim_mask['id']])
+ return None

def fs(self):
return self.fs_dict.values()
diff --git a/lsm/lsm/simulator.py b/lsm/lsm/simulator.py
index 1ea1a45..4b52b8a 100644
--- a/lsm/lsm/simulator.py
+++ b/lsm/lsm/simulator.py
@@ -20,6 +20,8 @@ from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,

from simarray import SimArray

+from lsm_plugin_helper import LsmPluginHelper
+

class SimPlugin(INfs, IStorageAreaNetwork):
"""
@@ -76,6 +78,10 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def capabilities(self, system, flags=0):
rc = Capabilities()
rc.enable_all()
+ rc.set(Capabilities.MASK_INFOS_QUICK_SEARCH,
+ Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.ACCESS_GROUPS_QUICK_SEARCH,
+ Capabilities.UNSUPPORTED)
return rc

def plugin_info(self, flags=0):
@@ -162,67 +168,46 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_offline(self, volume, flags=0):
return self.sim_array.volume_online(volume.id, flags)

- def access_group_list(self, flags=0):
- sim_ags = self.sim_array.ags()
- return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key is None:
+ sim_ags = self.sim_array.access_groups(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ else:
+ return LsmPluginHelper.access_group_search(
+ self, search_key, search_value, flags)

- def access_group_create(self, name, initiator_id, id_type, system_id,
- flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = self.sim_array.access_group_create(
- name, initiator_id, id_type, system_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)

- def access_group_del(self, group, flags=0):
- return self.sim_array.access_group_del(group.id, flags)
+ def access_group_delete(self, access_group, flags=0):
+ return self.sim_array.access_group_delete(access_group.id, flags)

- def access_group_add_initiator(self, group, initiator_id, id_type,
- flags=0):
- sim_ag = self.sim_array.access_group_add_initiator(
- group.id, initiator_id, id_type, flags)
+ def access_group_edit(self, access_group, new_init_ids, flags=0):
+ sim_ag = self.sim_array.access_group_edit(
+ access_group.id, new_init_ids, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)

- def access_group_del_initiator(self, group, initiator_id, flags=0):
- return self.sim_array.access_group_del_initiator(
- group.id, initiator_id, flags)
-
- def access_group_grant(self, group, volume, access, flags=0):
- return self.sim_array.access_group_grant(
- group.id, volume.id, access, flags)
-
- def access_group_revoke(self, group, volume, flags=0):
- return self.sim_array.access_group_revoke(
- group.id, volume.id, flags)
-
- def volumes_accessible_by_access_group(self, group, flags=0):
- sim_vols = self.sim_array.volumes_accessible_by_access_group(
- group.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- def access_groups_granted_to_volume(self, volume, flags=0):
- sim_vols = self.sim_array.access_groups_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- def initiators(self, flags=0):
- return self.sim_array.inits(flags)
-
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- flags=0):
- return self.sim_array.initiator_grant(
- initiator_id, initiator_type, volume.id, access, flags)
-
- def initiator_revoke(self, initiator, volume, flags=0):
- return self.sim_array.initiator_revoke(initiator.id, volume.id, flags)
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ if search_key is None:
+ sim_masks = self.sim_array.mask_infos(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(m) for m in sim_masks]
+ else:
+ return LsmPluginHelper.mask_info_search(
+ self, search_key, search_value, flags)

- def volumes_accessible_by_initiator(self, initiator, flags=0):
- sim_vols = self.sim_array.volumes_accessible_by_initiator(
- initiator.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
+ def volume_mask(self, volume, access_group, access_type, flags=0):
+ sim_mask = self.sim_array.volume_mask(
+ volume.id, access_group.id, access_type, flags)
+ return SimPlugin._sim_data_2_lsm(sim_mask)

- def initiators_granted_to_volume(self, volume, flags=0):
- sim_inits = self.sim_array.initiators_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(i) for i in sim_inits]
+ def volume_unmask(self, volume=None, access_group=None, flags=0):
+ return self.sim_array.volume_unmask(
+ vol_id=volume.id, ag_id=access_group.id, flags=flags)

def iscsi_chap_auth(self, initiator, in_user, in_password,
out_user, out_password, flags=0):
--
1.8.3.1
Tony Asleson
2014-04-23 22:50:35 UTC
Permalink
I pushed:

'[PATCH] LsmError: provide error number name in error message'

and with that I cannot apply this patch set. Please re-base this set
and resubmit.

Thanks,
Tony
Post by Gris Ge
Sorry for big patch. It's pretty hard to split them into pieces.
* Update Python API codes to compliant with API documents for
* Update lsm.AccessGroup
* Remove lsm.Initiator
* Add lsm.MaskInfo
* simulator plugin update for this change.
* Introduce LsmPluginHelper for searching/filtering.
* lsmcli update for support this change.
I just retype this patch set in three days after a data lose.
It surely has pretty errors. Please kindly let me know if you found anything
wrong or suspicious.
1. C/C++ codes.
2. Test codes. # I can do the python test codes part.
3. iSCSI CHAP.
# I will add a section in API document and provide patch before end of
# this week.
4. Query on lsm.Client.volumes()
# This will come alone with code-doc sync on lsm.Volume patch set.
* SMI-S -- smis.py # I will take this
* Simulator C -- simc_lsmplugin.c
* ONTAP -- ontap.py
* targetd -- targetd.py # I will take this
* IBM V7K -- ibmv7k.py
* NStor -- nstor.py
6. Add more search/filter support to other query methods.
Goal is allowing the same set of search_keys on every query methods.
1. Use shared ErrorNumber.
2. Removed lsm.MaskInfo.id property.
3. Change lsm.Client.volume_unmask() to take volume and access group only.
4. Renamed plugin_helper.py to lsm_plugin_helper.py.
# As it will not added into "lsm" namespace, we'd better give it a prefix.
5. Update rpm spec and automake files for new file lsm_plugin_helper.py.
Python API: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator
lsmcli: update volume masking related commands
simulator.py: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator
doc/man/lsmcli.1.in | 126 ++++------
libstoragemgmt.spec.in | 1 +
lsm/Makefile.am | 3 +-
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 555 ++++++++++++++++++++++++++++-------------
lsm/lsm/_cmdline.py | 398 +++++++++++------------------
lsm/lsm/_common.py | 30 ++-
lsm/lsm/_data.py | 166 ++++++------
lsm/lsm/lsm_plugin_helper.py | 72 ++++++
lsm/lsm/lsmcli_data_display.py | 149 ++++++++---
lsm/lsm/simarray.py | 367 +++++++++------------------
lsm/lsm/simulator.py | 89 +++----
12 files changed, 1045 insertions(+), 917 deletions(-)
create mode 100644 lsm/lsm/lsm_plugin_helper.py
Gris Ge
2014-04-28 09:17:38 UTC
Permalink
Sorry for big patch. It's pretty hard to split them into pieces.
* Update Python API codes to compliant with API documents for
volume masking part:
* Update lsm.AccessGroup
* Remove lsm.Initiator
* Add lsm.MaskInfo
* simulator plugin update for this change.
* Introduce LsmPluginHelper for searching/filtering.
* lsmcli update for support this change.

I just retype this patch set in three days after a data lose.
It surely has pretty errors. Please kindly let me know if you found anything
wrong or suspicious.

TODO:
1. C/C++ codes.
2. Test codes. # I can do the python test codes part.
3. iSCSI CHAP.
# I will add a section in API document and provide patch before end of
# this week.
4. Query on lsm.Client.volumes()
# This will come alone with code-doc sync on lsm.Volume patch set.
5. Update these plugins for this change:
* SMI-S -- smis.py # I will take this
* Simulator C -- simc_lsmplugin.c
* ONTAP -- ontap.py
* targetd -- targetd.py # I will take this
* IBM V7K -- ibmv7k.py
* NStor -- nstor.py
6. Add more search/filter support to other query methods.
Goal is allowing the same set of search_keys on every query methods.

Changes since V1:
1. Use shared ErrorNumber.
2. Removed lsm.MaskInfo.id property.
3. Change lsm.Client.volume_unmask() to take volume and access group only.
4. Renamed plugin_helper.py to lsm_plugin_helper.py.
# As it will not added into "lsm" namespace, we'd better give it a prefix.
5. Update rpm spec and automake files for new file lsm_plugin_helper.py.

Gris Ge (3):
Python API: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator
lsmcli: update volume masking related commands
simulator.py: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator

doc/man/lsmcli.1.in | 126 ++++------
libstoragemgmt.spec.in | 1 +
lsm/Makefile.am | 3 +-
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 555 ++++++++++++++++++++++++++++-------------
lsm/lsm/_cmdline.py | 398 +++++++++++------------------
lsm/lsm/_common.py | 31 ++-
lsm/lsm/_data.py | 166 ++++++------
lsm/lsm/lsm_plugin_helper.py | 72 ++++++
lsm/lsm/lsmcli_data_display.py | 149 ++++++++---
lsm/lsm/simarray.py | 367 +++++++++------------------
lsm/lsm/simulator.py | 89 +++----
12 files changed, 1046 insertions(+), 917 deletions(-)
create mode 100644 lsm/lsm/lsm_plugin_helper.py
--
1.8.3.1
Gris Ge
2014-04-28 09:17:39 UTC
Permalink
* Removed these methods:
access_group_add_initiator() # replaced by access_group_edit()
access_group_del_initiator() # replaced by access_group_edit()
access_group_grant() # replaced by volume_mask()
access_group_revoke() # replaced by volume_unmask()
access_groups_granted_to_volume # replaced by access_groups()
initiators_granted_to_volume() # No need.
* Renamed these methods:
access_group_del() -> access_group_delete()
access_group_list() -> access_groups()
* Added these methods:
volume_mask()
volume_unmask()
mask_infos()
* Removed this class:
lsm.Initiator
* Added this class:
lsm.MaskInfo
* Updated this class:
lsm.AccessGroup
* Add property 'optional_data', 'init_type'.
* Renam property 'initiators' to 'init_ids'.
* No doxygen document included as we have better one.
* Method document copied from API document.
* Purged private constants and methods of lsm.AccessGroup about value
converting. They have been moved to lsmcli_data_display.py. And API user
should use their own value converting codes.

Signed-off-by: Gris Ge <***@redhat.com>
---
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 555 ++++++++++++++++++++++++++++++++++++----------------
lsm/lsm/_common.py | 31 ++-
lsm/lsm/_data.py | 166 ++++++++--------
4 files changed, 504 insertions(+), 254 deletions(-)

diff --git a/lsm/lsm/__init__.py b/lsm/lsm/__init__.py
index 0635b7e..836ab75 100644
--- a/lsm/lsm/__init__.py
+++ b/lsm/lsm/__init__.py
@@ -4,9 +4,9 @@ from version import VERSION

from _common import Error, Info, LsmError, ErrorLevel, ErrorNumber, \
JobStatus, uri_parse, md5, Proxy, size_bytes_2_size_human
-from _data import Initiator, Disk, \
- Volume, Pool, System, FileSystem, FsSnapshot, NfsExport, BlockRange, \
- AccessGroup, OptionalData, Capabilities, txt_a
+from _data import Disk, Volume, Pool, System, FileSystem, FsSnapshot, \
+ NfsExport, BlockRange, AccessGroup, OptionalData, Capabilities, MaskInfo, \
+ txt_a
from _iplugin import IPlugin, IStorageAreaNetwork, INetworkAttachedStorage, \
INfs

diff --git a/lsm/lsm/_client.py b/lsm/lsm/_client.py
index a8a30c2..aa2aa6d 100644
--- a/lsm/lsm/_client.py
+++ b/lsm/lsm/_client.py
@@ -19,9 +19,9 @@ import time
import os
import unittest
from lsm import (Volume, NfsExport, Capabilities, Pool, System,
- Initiator, Disk, AccessGroup, FileSystem, FsSnapshot,
+ Disk, AccessGroup, FileSystem, FsSnapshot,
uri_parse, LsmError, JobStatus, ErrorNumber,
- INetworkAttachedStorage)
+ INetworkAttachedStorage, MaskInfo)

from _common import return_requires as _return_requires
from _common import UDS_PATH as _UDS_PATH
@@ -427,17 +427,6 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('systems', _del_self(locals()))

- ## Returns an array of initiator objects
- # @param self The this pointer
- # @param flags Reserved for future use, must be zero.
- # @returns An array of initiator objects.
- @_return_requires([Initiator])
- def initiators(self, flags=0):
- """
- Return an array of initiator objects
- """
- return self._tp.rpc('initiators', _del_self(locals()))
-
## Register a user/password for the specified initiator for CHAP
# authentication.
# Note: If you pass an empty user and password the expected behavior is to
@@ -459,57 +448,6 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('iscsi_chap_auth', _del_self(locals()))

- ## Grants access so an initiator can read/write the specified volume.
- # @param self The this pointer
- # @param initiator_id The iqn, WWID etc.
- # @param initiator_type Enumerated initiator type
- # @param volume Volume to grant access to
- # @param access Enumerated access type
- # @param flags Reserved for future use, must be zero
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- flags=0):
- """
- Allows an initiator to access a volume.
- """
- return self._tp.rpc('initiator_grant', _del_self(locals()))
-
- ## Revokes access for a volume for the specified initiator.
- # @param self The this pointer
- # @param initiator The iqn, WWID etc.
- # @param volume The volume to revoke access for
- # @param flags Reserved for future use, must be zero
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def initiator_revoke(self, initiator, volume, flags=0):
- """
- Revokes access to a volume for the specified initiator
- """
- return self._tp.rpc('initiator_revoke', _del_self(locals()))
-
- ## Returns a list of volumes that are accessible from the specified
- # initiator.
- # @param self The this pointer
- # @param initiator The initiator object
- # @param flags Reserved for future use, must be zero
- @_return_requires([Volume])
- def volumes_accessible_by_initiator(self, initiator, flags=0):
- """
- Returns a list of volumes that the initiator has access to.
- """
- return self._tp.rpc('volumes_accessible_by_initiator',
- _del_self(locals()))
-
- ## Returns a list of initiators that have access to the specified volume.
- # @param self The this pointer
- # @param volume The volume in question
- # @param flags Reserved for future use, must be zero
- # @returns List of initiators
- @_return_requires([Initiator])
- def initiators_granted_to_volume(self, volume, flags=0):
- return self._tp.rpc('initiators_granted_to_volume', _del_self(locals()))
-
## Returns an array of volume objects
# @param self The this pointer
# @param flags Reserved for future use, must be zero.
@@ -658,6 +596,159 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('volume_offline', _del_self(locals()))

+ @_return_requires([MaskInfo])
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ """
+ lsm.Client.mask_infos(self, search_key=None, search_value=None,
+ flags=0)
+
+ Usage:
+ Change the initiators of certain access group.
+ If 'search_key' and 'search_value' is defined, only system matches
+ will be contained in return list.
+ Valid search keys are stored in lsm.MaskInfo.SUPPORTED_SEARCH_KEYS:
+ volume_id # Search lsm.AccessGroup.volume_id property
+ access_group_id # Search lsm.AccessGroup.access_group_id
+ # property
+ system_id # Search mask info on given system.
+ This method does not check whether provided resources ID is valid
+ or not, if resource ID is not exist at all, the search result will
+ be a empty list.
+ When no matches found, return a empty list
+ Parameters:
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # Valid value:
+ # lsm.MaskInfo.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.MaskInfo object will contain
+ # # optional properties. But currently,
+ # # there is no optional property for
+ # # lsm.MaskInfo yet, this will make no
+ # # difference.
+ Returns:
+ [lsm.MaskInfo,] # A list of lsm.MaskInfo objects.
+ or
+ [] # Nothing found.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
+ # Got invalid search key.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.MASK_INFOS
+ # Indicate current system support query mask info via
+ # lsm.Client.mask_infos() method.
+ lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all mask info
+ # without 'search_key' parameter, then filter in user's code
+ # space.
+ """
+ if search_key and search_key not in MaskInfo.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search key %s" % search_key)
+
+ return self._tp.rpc('mask_infos', _del_self(locals()))
+
+ @_return_requires(MaskInfo)
+ def volume_mask(self, volume, access_group, access_type, flags=0):
+ """
+ lsm.Client.volume_mask(self, volume, access_group, access_type,
+ flags=0)
+
+ Usage:
+ Mask certain volume to defined access group.
+ To change access_type of existing mask info, just invoke this
+ method with new access_type.
+ Storage will never mask single volume to single access group with
+ two or more LUN IDs, duplicated call of this method will not
+ resulting mulitpal masking for single voluem to single access
+ group.
+ For those storage array which does not support changing existing
+ masking relationship, they will deleting existing and creating new
+ one.
+ Parameters:
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ access_type # Integer. Check lsm.MaskInfo.access_type property.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ lsm.MaskInfo # Object of lsm.MaskInfo.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support volume_mask()
+ lsm.ErrorNumber.NOT_FOUND_VOLUME
+ # Defined lsm.Volume does not exists.
+ lsm.ErrorNumber.NOT_FOUND_ACCESS_GROUP
+ # Defined lsm.AccessGroup does not exists.
+ lsm.ErrorNumber.UNSUPPORTED_ACCESS_TYPE
+ # Defined access type is not support by storage system
+ lsm.ErrorNumber.UNALLOWED_ACCESS_TYPE
+ # Defined volume does not allow defined access type.
+ # Currently, we have no capability to allow pre-invoke
+ # check on this.
+ # Example: DR(Disaster Recover) site volumes does not
+ # allow RW access, but other normal volume can still be
+ # masked with RW access.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.VOLUME_MASK
+ # Indicate current system support volume masking via
+ # lsm.Client.volume_mask() method.
+ lsm.Capabilities.VOLUME_MASK_RO
+ # Indicate current system support masking a volume to
+ # access group with read only access.
+ """
+ return self._tp.rpc('volume_mask', _del_self(locals()))
+
+ @_return_requires(None)
+ def volume_unmask(self, volume=None, access_group=None, flags=0):
+ """
+ lsm.Client.volume_unmask(self, volume=None, access_group=None, flags=0)
+
+ Usage:
+ Unmask the volume. If define volume is not masked to access group,
+ this method will do nothing but return.
+ Parameters:
+ volume # Object of lsm.Volume
+ access_group # Object of lsm.AccessGroup
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Unmask done without error.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support volume unmask.
+ lsm.ErrorNumber.NOT_FOUND_VOLUME
+ # Defined lsm.Volume does not exist.
+ lsm.ErrorNumber.NOT_FOUND_ACCESS_GROUP
+ # Defined lsm.AccessGroup does not exist.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ lsm_mask_infos = lsm.Client.mask_infos()
+ Capability:
+ lsm.Capabilities.VOLUME_UNMASK
+ # Indicate current system support volume unmasking via
+ # lsm.Client.volume_unmask() method.
+ """
+ return self._tp.rpc('volume_unmask', _del_self(locals()))
+
## Returns an array of disk objects
# @param self The this pointer
# @param flags When equal to DISK.RETRIEVE_FULL_INFO
@@ -672,128 +763,248 @@ class Client(INetworkAttachedStorage):
"""
return self._tp.rpc('disks', _del_self(locals()))

- ## Access control for allowing an access group to access a volume
- # @param self The this pointer
- # @param group The access group
- # @param volume The volume to grant access to
- # @param access The desired access
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_grant(self, group, volume, access, flags=0):
- """
- Allows an access group to access a volume.
- """
- return self._tp.rpc('access_group_grant', _del_self(locals()))
-
- ## Revokes access to a volume to initiators in an access group
- # @param self The this pointer
- # @param group The access group
- # @param volume The volume to grant access to
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_revoke(self, group, volume, flags=0):
- """
- Revokes access for an access group for a volume
- """
- return self._tp.rpc('access_group_revoke', _del_self(locals()))
-
- ## Returns a list of access group objects
- # @param self The this pointer
- # @param flags Reserved for future use, must be zero.
- # @returns List of access groups
@_return_requires([AccessGroup])
- def access_groups(self, flags=0):
- """
- Returns a list of access groups
- """
- return self._tp.rpc('access_group_list', _del_self(locals()))
-
- ## Creates an access a group with the specified initiator in it.
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ """
+ lsm.Client.access_groups(self, search_key=None, search_value=None,
+ flags=0)
+
+ Usage:
+ Return a list of objects of lsm.AccessGroup.
+ If 'search_key' and 'search_value' is defined, only access group
+ matches will be contained in return list.
+ Valid search keys are stored in
+ lsm.AccessGroup.SUPPORTED_SEARCH_KEYS:
+ access_group_id # Search lsm.AccessGroup.id property
+ name # Search lsm.AccessGroup.name property
+ init_id # If given init_id is contained in
+ # lsm.AccessGroup.init_ids
+ volume_id # If access group is masked to given volume.
+ system_id # Return access group for given system only.
+ This method does not check whether provided resources ID is valid
+ or not, if resource ID is not exist at all, the search result will
+ be a empty list.
+ When no matches found, return a empty list
+ Parameters:
+ search_key # String. The key name for the search
+ search_value # The value of search_key to match
+ flags # Integer, Bit Map(Check Appendix.D for detail).
+ # Valid value:
+ # lsm.AccessGroup.FLAG_RETRIEVE_OPTIONAL_DATA
+ # # Return lsm.AccessGroup object will
+ # # contain optional properties.
+ # # But currently, there is no optional
+ # # property for lsm.AccessGroup yet, this
+ # # will make no difference.
+ Returns:
+ [lsm.AccessGroup,] # A list of lsm.AccessGroup objects.
+ or
+ [] # Nothing found.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.UNSUPPORTED_SEARCH_KEY
+ # Got unsupported search key. Should be one of
+ # lsm.AccessGroup.SUPPORTED_SEARCH_KEYS
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ access_groups = lsm_cli_obj.access_groups(
+ search_key=None, search_value=None, flags=0)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUPS
+ # Indicate current system support access group query via
+ # lsm.Client.access_groups() method.
+ lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ # This capability indicate plugin can perform a quick
+ # search without pull all data from storage system.
+ # Without this capability, plugin will pull all data from
+ # storage system to search given key. It only save socket data
+ # transfer between plugin and user API. If user want to
+ # do multiple search, it is suggested to query all access
+ # groups without 'search_key' parameter, then filter in user's
+ # code space.
+ """
+ if search_key and search_key not in AccessGroup.SUPPORTED_SEARCH_KEYS:
+ raise LsmError(ErrorNumber.UNSUPPORTED_SEARCH_KEY,
+ "Unsupported search key %s" % search_key)
+
+ if search_key == 'access_group_id':
+ search_key = 'id'
+
+ return self._tp.rpc('access_groups', _del_self(locals()))
+
+ ## Creates an access group with the specified initiators in it.
# @param self The this pointer
- # @param name The initiator group name
- # @param initiator_id Initiator id
- # @param id_type Type of initiator (Enumeration)
+ # @param access_group_name The access group name
+ # @param init_ids List, Initiator ids
+ # @param init_type Type of initiator (Enumeration)
# @param system_id Which system to create this group on
# @param flags Reserved for future use, must be zero.
# @returns AccessGroup on success, else raises LsmError
@_return_requires(AccessGroup)
- def access_group_create(self, name, initiator_id, id_type, system_id,
- flags=0):
- """
- Creates an access group and add the specified initiator id, id_type and
- desired access.
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
+ """
+ lsm.Client.access_group_create(self, access_group_name, init_ids,
+ init_type, system_ids, flags=0)
+
+ Usage:
+ Create a access group.
+ Parameters:
+ access_group_name # String. The name for new access group.
+ # If provided access_group_name does not meet
+ # the requirement of storage system, storage
+ # system will chose a valid one in stead of
+ # raise a error.
+ init_ids # List of string. The initiator IDs.
+ init_type # Integer. The type of initiator, could be one
+ # of these values:
+ # * lsm.AccessGroup.INIT_TYPE_WWPN
+ # * lsm.AccessGroup.INIT_TYPE_WWNN
+ # * lsm.AccessGroup.INIT_TYPE_HOSTNAME
+ # * lsm.AccessGroup.INIT_TYPE_ISCSI_IQN
+ # * lsm.AccessGroup.INIT_TYPE_SAS
+ system_id # The id of lsm.System where this access group
+ # should create on.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ lsm.AccessGroup # Object of lsm.AccessGroup
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support creating access group.
+ lsm.ErrorNumber.INVALID_INIT
+ # Provide init_ids contain ilegal string. Exmaple:
+ # WWPN does not in (?:[0-9a-f]{2}:){7}[0-9a-f]{2} format.
+ # Its user's responsibity for convering init_ids into lsm
+ # requested format.
+ lsm.ErrorNumber.INVALID_INIT_TYPE
+ # Got one of these ilegal init_type:
+ # * lsm.AccessGroup.INIT_TYPE_OTHER
+ # * lsm.AccessGroup.INIT_TYPE_UNKNOWN
+ lsm.ErrorNumber.UNSUPPORTED_INIT_TYPE
+ # System does not support requested init_type.
+ # For example, on FC only storage array, when user trying
+ # to create a iSCSI access group, this error could be
+ # used.
+ lsm.ErrorNumber.EXCEED_LIMITATION
+ # Storage system refuse to create new access group due to
+ # internal limitations. For example:
+ # * System only allow max 10 initiators in one access
+ # group. When creating access group with 11 or more
+ # initiators, this error will be used.
+ # * System only allow 100 access groups, when created new
+ access group after that, this error will be used.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_CREATE
+ # Indicate current system support create access group via
+ # lsm.Client.access_group_create() method.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWPN
+ # Support creating WWPN access group.
+ lsm.Capabilities.ACCESS_GROUP_CREATE_WWNN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_HOSTNAME
+ lsm.Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN
+ lsm.Capabilities.ACCESS_GROUP_CREATE_SAS
"""
return self._tp.rpc('access_group_create', _del_self(locals()))

## Deletes an access group.
- # @param self The this pointer
- # @param group The access group to delete
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_del(self, group, flags=0):
- """
- Deletes an access group
- """
- return self._tp.rpc('access_group_del', _del_self(locals()))
-
- ## Adds an initiator to an access group
- # @param self The this pointer
- # @param group Group to add initiator to
- # @param initiator_id Initiators id
- # @param id_type Initiator id type (enumeration)
- # @param flags Reserved for future use, must be zero.
- # @returns None on success, throws LsmError on errors.
- @_return_requires(None)
- def access_group_add_initiator(self, group, initiator_id, id_type,
- flags=0):
- """
- Adds an initiator to an access group
- """
- return self._tp.rpc('access_group_add_initiator', _del_self(locals()))
-
- ## Deletes an initiator from an access group
# @param self The this pointer
- # @param group The access group to remove initiator from
- # @param initiator_id The initiator to remove from the group
+ # @param access_group The lsm.AccessGroup to delete
# @param flags Reserved for future use, must be zero.
# @returns None on success, throws LsmError on errors.
@_return_requires(None)
- def access_group_del_initiator(self, group, initiator_id, flags=0):
- """
- Deletes an initiator from an access group
- """
- return self._tp.rpc('access_group_del_initiator', _del_self(locals()))
-
- ## Returns the list of volumes that access group has access to.
- # @param self The this pointer
- # @param group The access group to list volumes for
- # @param flags Reserved for future use, must be zero.
- # @returns list of volumes
- @_return_requires([Volume])
- def volumes_accessible_by_access_group(self, group, flags=0):
- """
- Returns the list of volumes that access group has access to.
- """
- return self._tp.rpc('volumes_accessible_by_access_group',
- _del_self(locals()))
-
- ##Returns the list of access groups that have access to the specified
- #volume.
- # @param self The this pointer
- # @param volume The volume to list access groups for
- # @param flags Reserved for future use, must be zero.
- # @returns list of access groups
- @_return_requires([AccessGroup])
- def access_groups_granted_to_volume(self, volume, flags=0):
- """
- Returns the list of access groups that have access to the specified
- volume.
- """
- return self._tp.rpc('access_groups_granted_to_volume',
- _del_self(locals()))
+ def access_group_delete(self, access_group, flags=0):
+ """
+ lsm.Client.access_group_delete(self, access_group, flags=0)
+
+ Usage:
+ Delete a access group. Only access group has no volume masked to
+ can be deleted.
+ User can use this method to query volumes masked to give access
+ group:
+ lsm.Client.volumes(self, search_key='access_group_id',
+ search_value=access_group.id)
+ Parameters:
+ access_group # Object of lsm.AccessGroup to delete.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Access group deleted without errors.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support deleting a access group.
+ lsm.ErrorNumber.IS_MASKED
+ # Access group has volume masked to.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ lsm_cli_obj.access_group_delete(new_ag)
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_DELETE
+ # Indicate current system support delete access group via
+ # lsm.Client.access_group_delete() method.
+ """
+ return self._tp.rpc('access_group_delete', _del_self(locals()))
+
+ ## Update an existing access group with new initiators.
+ # @param self The this pointer
+ # @param access_group The lsm.AccessGroup object
+ # @param new_init_ids New initiator IDs
+ # @param flags Reserved for future use, must be zero.
+ # @returns AccessGroup on success, else raises LsmError
+ @_return_requires(AccessGroup)
+ def access_group_edit(self, access_group, new_init_ids, flags=0):
+ """
+ lsm.Client.access_group_edit(self, access_group, new_init_ids, flags=0)
+
+ Usage:
+ Change the initiators of certain access group.
+ Will use initiators IDs defined in 'new_init_ids' to overide the
+ old initiators.
+ Parameters:
+ access_group # Object of lsm.AccessGroup to edit.
+ new_init_ids # List of string. New initiator IDs. Old one will
+ # be overided.
+ flags # Reserved for future use, should be 0.
+ Returns:
+ None # Access group edited without errors.
+ Exception:
+ LsmError
+ lsm.ErrorNumber.PLUGIN_ERROR
+ # Plugin error when query. Please report a bug.
+ lsm.ErrorNumber.NO_SUPPORT
+ # Does not support deleting a access group.
+ lsm.ErrorNumber.INVALID_INIT
+ # Provide new_init_ids contain ilegal string.
+ Sample:
+ lsm_cli_obj = lsm.Client('sim://')
+ new_ag = lsm_cli_obj.access_group_create(
+ "Test access group",
+ ['20:00:00:1b:21:67:65:d7', '20:00:00:1b:21:67:65:d6'],
+ lsm.AccessGroup.INIT_TYPE_WWPN)
+ # Remove one WWPN out.
+ lsm_cli_obj.access_group_edit(new_ag, ['20:00:00:1b:21:67:65:d7'])
+ Capability:
+ lsm.Capabilities.ACCESS_GROUP_EDIT
+ # Indicate current system support edit access group member
+ # via lsm.Client.access_group_edit() method.
+ """
+ return self._tp.rpc('access_group_edit', _del_self(locals()))

## Checks to see if a volume has child dependencies.
# @param self The this pointer
diff --git a/lsm/lsm/_common.py b/lsm/lsm/_common.py
index 5c8d278..c02b544 100644
--- a/lsm/lsm/_common.py
+++ b/lsm/lsm/_common.py
@@ -423,7 +423,7 @@ class ErrorNumber(object):
EXISTS_INITIATOR = 52
EXISTS_NAME = 53
FS_NOT_EXPORTED = 54
- INITIATOR_NOT_IN_ACCESS_GROUP = 55
+
EXISTS_POOL = 56
EXISTS_VOLUME = 57

@@ -433,6 +433,9 @@ class ErrorNumber(object):
INVALID_ERR = 103
INVALID_FS = 104
INVALID_INIT = 105
+ # Provided initiator ID is ilegal.
+ # For example, when 'init_type' is WWPN, 'INVALID_INIT' will be raised
+ # if you provide a iSCSI IQN.
INVALID_JOB = 106
INVALID_NAME = 107
INVALID_NFS = 108
@@ -450,7 +453,9 @@ class ErrorNumber(object):
INVALID_OPTIONAL_DATA = 120
INVALID_BLOCK_RANGE = 121

- IS_MAPPED = 125
+ IS_MASKED = 125
+ # volume is masked to certain access group.
+ # this error could be used when deleting a volume or access group.

NO_CONNECT = 150
NO_MAPPING = 151
@@ -504,6 +509,28 @@ class ErrorNumber(object):
DISK_BUSY = 500
VOLUME_BUSY = 501

+ UNSUPPORTED_SEARCH_KEY = 510
+ # Got unsupported search key when querying resources.
+
+ INVALID_INIT_TYPE = 511
+ # Got one of these ilegal init type:
+ # * lsm.AccessGroup.INIT_TYPE_OTHER
+ # * lsm.AccessGroup.INIT_TYPE_UNKNOWN
+
+ EXCEED_LIMITATION = 512
+ # Action was refused by storage system due to design limiation.
+ # For example, EMC VNX only allow 512 volumes in one pool.
+
+ UNSUPPORTED_ACCESS_TYPE = 513
+ # defined 'access_type' is not supported by storage system.
+ # For example, some array does not allow masking volume to a access group
+ # with read only access.
+
+ UNALLOWED_ACCESS_TYPE = 514
+ # Defined volume does not allow defined access type.
+ # Example: DR(Disaster Recover) site volumes does not allow RW access, but
+ # other normal volume can still be masked with RW access.
+
_LOCALS = locals()

@staticmethod
diff --git a/lsm/lsm/_data.py b/lsm/lsm/_data.py
index 603bf16..c268ccd 100644
--- a/lsm/lsm/_data.py
+++ b/lsm/lsm/_data.py
@@ -263,48 +263,6 @@ class IData(object):


@default_property('id', doc="Unique identifier")
-@default_property('type', doc="Enumerated initiator type")
-@default_property('name', doc="User supplied name")
-class Initiator(IData):
- """
- Represents an initiator.
- """
- (TYPE_OTHER, TYPE_PORT_WWN, TYPE_NODE_WWN, TYPE_HOSTNAME, TYPE_ISCSI,
- TYPE_SAS) = (1, 2, 3, 4, 5, 7)
-
- _type_map = {1: 'Other', 2: 'Port WWN', 3: 'Node WWN', 4: 'Hostname',
- 5: 'iSCSI', 7: "SAS"}
-
- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'type': 'Type',
- }
-
- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'type']
-
- def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
- if not enum_as_number:
- if key_name == 'type':
- value = Initiator._type_to_str(value)
- return value
-
- @staticmethod
- def _type_to_str(init_type):
- return Initiator._type_map[init_type]
-
- def __init__(self, _id, _type, _name):
-
- if not _name or not len(_name):
- name = "Unsupported"
-
- self._id = _id # Identifier
- self._type = _type # Initiator type id
- self._name = _name # Initiator name
-
-
-@default_property('id', doc="Unique identifier")
@default_property('name', doc="Disk name (aka. vendor)")
@default_property('disk_type', doc="Enumerated type of disk")
@default_property('block_size', doc="Size of each block")
@@ -1404,31 +1362,48 @@ class BlockRange(IData):

@default_property('id', doc="Unique instance identifier")
@default_property('name', doc="Access group name")
-@default_property('initiators', doc="List of initiators")
+@default_property('init_ids', doc="List of initiators IDs")
+@default_property('init_type', doc="Initiator type")
@default_property('system_id', doc="System identifier")
+@default_property("optional_data", doc="Optional data")
class AccessGroup(IData):
- def __init__(self, _id, _name, _initiators, _system_id='NA'):
- self._id = _id
- self._name = _name # AccessGroup name
- self._initiators = _initiators # List of initiators
- self._system_id = _system_id # System id this group belongs

- _MAN_PROPERTIES_2_HEADER = {
- 'id': 'ID',
- 'name': 'Name',
- 'initiators': 'Initiator IDs',
- 'system_id': 'System ID',
- }
+ FLAG_RETRIEVE_FULL_INFO = 1 << 0

- _MAN_PROPERTIES_SEQUENCE = ['id', 'name', 'initiators', 'system_id']
- _OPT_PROPERTIES_SEQUENCE = []
+ SUPPORTED_SEARCH_KEYS = ['id', 'access_group_id', 'name', 'init_ids',
+ 'volume_id', 'system_id']

- def _value_convert(self, key_name, value, human, enum_as_number,
- list_convert):
- if list_convert:
- if key_name == 'initiators':
- value = ','.join(str(x) for x in value)
- return value
+ INIT_TYPE_UNKNOWN = 0
+ INIT_TYPE_OTHER = 1
+ INIT_TYPE_WWPN = 2
+ INIT_TYPE_WWNN = 3
+ INIT_TYPE_HOSTNAME = 4
+ INIT_TYPE_ISCSI_IQN = 5
+ INIT_TYPE_SAS = 6
+
+ OPT_PROPERTIES = []
+
+ def __init__(self, _id, _name, _init_ids, _init_type, _system_id,
+ _optional_data=None):
+ self._id = _id
+ self._name = _name # AccessGroup name
+ self._init_ids = _init_ids # List of initiators
+ self._init_type = _init_type # Initiator type
+ self._system_id = _system_id # System id this group belongs
+
+ if _optional_data is None:
+ self._optional_data = OptionalData()
+ else:
+ #Make sure the properties only contain ones we permit
+ allowed = set(AccessGroup.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ if actual <= allowed:
+ self._optional_data = _optional_data
+ else:
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))


class OptionalData(IData):
@@ -1492,30 +1467,31 @@ class Capabilities(IData):
VOLUME_ONLINE = 34
VOLUME_OFFLINE = 35

- ACCESS_GROUP_GRANT = 36
- ACCESS_GROUP_REVOKE = 37
- ACCESS_GROUP_LIST = 38
- ACCESS_GROUP_CREATE = 39
- ACCESS_GROUP_DELETE = 40
- ACCESS_GROUP_ADD_INITIATOR = 41
- ACCESS_GROUP_DEL_INITIATOR = 42
+ ACCESS_GROUPS = 36
+ ACCESS_GROUPS_QUICK_SEARCH = 37
+ ACCESS_GROUP_CREATE = 38
+ ACCESS_GROUP_DELETE = 39
+ ACCESS_GROUP_EDIT = 39

- VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
- ACCESS_GROUPS_GRANTED_TO_VOLUME = 44
+ ACCESS_GROUP_CREATE_WWPN = 40
+ ACCESS_GROUP_CREATE_WWNN = 41
+ ACCESS_GROUP_CREATE_HOSTNAME = 42
+ ACCESS_GROUP_CREATE_ISCSI_IQN = 43
+ ACCESS_GROUP_CREATE_SAS = 44

VOLUME_CHILD_DEPENDENCY = 45
VOLUME_CHILD_DEPENDENCY_RM = 46

- INITIATORS = 47
- INITIATORS_GRANTED_TO_VOLUME = 48
-
- VOLUME_INITIATOR_GRANT = 50
- VOLUME_INITIATOR_REVOKE = 51
- VOLUME_ACCESSIBLE_BY_INITIATOR = 52
VOLUME_ISCSI_CHAP_AUTHENTICATION = 53

VOLUME_THIN = 55

+ MASK_INFOS = 60
+ MASK_INFOS_QUICK_SEARCH = 61
+ VOLUME_MASK = 62
+ VOLUME_MASK_RO = 63
+ VOLUME_UNMASK = 64
+
#File system
FS = 100
FS_DELETE = 101
@@ -1622,6 +1598,42 @@ class PlugData(IData):
self.desc = description
self.version = plugin_version

+@default_property('volume_id', doc="Volume ID")
+@default_property('access_group_id', doc="Access Group ID")
+@default_property('access_type', doc="Access Type")
+@default_property('system_id', doc="System ID")
+@default_property("optional_data", doc="Optional data")
+class MaskInfo(IData):
+
+ ACCESS_TYPE_UNKNOWN = 0
+ ACCESS_TYPE_READ_WRITE = 1
+ ACCESS_TYPE_READ_ONLY = 2
+
+ OPT_PROPERTIES = []
+
+ SUPPORTED_SEARCH_KEYS = ['volume_id', 'access_group_id', 'system_id']
+
+ def __init__(self, _volume_id, _access_group_id, _access_type,
+ _system_id, _optional_data=None):
+ self._volume_id = _volume_id
+ self._access_group_id = _access_group_id
+ self._access_type = _access_type
+ self._system_id = _system_id
+
+ if _optional_data is None:
+ self._optional_data = OptionalData()
+ else:
+ #Make sure the properties only contain ones we permit
+ allowed = set(MaskInfo.OPT_PROPERTIES)
+ actual = set(_optional_data.list())
+
+ if actual <= allowed:
+ self._optional_data = _optional_data
+ else:
+ raise LsmError(ErrorNumber.INVALID_ARGUMENT,
+ "Property keys are invalid: %s" %
+ "".join(actual - allowed))
+

if __name__ == '__main__':
#TODO Need some unit tests that encode/decode all the types with nested
--
1.8.3.1
Gris Ge
2014-04-28 09:17:40 UTC
Permalink
* Removed old volume masking commands:
access-grant
access-group-grant
access-revoke
access-group-revoke
volumes_accessible_by_access_group
volumes-accessible-initiator
access-group-volumes
initiators-granted-volume
* Added new commands:
volume-mask
volume-unmask
* New list type:
MASK_INFOS
* New options for list command on filtering:
--vol
--ag
--sys
# Currently only supported by list type: MASK_INFOS and ACCESS_GROUPS
* When '-o, --optional' define, the display method will be forced to script
way.

Signed-off-by: Gris Ge <***@redhat.com>
---
doc/man/lsmcli.1.in | 126 ++++++-------
lsm/lsm/_cmdline.py | 398 ++++++++++++++++-------------------------
lsm/lsm/lsmcli_data_display.py | 149 +++++++++++----
3 files changed, 319 insertions(+), 354 deletions(-)

diff --git a/doc/man/lsmcli.1.in b/doc/man/lsmcli.1.in
index ff11472..f7b908a 100644
--- a/doc/man/lsmcli.1.in
+++ b/doc/man/lsmcli.1.in
@@ -117,7 +117,7 @@ circumstance, \fb-b\fR will be effective, command will wait until finished.
\fB-s\fR, \fB--script\fR
Displaying data in script friendly way.
.br
-Without this option, data is displayed in this manner:
+Without this option, data is displayed in this column way:

ID | Name | Member IDs
-------------------------------------+-------+-------------
@@ -126,7 +126,7 @@ Without this option, data is displayed in this manner:
aa0ffc70-3dba-11df-b8cf-00a0980ad71b | aggr1 | ID_C
| | ID_D

-With this option, data is displayed in this manner.
+With this option, data is displayed in this script way.

--------------------------------------------------
ID | 87720e90-3a83-11df-b8bf-00a0980ad71b
@@ -139,6 +139,11 @@ With this option, data is displayed in this manner.
Member IDs | ID_C
| ID_D
--------------------------------------------------
+Please note:
+Not all mandatory properties will be displayed in column way considering the
+user's console text width. All mandatory properties will be displayed in
+script way. When with "-o, --optional", both mandatory properties and optional
+properties will be displayed.

.SH COMMANDS
.SS list
@@ -147,11 +152,25 @@ List information on LSM objects
\fB--type\fR \fI<TYPE>\fR
Required. Valid values are:
.br
-\fBVOLUMES\fR, \fBINITIATORS\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR,
+\fBVOLUMES\fR, \fBPOOLS\fR, \fBFS\fR, \fBSNAPSHOTS\fR, \fBMASK_INFOS\fR,
\fBEXPORTS\fR, \fBDISKS\fR,
.br
\fBNFS_CLIENT_AUTH\fR, \fBACCESS_GROUPS\fR,
\fBSYSTEMS\fR, \fBPLUGINS.
+.br
+The \fBVOLUMES\fR well-known as LUN by many storage array vendors. It is a
+virtual storage space which host operation system treated as a or many block
+device(s).
+.br
+The \fBACCESS_GROUPS\fR define a group of initiators to access volume.
+For example, most FC/FCoE HBA cards has two ports, each of two ports has
+one(or more with NPIV) WWPN(s). When performing volume masking, a volume is
+often mask to a access group containing all of these WWPNs. On EMC devices,
+access group is refered as "Host Group". On NetApp ONTAP devices, access group
+is refered as "Initiator Group(igroup)".
+.br
+The \fBMASK_INFOS\fR reprent a relationship between access group and volume.
+The volume masking is well-known as "LUN Masking".
.TP
\fB--fs\fR \fI<FS_ID>\fR
Required for \fB--type\fR=\fBSNAPSHOTS\fR. List the snapshots of certain
@@ -160,7 +179,18 @@ PLUGINS will list all supported plugins of LSM, not only the current one.
.TP
\fB-o\fR, \fB--optional\fR
Optional. Valid for \fBPOOLS\fR and \fBDISKS\fR to retrieve optional data if
-available.
+available. When define, will use '--script' display method.
+By default(without this argument), only mandatory properties will be
+retrieved.
+.TP
+\fB--vol\fR \fI<VOL_ID>\fR
+Optional. Filter with volume ID: <VOL_ID>
+.TP
+\fB--ag\fR \fI<AG_ID>\fR
+Optional. Filter with access group ID: <AG_ID>
+.TP
+\fB--sys\fR \fI<SYS_ID>\fR
+Optional. Filter with system ID: <SYS_ID>

.SS job-status
Retrieve information about a job.
@@ -281,43 +311,30 @@ Removes volume dependencies(like replication).
\fB--vol\fR \fI<VOL_ID>\fR
Required. The ID of volume to remove dependency.

-.SS volume-access-group
-Lists the access group(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS volumes-accessible-initiator
-Lists the initiator(s) that have access to the provided volume.
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to query access.
-
-.SS access-group-grant
+.SS volume-mask
.TP 15
-Grant a access group the RO or RW access to certain volume. Like LUN masking
-or NFS export.
+Mask a volume to certain access group. When defined volume is already masked
+to defined access group, the new '--access' will overide the old one.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to access.
+Required. The ID of volume to mask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to grant.
-
+Required. ID of access group to mask.
.TP
\fB--access\fR \fI<ACCESS>\fR
-Optional. Permission of access, valid values are \fBRO\fR, \fBRW\fR. Default
-value is \fBRW\fR.
+Optional. Define the access type, valid values are \fBRW\fR and \fBRO\fR,
+stand for "Read and Write" and "Read Only". Default value is RW.

-.SS access-group-revoke
+.SS volume-unmask
.TP 15
-Revoke an access group the RO or RW access to certain volume.
+Unmask a volume from certain access group.
.TP
\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
+Required. The ID of volume to unmask.
.TP
\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to revoke.
+Required. ID of access group to unmask.

.SS access-group-create
.TP 15
@@ -327,77 +344,36 @@ Create an access group.
Required. The human friendly name for new access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. The first initiator ID of new access group.
+Required. Repeatable. The initiator ID of new access group. For more than one
+initiators, use this: '\fB--init INIT_ID_1 --init INIT_ID_2\fR'.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
+\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR. All initiators defined should be in
+same initiator type.
.TP
\fB--sys\fR \fI<SYS_ID>\fR
Required. The ID of system where this access group should reside on.

-.SS access-group-add
+.SS access-group-edit
Adds an initiator to an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group.
.TP
\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to add.
+Required. Repeatable. New initiator ID.
.TP
\fB--init-type\fR \fI<INIT_TYPE>\fR
Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.

-.SS access-group-remove
-Removes an initiator from an access group.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. ID of access group.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to remove.
-
.SS access-group-delete
Delete an access group.
.TP 15
\fB--ag\fR \fI<AG_ID>\fR
Required. ID of access group to delete.

-.SS access-grant
-Grants access to an initiator to a volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to grant access.
-.TP
-\fB--init-type\fR \fI<INIT_TYPE>\fR
-Required. The initiator type. Valid values are: \fBWWPN\fR, \fBWWNN\fR,
-\fBISCSI\fR, \fBHOSTNAME\fR, \fBSAS\fR.
-.TP
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to grant access.
-
-.SS access-revoke
-Removes access for an initiator to a volume
-.TP 15
-\fB--vol\fR \fI<VOL_ID>\fR
-Required. The ID of volume to revoke.
-.TP
-\fB--init\fR \fI<INIT_ID>\fR
-Required. ID of initiator to revoke.
-
-.SS access-group-volumes
-Lists the volumes that the access group has been granted access to.
-.TP 15
-\fB--ag\fR \fI<AG_ID>\fR
-Required. The ID of access group to query.
-
-.SS initiators-granted-volume
-Lists the initiators that have been granted access to specified volume
-.TP 15
-\fB--init\fR \fI<INIT_ID>\fR
-Required. The ID of initiator to query.
-
.SS iscsi-chap
Configures ISCSI inbound/outbound CHAP authentication
.TP 15
diff --git a/lsm/lsm/_cmdline.py b/lsm/lsm/_cmdline.py
index 778647e..e174674 100644
--- a/lsm/lsm/_cmdline.py
+++ b/lsm/lsm/_cmdline.py
@@ -23,8 +23,8 @@ from argparse import ArgumentParser
from argparse import RawTextHelpFormatter

from lsm import (Client, Pool, VERSION, LsmError, Capabilities, Disk,
- Initiator, Volume, JobStatus, ErrorNumber, BlockRange,
- uri_parse)
+ Volume, JobStatus, ErrorNumber, BlockRange, uri_parse,
+ AccessGroup, MaskInfo)

from _data import PlugData
from _common import getch, size_human_2_size_bytes, Proxy
@@ -92,7 +92,7 @@ def _get_item(l, the_id, friendly_name='item'):
return i
raise ArgError('%s with id %s not found!' % (friendly_name, the_id))

-list_choices = ['VOLUMES', 'INITIATORS', 'POOLS', 'FS', 'SNAPSHOTS',
+list_choices = ['VOLUMES', 'MASK_INFOS', 'POOLS', 'FS', 'SNAPSHOTS',
'EXPORTS', "NFS_CLIENT_AUTH", 'ACCESS_GROUPS',
'SYSTEMS', 'DISKS', 'PLUGINS']

@@ -165,13 +165,22 @@ cmds = (
type=str.upper),
],
optional=[
- dict(name=('-a', '--all'),
- help='Retrieve and display in script friendly way with ' +
- 'all information including optional data if available',
+ dict(name=('-o', '--optional'),
+ help='Retrieve both mandatory and optional properties.\n'
+ 'Once define, will use "-s, --script" display way',
default=False,
- dest='all',
+ dest='optional',
action='store_true'),
dict(fs_id_opt),
+ dict(name=('--vol'),
+ metavar='<VOL_ID>',
+ help='Filter with defined volume ID'),
+ dict(name=('--ag'),
+ metavar='<AG_ID>',
+ help='Filter with defined access group ID'),
+ dict(name=('--sys'),
+ metavar='<SYS_ID>',
+ help='Filter with defined system ID'),
],
),

@@ -231,6 +240,28 @@ cmds = (
),

dict(
+ name='volume-mask',
+ help='Mask a volume to certain access group',
+ args=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ ],
+ optional=[
+ dict(access_opt),
+ ],
+ ),
+
+ dict(
+ name='volume-unmask',
+ help='Unmask a volume from certain access group via mask ID or ' +
+ 'combination of volume ID and access group ID',
+ args=[
+ dict(vol_id_opt),
+ dict(ag_id_opt),
+ ],
+ ),
+
+ dict(
name='volume-replicate',
help='Creates a new volume and peplicates provided volume to it.',
args=[
@@ -297,78 +328,19 @@ cmds = (
),

dict(
- name='volume-access-group',
- help='Lists the access group(s) that have access to volume',
- args=[
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='volumes-accessible-initiator',
- help='Lists the volumes that are accessible by the initiator',
- args=[
- dict(init_id_opt),
- ],
- ),
-
-
- dict(
- name='access-group-grant',
- help='Grants access to an access group to a volume, '
- 'like LUN Masking',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-group-revoke',
- help='Revoke the access of certain access group to a volume',
- args=[
- dict(ag_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
name='access-group-create',
help='Create an access group',
args=[
dict(name='--name', metavar='<AG_NAME>',
help="Human readble name for access group"),
- # TODO: _client.py access_group_create should support multipal
- # initiators when creating.
- dict(init_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
dict(init_type_opt),
dict(sys_id_opt),
],
),

- # TODO: Merge access_group_add() and access_group_remove() into
- # access_group_mofidy(ag_id, new_inits, init_type, flags)
- dict(
- name='access-group-add',
- help='Add an initiator into existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- dict(init_type_opt),
- ],
- ),
- dict(
- name='access-group-remove',
- help='Remove an initiator from existing access group',
- args=[
- dict(ag_id_opt),
- dict(init_id_opt),
- ],
- ),
-
dict(
name='access-group-delete',
help='Deletes an access group',
@@ -378,42 +350,13 @@ cmds = (
),

dict(
- name='access-grant',
- help='Grants access to an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(init_type_opt),
- dict(vol_id_opt),
- ],
- optional=[
- dict(access_opt),
- ],
- ),
-
- dict(
- name='access-revoke',
- help='Removes access for an initiator to a volume',
- args=[
- dict(init_id_opt),
- dict(vol_id_opt),
- ],
- ),
-
- dict(
- name='access-group-volumes',
- help='Lists the volumes that the access group has'
- ' been granted access to',
+ name='access-group-edit',
+ help='Edit the initiators of an access group',
args=[
dict(ag_id_opt),
- ],
- ),
-
- dict(
- name='initiators-granted-volume',
- help='Lists the initiators that have been '
- 'granted access to specified volume',
- args=[
- dict(vol_id_opt),
+ dict(name='--init', metavar='<INIT_ID>', action='append',
+ help='Initiator ID\n'
+ 'This is repeatable argument.'),
],
),

@@ -720,6 +663,28 @@ class CmdLine:
"""
Command line interface class.
"""
+ @staticmethod
+ def _init_type_to_enum(init_type_str):
+ _INIT_TYPE_STR_2_TYPE = {
+ 'WWPN': AccessGroup.INIT_TYPE_WWPN,
+ 'WWNN': AccessGroup.INIT_TYPE_WWNN,
+ 'ISCSI': AccessGroup.INIT_TYPE_ISCSI_IQN,
+ 'HOSTNAME': AccessGroup.INIT_TYPE_HOSTNAME,
+ 'SAS': AccessGroup.INIT_TYPE_SAS,
+ }
+ if init_type_str in _INIT_TYPE_STR_2_TYPE.keys():
+ return _INIT_TYPE_STR_2_TYPE[init_type_str]
+ raise ArgError("Invalid init_type string %s" % init_type_str)
+
+ @staticmethod
+ def _access_type_to_enum(access_type_str):
+ _ACCESS_TYPE_2_TYPE = {
+ 'RW': MaskInfo.ACCESS_TYPE_READ_WRITE,
+ 'RO': MaskInfo.ACCESS_TYPE_READ_ONLY,
+ }
+ if access_type_str in _ACCESS_TYPE_2_TYPE.keys():
+ return _ACCESS_TYPE_2_TYPE[access_type_str]
+ raise ArgError("Invalid access_type string %s" % access_type_str)

##
# Warn of imminent data loss
@@ -817,9 +782,6 @@ class CmdLine:
if len(objects) == 0:
return

- if hasattr(self.args, 'all') and self.args.all:
- display_all = True
-
flag_with_header = True
if self.args.sep:
flag_with_header = False
@@ -830,6 +792,10 @@ class CmdLine:
if self.args.script:
display_way = DisplayData.DISPLAY_WAY_SCRIPT

+ if hasattr(self.args, 'optional') and self.args.optional:
+ display_all = True
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
flag_new_way_works = DisplayData.display_data(
objects, display_way=display_way, flag_human=self.args.human,
flag_enum=self.args.enum, extra_properties=extra_properties,
@@ -1078,10 +1044,28 @@ class CmdLine:
## Method that calls the appropriate method based on what the list type is
# @param args Argparse argument object
def list(self, args):
+ search_key = None
+ search_value = None
+ if args.vol:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'volume_id'
+ search_value = args.vol
+ if args.ag:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'access_group_id'
+ search_value = args.ag
+ if args.sys:
+ if search_key is not None:
+ raise ArgError("Does not support specify two or more filters")
+ search_key = 'system_id'
+ search_value = args.sys
+
if args.type == 'VOLUMES':
self.display_data(self.c.volumes())
elif args.type == 'POOLS':
- if args.all:
+ if args.optional:
self.display_data(
self.c.pools(Pool.RETRIEVE_FULL_INFO))
else:
@@ -1094,88 +1078,47 @@ class CmdLine:

fs = _get_item(self.c.fs(), args.fs, 'filesystem')
self.display_data(self.c.fs_snapshots(fs))
- elif args.type == 'INITIATORS':
- self.display_data(self.c.initiators())
elif args.type == 'EXPORTS':
self.display_data(self.c.exports())
elif args.type == 'NFS_CLIENT_AUTH':
self.display_nfs_client_authentication()
elif args.type == 'ACCESS_GROUPS':
- self.display_data(self.c.access_groups())
+ self.display_data(self.c.access_groups(
+ search_key, search_value, 0))
elif args.type == 'SYSTEMS':
self.display_data(self.c.systems())
elif args.type == 'DISKS':
- if args.all:
+ if args.optional:
self.display_data(
self.c.disks(Disk.RETRIEVE_FULL_INFO))
else:
self.display_data(self.c.disks())
elif args.type == 'PLUGINS':
self.display_available_plugins()
+ elif args.type == 'MASK_INFOS':
+ self.display_data(self.c.mask_infos(search_key, search_value, 0))
else:
raise ArgError("unsupported listing type=%s" % args.type)

- ## Converts type initiator type to enumeration type.
- # @param type String representation of type
- # @returns Enumerated value
- @staticmethod
- def _init_type_to_enum(init_type):
- if init_type == 'WWPN':
- i = Initiator.TYPE_PORT_WWN
- elif init_type == 'WWNN':
- i = Initiator.TYPE_NODE_WWN
- elif init_type == 'ISCSI':
- i = Initiator.TYPE_ISCSI
- elif init_type == 'HOSTNAME':
- i = Initiator.TYPE_HOSTNAME
- elif init_type == 'SAS':
- i = Initiator.TYPE_SAS
- else:
- raise ArgError("invalid initiator type " + init_type)
- return i
-
## Creates an access group.
def access_group_create(self, args):
- i = CmdLine._init_type_to_enum(args.init_type)
- access_group = self.c.access_group_create(args.name, args.init, i,
- args.sys)
+ init_type = CmdLine._init_type_to_enum(args.init_type)
+ access_group = self.c.access_group_create(
+ args.name, args.init, init_type, args.sys)
self.display_data([access_group])

- def _add_rm_access_grp_init(self, args, op):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
-
- if op:
- i = CmdLine._init_type_to_enum(args.init_type)
- self.c.access_group_add_initiator(
- group, args.init, i)
- else:
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- self.c.access_group_del_initiator(group, i.id)
-
- ## Adds an initiator from an access group
- def access_group_add(self, args):
- self._add_rm_access_grp_init(args, True)
-
- ## Removes an initiator from an access group
- def access_group_remove(self, args):
- self._add_rm_access_grp_init(args, False)
-
- def access_group_volumes(self, args):
+ ## Used to delete access group
+ def access_group_delete(self, args):
agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- vols = self.c.volumes_accessible_by_access_group(group)
- self.display_data(vols)
+ access_group = _get_item(agl, args.ag, "access group id")
+ return self.c.access_group_delete(access_group)

- def volumes_accessible_initiator(self, args):
- i = _get_item(self.c.initiators(), args.init, "initiator id")
- volumes = self.c.volumes_accessible_by_initiator(i)
- self.display_data(volumes)
-
- def initiators_granted_volume(self, args):
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- initiators = self.c.initiators_granted_to_volume(vol)
- self.display_data(initiators)
+ def access_group_edit(self, args):
+ access_groups = self.c.access_groups()
+ access_group = _get_item(access_groups, args.ag, "access group id")
+ new_access_group = self.c.access_group_edit(
+ access_group, args.init)
+ self.display_data([new_access_group])

def iscsi_chap(self, args):
init = _get_item(self.c.initiators(), args.init, "initiator id")
@@ -1184,17 +1127,6 @@ class CmdLine:
self.args.out_user,
self.args.out_pass)

- def volume_access_group(self, args):
- vol = _get_item(self.c.volumes(), args.vol, "volume id")
- groups = self.c.access_groups_granted_to_volume(vol)
- self.display_data(groups)
-
- ## Used to delete access group
- def access_group_delete(self, args):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- return self.c.access_group_del(group)
-
## Used to delete a file system
def fs_delete(self, args):
fs = _get_item(self.c.fs(), args.fs, "filesystem id")
@@ -1283,9 +1215,6 @@ class CmdLine:
cap = self.c.capabilities(s)
self._cp("BLOCK_SUPPORT", cap.get(Capabilities.BLOCK_SUPPORT))
self._cp("FS_SUPPORT", cap.get(Capabilities.FS_SUPPORT))
- self._cp("INITIATORS", cap.get(Capabilities.INITIATORS))
- self._cp("INITIATORS_GRANTED_TO_VOLUME",
- cap.get(Capabilities.INITIATORS_GRANTED_TO_VOLUME))
self._cp("VOLUMES", cap.get(Capabilities.VOLUMES))
self._cp("VOLUME_CREATE", cap.get(Capabilities.VOLUME_CREATE))
self._cp("VOLUME_RESIZE", cap.get(Capabilities.VOLUME_RESIZE))
@@ -1310,34 +1239,40 @@ class CmdLine:
self._cp("VOLUME_DELETE", cap.get(Capabilities.VOLUME_DELETE))
self._cp("VOLUME_ONLINE", cap.get(Capabilities.VOLUME_ONLINE))
self._cp("VOLUME_OFFLINE", cap.get(Capabilities.VOLUME_OFFLINE))
- self._cp("VOLUME_INITIATOR_GRANT",
- cap.get(Capabilities.VOLUME_INITIATOR_GRANT))
- self._cp("VOLUME_INITIATOR_REVOKE",
- cap.get(Capabilities.VOLUME_INITIATOR_REVOKE))
self._cp("VOLUME_THIN",
cap.get(Capabilities.VOLUME_THIN))
self._cp("VOLUME_ISCSI_CHAP_AUTHENTICATION",
cap.get(Capabilities.VOLUME_ISCSI_CHAP_AUTHENTICATION))
- self._cp("ACCESS_GROUP_GRANT",
- cap.get(Capabilities.ACCESS_GROUP_GRANT))
- self._cp("ACCESS_GROUP_REVOKE",
- cap.get(Capabilities.ACCESS_GROUP_REVOKE))
- self._cp("ACCESS_GROUP_LIST",
- cap.get(Capabilities.ACCESS_GROUP_LIST))
+ self._cp("ACCESS_GROUPS",
+ cap.get(Capabilities.ACCESS_GROUPS))
+ self._cp("ACCESS_GROUPS_QUICK_SEARCH",
+ cap.get(Capabilities.ACCESS_GROUPS_QUICK_SEARCH))
self._cp("ACCESS_GROUP_CREATE",
cap.get(Capabilities.ACCESS_GROUP_CREATE))
+ self._cp("ACCESS_GROUP_CREATE_WWPN",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_WWPN))
+ self._cp("ACCESS_GROUP_CREATE_WWNN",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_WWNN))
+ self._cp("ACCESS_GROUP_CREATE_HOSTNAME",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_HOSTNAME))
+ self._cp("ACCESS_GROUP_CREATE_ISCSI_IQN",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_ISCSI_IQN))
+ self._cp("ACCESS_GROUP_CREATE_SAS",
+ cap.get(Capabilities.ACCESS_GROUP_CREATE_SAS))
self._cp("ACCESS_GROUP_DELETE",
cap.get(Capabilities.ACCESS_GROUP_DELETE))
- self._cp("ACCESS_GROUP_ADD_INITIATOR",
- cap.get(Capabilities.ACCESS_GROUP_ADD_INITIATOR))
- self._cp("ACCESS_GROUP_DEL_INITIATOR",
- cap.get(Capabilities.ACCESS_GROUP_DEL_INITIATOR))
- self._cp("VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP",
- cap.get(Capabilities.VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP))
- self._cp("VOLUME_ACCESSIBLE_BY_INITIATOR",
- cap.get(Capabilities.VOLUME_ACCESSIBLE_BY_INITIATOR))
- self._cp("ACCESS_GROUPS_GRANTED_TO_VOLUME",
- cap.get(Capabilities.ACCESS_GROUPS_GRANTED_TO_VOLUME))
+ self._cp("ACCESS_GROUP_EDIT",
+ cap.get(Capabilities.ACCESS_GROUP_EDIT))
+ self._cp("MASK_INFOS",
+ cap.get(Capabilities.MASK_INFOS))
+ self._cp("MASK_INFOS_QUICK_SEARCH",
+ cap.get(Capabilities.MASK_INFOS_QUICK_SEARCH))
+ self._cp("VOLUME_MASK",
+ cap.get(Capabilities.VOLUME_MASK))
+ self._cp("VOLUME_MASK_RO",
+ cap.get(Capabilities.VOLUME_MASK_RO))
+ self._cp("VOLUME_UNMASK",
+ cap.get(Capabilities.VOLUME_UNMASK))
self._cp("VOLUME_CHILD_DEPENDENCY",
cap.get(Capabilities.VOLUME_CHILD_DEPENDENCY))
self._cp("VOLUME_CHILD_DEPENDENCY_RM",
@@ -1535,53 +1470,6 @@ class CmdLine:
s = _get_item(self.c.systems(), args.sys, "system id")
out(self.c.volume_replicate_range_block_size(s))

- ## Used to grant or revoke access to a volume to an initiator.
- # @param grant bool, if True we grant, else we un-grant.
- def _access(self, grant, args):
- v = _get_item(self.c.volumes(), args.vol, "volume id")
- initiator_id = args.init
-
- if grant:
- i_type = CmdLine._init_type_to_enum(args.init_type)
- access = 'DEFAULT'
- if args.access is not None:
- access = Volume._access_string_to_type(args.access)
-
- self.c.initiator_grant(initiator_id, i_type, v, access)
- else:
- initiator = _get_item(self.c.initiators(), initiator_id,
- "initiator id")
-
- self.c.initiator_revoke(initiator, v)
-
- ## Grant access to volume to an initiator
- def access_grant(self, args):
- return self._access(True, args)
-
- ## Revoke access to volume to an initiator
- def access_revoke(self, args):
- return self._access(False, args)
-
- def _access_group(self, args, grant=True):
- agl = self.c.access_groups()
- group = _get_item(agl, args.ag, "access group id")
- v = _get_item(self.c.volumes(), args.vol, "volume id")
-
- if grant:
- access = 'RW'
- if args.access is not None:
- access = args.access
- access = Volume._access_string_to_type(args.access)
- self.c.access_group_grant(group, v, access)
- else:
- self.c.access_group_revoke(group, v)
-
- def access_group_grant(self, args):
- return self._access_group(args, grant=True)
-
- def access_group_revoke(self, args):
- return self._access_group(args, grant=False)
-
## Re-sizes a volume
def volume_resize(self, args):
v = _get_item(self.c.volumes(), args.vol, "volume id")
@@ -1592,6 +1480,22 @@ class CmdLine:
*self.c.volume_resize(v, size))
self.display_data([vol])

+ def volume_mask(self, args):
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ access_type = MaskInfo.ACCESS_TYPE_READ_WRITE
+ if args.access:
+ access_type = CmdLine._access_type_to_enum(args.access)
+ mask_info = self.c.volume_mask(volume, access_group, access_type)
+ self.display_data([mask_info])
+
+ def volume_unmask(self, args):
+ volume = _get_item(self.c.volumes(), args.vol, "Volume ID")
+ access_group = _get_item(self.c.access_groups(), args.ag,
+ "Access Group ID")
+ self.c.volume_unmask(volume, access_group, 0)
+
## Removes a nfs export
def fs_unexport(self, args):
export = _get_item(self.c.exports(), args.export, "nfs export id")
diff --git a/lsm/lsm/lsmcli_data_display.py b/lsm/lsm/lsmcli_data_display.py
index 810f590..da835d5 100644
--- a/lsm/lsm/lsmcli_data_display.py
+++ b/lsm/lsm/lsmcli_data_display.py
@@ -18,7 +18,7 @@
import sys
from collections import OrderedDict

-from lsm import System, size_bytes_2_size_human
+from lsm import System, AccessGroup, MaskInfo, size_bytes_2_size_human


class EnumConvert(object):
@@ -55,6 +55,34 @@ class EnumConvert(object):
return EnumConvert.SYSTEM_STATUS_CONV[System.STATUS_UNKNOWN]
return rc

+ INIT_TYPE_CONV = {
+ AccessGroup.INIT_TYPE_UNKNOWN: 'Unknown',
+ AccessGroup.INIT_TYPE_OTHER: 'Other',
+ AccessGroup.INIT_TYPE_WWPN: 'WWPN',
+ AccessGroup.INIT_TYPE_WWNN: 'WWNN',
+ AccessGroup.INIT_TYPE_HOSTNAME: 'Hostname',
+ AccessGroup.INIT_TYPE_ISCSI_IQN: 'iSCSI IQN',
+ AccessGroup.INIT_TYPE_SAS: 'SAS',
+ }
+
+ @staticmethod
+ def init_type_to_str(init_type):
+ if init_type in EnumConvert.INIT_TYPE_CONV.keys():
+ return EnumConvert.INIT_TYPE_CONV[init_type]
+ return EnumConvert.INIT_TYPE_CONV[AccessGroup.INIT_TYPE_UNKNOWN]
+
+ ACCESS_TYPE_CONV = {
+ MaskInfo.ACCESS_TYPE_UNKNOWN: 'Unknown',
+ MaskInfo.ACCESS_TYPE_READ_WRITE: 'Read Write',
+ MaskInfo.ACCESS_TYPE_READ_ONLY: 'Read Only',
+ }
+
+ @staticmethod
+ def access_type_to_str(access_type):
+ if access_type in EnumConvert.ACCESS_TYPE_CONV.keys():
+ return EnumConvert.ACCESS_TYPE_CONV[access_type]
+ return EnumConvert.INIT_TYPE_CONV[MaskInfo.ACCESS_TYPE_UNKNOWN]
+

class DisplayData(object):

@@ -77,6 +105,9 @@ class DisplayData(object):

DEFAULT_SPLITTER = ' | '

+ VALUE_CONVERT = dict()
+
+ # lsm.System
SYSTEM_MAN_HEADER = OrderedDict()
SYSTEM_MAN_HEADER['id'] = 'ID'
SYSTEM_MAN_HEADER['name'] = 'Name'
@@ -85,8 +116,14 @@ class DisplayData(object):

SYSTEM_OPT_HEADER = OrderedDict()

- SYSTEM_DSP_HEADER = SYSTEM_MAN_HEADER # SYSTEM_DSP_HEADER should be
- # subset of SYSTEM_MAN_HEADER
+ SYSTEM_COLUME_KEYS = SYSTEM_MAN_HEADER.keys()
+ # SYSTEM_COLUME_KEYS should be subset of SYSTEM_MAN_HEADER.keys()
+ # XXX_COLUME_KEYS contain a list of mandatory properties which will be
+ # displayed in column way. It was used to limit the output of properties
+ # in sure the colume display way does not exceeded the column width 78.
+ # All mandatory_headers will be displayed in script way.
+ # if '-o' define, both mandatory_headers and optional_headers will be
+ # displayed in script way.

SYSTEM_VALUE_CONV_ENUM = {
'status': EnumConvert.system_status_to_str,
@@ -94,14 +131,61 @@ class DisplayData(object):

SYSTEM_VALUE_CONV_HUMAN = []

- VALUE_CONVERT = {
- System: {
- 'mandatory_headers': SYSTEM_MAN_HEADER,
- 'display_headers': SYSTEM_DSP_HEADER,
- 'optional_headers': SYSTEM_OPT_HEADER,
- 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
- 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
- }
+ VALUE_CONVERT[System] = {
+ 'mandatory_headers': SYSTEM_MAN_HEADER,
+ 'column_keys': SYSTEM_COLUME_KEYS,
+ 'optional_headers': SYSTEM_OPT_HEADER,
+ 'value_conv_enum': SYSTEM_VALUE_CONV_ENUM,
+ 'value_conv_human': SYSTEM_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.AccessGroup
+ ACCESS_GROUP_MAN_HEADER = OrderedDict()
+ ACCESS_GROUP_MAN_HEADER['id'] = 'ID'
+ ACCESS_GROUP_MAN_HEADER['name'] = 'Name'
+ ACCESS_GROUP_MAN_HEADER['init_ids'] = 'Initiators'
+ ACCESS_GROUP_MAN_HEADER['init_type'] = 'Type'
+ ACCESS_GROUP_MAN_HEADER['system_id'] = 'System ID'
+ ACCESS_GROUP_OPT_HEADER = OrderedDict()
+ ACCESS_GROUP_COLUME_KEYS = ACCESS_GROUP_MAN_HEADER
+ ACCESS_GROUP_VALUE_CONV_ENUM = {
+ 'init_type': EnumConvert.init_type_to_str,
+ }
+
+ ACCESS_GROUP_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[AccessGroup] = {
+ 'mandatory_headers': ACCESS_GROUP_MAN_HEADER,
+ 'column_keys': ACCESS_GROUP_COLUME_KEYS,
+ 'optional_headers': ACCESS_GROUP_OPT_HEADER,
+ 'value_conv_enum': ACCESS_GROUP_VALUE_CONV_ENUM,
+ 'value_conv_human': ACCESS_GROUP_VALUE_CONV_HUMAN,
+ }
+
+ # lsm.MaskInfo
+ MASK_INFO_MAN_HEADER = OrderedDict()
+ MASK_INFO_MAN_HEADER['volume_id'] = 'Volume ID'
+ MASK_INFO_MAN_HEADER['access_group_id'] = 'Access Group ID'
+ MASK_INFO_MAN_HEADER['access_type'] = 'Access Type'
+ MASK_INFO_MAN_HEADER['system_id'] = 'System ID'
+
+ MASK_INFO_COLUME_KEYS = ['volume_id', 'access_group_id', 'access_type']
+
+ MASK_INFO_OPT_HEADER = OrderedDict()
+
+ # Hide system_id in default display.
+ MASK_INFO_VALUE_CONV_ENUM = {
+ 'access_type': EnumConvert.access_type_to_str,
+ }
+
+ MASK_INFO_VALUE_CONV_HUMAN = []
+
+ VALUE_CONVERT[MaskInfo] = {
+ 'mandatory_headers': MASK_INFO_MAN_HEADER,
+ 'column_keys': MASK_INFO_COLUME_KEYS,
+ 'optional_headers': MASK_INFO_OPT_HEADER,
+ 'value_conv_enum': MASK_INFO_VALUE_CONV_ENUM,
+ 'value_conv_human': MASK_INFO_VALUE_CONV_HUMAN,
}

@staticmethod
@@ -138,33 +222,34 @@ class DisplayData(object):
return max_width

@staticmethod
- def _data_dict_gen(obj, flag_human, flag_enum, extra_properties=None,
- flag_dsp_all_data=False):
+ def _data_dict_gen(obj, flag_human, flag_enum, display_way,
+ extra_properties=None, flag_dsp_all_data=False):
data_dict = OrderedDict()
value_convert = DisplayData.VALUE_CONVERT[type(obj)]
mandatory_headers = value_convert['mandatory_headers']
- display_headers = value_convert['display_headers']
optional_headers = value_convert['optional_headers']
value_conv_enum = value_convert['value_conv_enum']
value_conv_human = value_convert['value_conv_human']

- for key in display_headers.keys():
- key_str = display_headers[key]
+ if flag_dsp_all_data:
+ display_way = DisplayData.DISPLAY_WAY_SCRIPT
+
+ display_keys = []
+
+ if display_way == DisplayData.DISPLAY_WAY_COLUMN:
+ display_keys = value_convert['column_keys']
+ elif display_way == DisplayData.DISPLAY_WAY_SCRIPT:
+ display_keys = mandatory_headers.keys()
+
+ for key in display_keys:
+ key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human, flag_human,
flag_enum)
data_dict[key_str] = value

- if flag_dsp_all_data:
- for key in mandatory_headers.keys():
- if key in display_headers.keys():
- continue
- key_str = mandatory_headers[key]
- value = DisplayData._get_man_pro_value(
- obj, key, value_conv_enum, value_conv_human, flag_human,
- flag_enum)
- data_dict[key_str] = value

+ if flag_dsp_all_data:
for key in optional_headers.keys():
key_str = optional_headers[key]
value = DisplayData._get_opt_pro_value(
@@ -172,11 +257,12 @@ class DisplayData(object):
flag_enum)
data_dict[key_str] = value

- elif extra_properties:
+ if extra_properties:
for key in extra_properties:
+ if key in data_dict.keys():
+ # already contained
+ continue
if key in mandatory_headers.keys():
- if key in display_headers.keys():
- continue
key_str = mandatory_headers[key]
value = DisplayData._get_man_pro_value(
obj, key, value_conv_enum, value_conv_human,
@@ -188,7 +274,6 @@ class DisplayData(object):
obj, key, value_conv_enum, value_conv_human,
flag_human, flag_enum)
data_dict[key_str] = value
-
return data_dict

@staticmethod
@@ -211,8 +296,8 @@ class DisplayData(object):
if type(objs[0]) in DisplayData.VALUE_CONVERT.keys():
for obj in objs:
data_dict = DisplayData._data_dict_gen(
- obj, flag_human, flag_enum, extra_properties,
- flag_dsp_all_data)
+ obj, flag_human, flag_enum, display_way,
+ extra_properties, flag_dsp_all_data)
data_dict_list.extend([data_dict])
else:
return None
@@ -304,7 +389,7 @@ class DisplayData(object):
for raw in range(0, row_width):
new = []
for column in range(0, item_count):
- new.append([''])
+ new.append('')
two_d_list.append(new)

# header
--
1.8.3.1
Gris Ge
2014-04-28 09:17:41 UTC
Permalink
* Just sync with API changes on volume masking.
* Use lsm_plugin_helper.py(class LsmPluginHelper) for searching/filter
support on lsm.Client.access_groups() and lsm.Client.mask_infos()
# This could a example when other plugin want to use this routine but
# resource consuming searching methods.
* The simulator plugin does not enable all capabilities now. It will unset
these two as using LsmPluginHelper:
MASK_INFOS_QUICK_SEARCH
ACCESS_GROUPS_QUICK_SEARCH

Signed-off-by: Gris Ge <***@redhat.com>
---
libstoragemgmt.spec.in | 1 +
lsm/Makefile.am | 3 +-
lsm/lsm/lsm_plugin_helper.py | 72 +++++++++
lsm/lsm/simarray.py | 367 +++++++++++++------------------------------
lsm/lsm/simulator.py | 89 +++++------
5 files changed, 223 insertions(+), 309 deletions(-)
create mode 100644 lsm/lsm/lsm_plugin_helper.py

diff --git a/libstoragemgmt.spec.in b/libstoragemgmt.spec.in
index 57ee743..6fdedef 100644
--- a/libstoragemgmt.spec.in
+++ b/libstoragemgmt.spec.in
@@ -332,6 +332,7 @@ fi
%{python_sitelib}/lsm/simarray.*
%{python_sitelib}/lsm/_transport.*
%{python_sitelib}/lsm/version.*
+%{python_sitelib}/lsm/lsm_plugin_helper.*
%{_bindir}/sim_lsmplugin

%files smis-plugin
diff --git a/lsm/Makefile.am b/lsm/Makefile.am
index 029e87b..4f47f96 100644
--- a/lsm/Makefile.am
+++ b/lsm/Makefile.am
@@ -30,7 +30,8 @@ lsm_PYTHON = lsm/__init__.py \
lsm/smisproxy.py \
lsm/_transport.py \
lsm/version.py \
- lsm/targetd.py
+ lsm/targetd.py \
+ lsm/lsm_plugin_helper.py


dist_bin_SCRIPTS += $(IBM_PLUG)
diff --git a/lsm/lsm/lsm_plugin_helper.py b/lsm/lsm/lsm_plugin_helper.py
new file mode 100644
index 0000000..2d96d20
--- /dev/null
+++ b/lsm/lsm/lsm_plugin_helper.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2014 Red Hat, Inc.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Author: Gris Ge <***@redhat.com>
+
+from lsm import LsmError, ErrorNumber
+
+class LsmPluginHelper():
+ """
+ PluginHelper just containing the routine methods could be used by LSM
+ plugins. Some of these methods are having a very bad performance since
+ should be the last choice.
+ """
+
+ @staticmethod
+ def access_group_search(plugin_self, search_key, search_value, flags=0):
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
+ Conflict with lsm.Capabilities.ACCESS_GROUPS_QUICK_SEARCH
+ """
+ all_access_groups = plugin_self.access_groups(flags=flags)
+ if search_key in ['id', 'name', 'system_id']:
+ return list(a for a in all_access_groups
+ if getattr(a, search_key) == search_value)
+ if search_key == 'volume_id':
+ rc = []
+ ag_ids = []
+ all_mask_infos = plugin_self.mask_infos()
+ for mask_info in all_mask_infos:
+ if mask_info.volume_id == search_value:
+ ag_ids.extend([mask_info.access_group_id])
+ if ag_ids:
+ for access_group in all_access_groups:
+ if access_group.id in ag_ids:
+ rc.extend([access_group])
+ return rc
+ else:
+ return []
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
+
+ @staticmethod
+ def mask_info_search(plugin_self, search_key, search_value, flags=0):
+ """
+ BAD PERFORMANCE. Implemenet your own if possible.
+ Conflict with lsm.Capabilities.MASK_INFOS_QUICK_SEARCH
+ """
+ all_mask_infos = plugin_self.mask_infos(flags=flags)
+ if search_key in ['id', 'volume_id', 'access_group_id', 'system_id']:
+ return list(m for m in all_mask_infos
+ if getattr(m, search_key) == search_value)
+
+ # _client.py already checked the keys. If we reach here, it means
+ # user hit a bug.
+ raise LsmError(ErrorNumber.PLUGIN_ERROR,
+ "Search key %s is not implemented in PluginHelper" %
+ search_key)
diff --git a/lsm/lsm/simarray.py b/lsm/lsm/simarray.py
index 29cab42..c868846 100644
--- a/lsm/lsm/simarray.py
+++ b/lsm/lsm/simarray.py
@@ -27,8 +27,8 @@ import time

from _common import (size_human_2_size_bytes, size_bytes_2_size_human)
from lsm import (System, Volume, Disk, Pool, FileSystem, AccessGroup,
- Initiator, FsSnapshot, NfsExport, OptionalData, md5,
- LsmError, ErrorNumber, JobStatus)
+ FsSnapshot, NfsExport, OptionalData, md5, LsmError,
+ ErrorNumber, JobStatus, MaskInfo)

# Used for format width for disks
D_FMT = 5
@@ -350,71 +350,50 @@ class SimArray(object):

@staticmethod
def _sim_ag_2_lsm(sim_ag):
- return AccessGroup(sim_ag['ag_id'], sim_ag['name'],
- sim_ag['init_ids'], sim_ag['sys_id'])
+ return AccessGroup(sim_ag['id'], sim_ag['name'],
+ sim_ag['init_ids'], sim_ag['init_type'],
+ sim_ag['system_id'], OptionalData())

- def ags(self):
- sim_ags = self.data.ags()
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ sim_ags = self.data.access_groups(flags=flags)
+ return SimArray._sort_by_id(
+ [SimArray._sim_ag_2_lsm(a) for a in sim_ags])

- def access_group_create(self, name, init_id, init_type, sys_id, flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = self.data.access_group_create(
- name, init_id, init_type, sys_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimArray._sim_ag_2_lsm(sim_ag)

- def access_group_del(self, ag_id, flags=0):
- return self.data.access_group_del(ag_id, flags)
-
- def access_group_add_initiator(self, ag_id, init_id, init_type, flags=0):
- return self.data.access_group_add_initiator(
- ag_id, init_id, init_type, flags)
-
- def access_group_del_initiator(self, ag_id, init_id, flags=0):
- return self.data.access_group_del_initiator(ag_id, init_id, flags)
-
- def access_group_grant(self, ag_id, vol_id, access, flags=0):
- return self.data.access_group_grant(ag_id, vol_id, access, flags)
-
- def access_group_revoke(self, ag_id, vol_id, flags=0):
- return self.data.access_group_revoke(ag_id, vol_id, flags)
-
- def volumes_accessible_by_access_group(self, ag_id, flags=0):
- sim_vols = self.data.volumes_accessible_by_access_group(ag_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
-
- def access_groups_granted_to_volume(self, vol_id, flags=0):
- sim_ags = self.data.access_groups_granted_to_volume(vol_id, flags)
- return [SimArray._sim_ag_2_lsm(a) for a in sim_ags]
-
- @staticmethod
- def _sim_init_2_lsm(sim_init):
- return Initiator(sim_init['init_id'], sim_init['init_type'],
- sim_init['name'])
-
- def inits(self, flags=0):
- sim_inits = self.data.inits()
- return [SimArray._sim_init_2_lsm(a) for a in sim_inits]
-
- def initiator_grant(self, init_id, init_type, vol_id, access, flags=0):
- return self.data.initiator_grant(
- init_id, init_type, vol_id, access, flags)
-
- def initiator_revoke(self, init_id, vol_id, flags=0):
- return self.data.initiator_revoke(init_id, vol_id, flags)
-
- def volumes_accessible_by_initiator(self, init_id, flags=0):
- sim_vols = self.data.volumes_accessible_by_initiator(init_id, flags)
- return [SimArray._sim_vol_2_lsm(v) for v in sim_vols]
+ def access_group_delete(self, ag_id, flags=0):
+ return self.data.access_group_delete(ag_id, flags)

- def initiators_granted_to_volume(self, vol_id, flags=0):
- sim_inits = self.data.initiators_granted_to_volume(vol_id, flags)
- return [SimArray._sim_init_2_lsm(i) for i in sim_inits]
+ def access_group_edit(self, ag_id, new_init_ids, flags=0):
+ sim_ag = self.data.access_group_edit(ag_id, new_init_ids, flags)
+ return SimArray._sim_ag_2_lsm(sim_ag)

def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
flags=0):
return self.data.iscsi_chap_auth(init_id, in_user, in_pass, out_user,
out_pass, flags)

+ @staticmethod
+ def _sim_mask_2_lsm(sim_mask):
+ return MaskInfo(sim_mask['volume_id'], sim_mask['access_group_id'],
+ sim_mask['access_type'], sim_mask['system_id'],
+ OptionalData())
+
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ # search was handled by PlugHelper in simulator.py
+ sim_masks = self.data.mask_infos(flags=flags)
+ return [SimArray._sim_mask_2_lsm(m) for m in sim_masks]
+
+ def volume_mask(self, vol_id, ag_id, access_type, flags=0):
+ sim_mask = self.data.volume_mask(vol_id, ag_id, access_type, flags)
+ return SimArray._sim_mask_2_lsm(sim_mask)
+
+ def volume_unmask(self, vol_id=None, ag_id=None, flags=0):
+ return self.data.volume_unmask(vol_id, ag_id, flags)

class SimData(object):
"""
@@ -444,22 +423,6 @@ class SimData(object):
},
],
},
- 'mask': {
- ag_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- },
- 'mask_init': {
- init_id = Volume.ACCESS_READ_WRITE|Volume.ACCESS_READ_ONLY,
- }
- }
-
- self.init_dict = {
- Initiator.id = sim_init,
- }
- sim_init = {
- 'init_id': Initiator.id,
- 'init_type': Initiator.TYPE_XXXX,
- 'name': SimData.SIM_DATA_INIT_NAME,
- 'sys_id': SimData.SIM_DATA_SYS_ID,
}

self.ag_dict ={
@@ -467,9 +430,10 @@ class SimData(object):
}
sim_ag = {
'init_ids': [init_id,],
- 'sys_id': SimData.SIM_DATA_SYS_ID,
+ 'init_type': AccessGroup.INIT_TYPE_XXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
'name': name,
- 'ag_id': self._next_ag_id()
+ 'id': self._next_ag_id()
}

self.fs_dict = {
@@ -524,7 +488,7 @@ class SimData(object):
'name': pool_name,
'pool_id': Pool.id,
'raid_type': Pool.RAID_TYPE_XXXX,
- 'member_ids': [ disk_id or pool_id or volume_id ],
+ 'member_ids': [ disk_id or pool_id or vol_id ],
'member_type': Pool.MEMBER_TYPE_XXXX,
'member_size': size_bytes # space allocated from each member pool.
# only for MEMBER_TYPE_POOL
@@ -532,9 +496,21 @@ class SimData(object):
'sys_id': SimData.SIM_DATA_SYS_ID,
'element_type': SimData.SIM_DATA_POOL_ELEMENT_TYPE,
}
+
+ self.mask_dict= {
+ MaskInfo.id: sim_mask,
+ }
+
+ sim_mask = {
+ 'id': self._next_mask_id() # Internal use only.
+ 'access_group_id': AccessGroup.id,
+ 'volume_id': Volume.id,
+ 'access_type': MaskInfo.ACCESS_TYPE_XXXX,
+ 'system_id': SimData.SIM_DATA_SYS_ID,
+ }
"""
SIM_DATA_BLK_SIZE = 512
- SIM_DATA_VERSION = "2.2"
+ SIM_DATA_VERSION = "2.3"
SIM_DATA_SYS_ID = 'sim-01'
SIM_DATA_INIT_NAME = 'NULL'
SIM_DATA_TMO = 30000 # ms
@@ -555,6 +531,7 @@ class SimData(object):
SIM_DATA_CUR_AG_ID = 0
SIM_DATA_CUR_SNAP_ID = 0
SIM_DATA_CUR_EXP_ID = 0
+ SIM_DATA_CUR_MASK_ID = 0

def _next_pool_id(self):
self.SIM_DATA_CUR_POOL_ID += 1
@@ -580,6 +557,10 @@ class SimData(object):
self.SIM_DATA_CUR_EXP_ID += 1
return "EXP_ID_%08d" % self.SIM_DATA_CUR_EXP_ID

+ def _next_mask_id(self):
+ self.SIM_DATA_CUR_MASK_ID += 1
+ return "MASK_ID_%08d" % self.SIM_DATA_CUR_MASK_ID
+
@staticmethod
def state_signature():
return 'LSM_SIMULATOR_DATA_%s' % md5(SimData.SIM_DATA_VERSION)
@@ -674,8 +655,7 @@ class SimData(object):

self.ag_dict = {
}
- self.init_dict = {
- }
+ self.mask_dict = dict()
# Create some volumes, fs and etc
self.volume_create(
'POO1', 'Volume 000', size_human_2_size_bytes('200GiB'),
@@ -864,7 +844,8 @@ class SimData(object):
def disks(self):
return self.disk_dict.values()

- def access_group_list(self):
+ def access_groups(self, flags=0):
+ # search was handled by simulator.py
return self.ag_dict.values()

def volume_create(self, pool_id, vol_name, size_bytes, thinp, flags=0):
@@ -1029,207 +1010,81 @@ class SimData(object):
del sim_vol['replicate'][vol_id]
return None

- def ags(self, flags=0):
- return self.ag_dict.values()
-
- def access_group_create(self, name, init_id, init_type, sys_id, flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = dict()
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
-
- sim_ag['init_ids'] = [init_id]
- sim_ag['sys_id'] = SimData.SIM_DATA_SYS_ID
- sim_ag['name'] = name
- sim_ag['ag_id'] = self._next_ag_id()
- self.ag_dict[sim_ag['ag_id']] = sim_ag
+ sim_ag['init_ids'] = init_ids
+ sim_ag['init_type'] = init_type
+ sim_ag['system_id'] = SimData.SIM_DATA_SYS_ID
+ sim_ag['name'] = access_group_name
+ sim_ag['id'] = self._next_ag_id()
+ self.ag_dict[sim_ag['id']] = sim_ag
return sim_ag

- def access_group_del(self, ag_id, flags=0):
+ def access_group_delete(self, ag_id, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
del(self.ag_dict[ag_id])
return None

- def access_group_add_initiator(self, ag_id, init_id, init_type, flags=0):
+ def access_group_edit(self, ag_id, new_init_ids, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
"Access group not found")
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- if init_id in self.ag_dict[ag_id]['init_ids']:
- return self.ag_dict[ag_id]
-
- self.ag_dict[ag_id]['init_ids'].extend([init_id])
+ sim_ag = self.ag_dict[ag_id]
+ del(self.ag_dict[ag_id])
+ sim_ag['init_ids'] = new_init_ids
+ self.ag_dict[ag_id] = sim_ag
+ return sim_ag

+ def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
+ flags=0):
+ # No iscsi chap query API yet
return None

- def access_group_del_initiator(self, ag_id, init_id, flags=0):
- if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- if init_id not in self.init_dict.keys():
- return None
-
- if init_id in self.ag_dict[ag_id]['init_ids']:
- new_init_ids = []
- for cur_init_id in self.ag_dict[ag_id]['init_ids']:
- if cur_init_id != init_id:
- new_init_ids.extend([cur_init_id])
- del(self.ag_dict[ag_id]['init_ids'])
- self.ag_dict[ag_id]['init_ids'] = new_init_ids
- return None
+ def mask_infos(self, flags=0):
+ # search was handled by PlugHelper in simulator.py
+ return self.mask_dict.values()

- def access_group_grant(self, ag_id, vol_id, access, flags=0):
+ def volume_mask(self, vol_id, ag_id, access_type, flags=0):
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
+ "Access group %s not found" % ag_id)
if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
+ raise LsmError(ErrorNumber.NOT_FOUND_VOLUME,
"No such Volume: %s" % vol_id)
- if 'mask' not in self.vol_dict[vol_id].keys():
- self.vol_dict[vol_id]['mask'] = dict()
-
- self.vol_dict[vol_id]['mask'][ag_id] = access
- return None
-
- def access_group_revoke(self, ag_id, vol_id, flags=0):
- if ag_id not in self.ag_dict.keys():
- raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
+ mask_id = None
+ # Check whether we are updating existing mask.
+ for sim_mask in self.mask_dict.values():
+ if sim_mask['volume_id'] == vol_id and \
+ sim_mask['access_group_id'] == ag_id:
+ mask_id = sim_mask['id']
+ del(self.mask_dict[mask_id])
+ break
+ if mask_id is None:
+ mask_id = self._next_mask_id()
+ sim_mask = dict()
+ sim_mask['id'] = mask_id
+ sim_mask['volume_id'] = vol_id
+ sim_mask['access_group_id'] = ag_id
+ sim_mask['access_type'] = access_type
+ sim_mask['system_id'] = SimData.SIM_DATA_SYS_ID
+ self.mask_dict[mask_id] = sim_mask
+ return sim_mask
+
+ def volume_unmask(self, vol_id=None, ag_id=None, flags=0):
if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if 'mask' not in self.vol_dict[vol_id].keys():
- return None
-
- if ag_id not in self.vol_dict[vol_id]['mask'].keys():
- return None
-
- del(self.vol_dict[vol_id]['mask'][ag_id])
- return None
-
- def volumes_accessible_by_access_group(self, ag_id, flags=0):
+ raise LsmError(ErrorNumber.NOT_FOUND_VOLUME,
+ "Invalid volume ID: %s" % vol_id)
if ag_id not in self.ag_dict.keys():
raise LsmError(ErrorNumber.NOT_FOUND_ACCESS_GROUP,
- "Access group not found: %s" % ag_id)
- rc = []
- for sim_vol in self.vol_dict.values():
- if 'mask' not in sim_vol:
- continue
- if ag_id in sim_vol['mask'].keys():
- rc.extend([sim_vol])
- return rc
-
- def access_groups_granted_to_volume(self, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- sim_ags = []
- if 'mask' in self.vol_dict[vol_id].keys():
- ag_ids = self.vol_dict[vol_id]['mask'].keys()
- for ag_id in ag_ids:
- sim_ags.extend([self.ag_dict[ag_id]])
- return sim_ags
-
- def inits(self, flags=0):
- return self.init_dict.values()
-
- def initiator_grant(self, init_id, init_type, vol_id, access, flags):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if init_id not in self.init_dict.keys():
- sim_init = dict()
- sim_init['init_id'] = init_id
- sim_init['init_type'] = init_type
- sim_init['name'] = SimData.SIM_DATA_INIT_NAME
- sim_init['sys_id'] = SimData.SIM_DATA_SYS_ID
- self.init_dict[init_id] = sim_init
- if 'mask_init' not in self.vol_dict[vol_id].keys():
- self.vol_dict[vol_id]['mask_init'] = dict()
-
- self.vol_dict[vol_id]['mask_init'][init_id] = access
- return None
-
- def initiator_revoke(self, init_id, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
-
- if 'mask_init' in self.vol_dict[vol_id].keys():
- if init_id in self.vol_dict[vol_id]['mask_init'].keys():
- del self.vol_dict[vol_id]['mask_init'][init_id]
-
- return None
-
- def _ag_ids_of_init(self, init_id):
- """
- Find out the access groups defined initiator belong to.
- Will return a list of access group id or []
- """
- rc = []
- for sim_ag in self.ag_dict.values():
- if init_id in sim_ag['init_ids']:
- rc.extend([sim_ag['ag_id']])
- return rc
-
- def volumes_accessible_by_initiator(self, init_id, flags=0):
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- rc_dedup_dict = dict()
- ag_ids = self._ag_ids_of_init(init_id)
- for ag_id in ag_ids:
- sim_vols = self.volumes_accessible_by_access_group(ag_id)
- for sim_vol in sim_vols:
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
-
- for sim_vol in self.vol_dict.values():
- if 'mask_init' in sim_vol:
- if init_id in sim_vol['mask_init'].keys():
- rc_dedup_dict[sim_vol['vol_id']] = sim_vol
- return rc_dedup_dict.values()
-
- def initiators_granted_to_volume(self, vol_id, flags=0):
- if vol_id not in self.vol_dict.keys():
- raise LsmError(ErrorNumber.INVALID_VOLUME,
- "No such Volume: %s" % vol_id)
- rc_dedup_dict = dict()
- sim_ags = self.access_groups_granted_to_volume(vol_id, flags)
- for sim_ag in sim_ags:
- for init_id in sim_ag['init_ids']:
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- if 'mask_init' in self.vol_dict[vol_id].keys():
- for init_id in self.vol_dict[vol_id]['mask_init']:
- rc_dedup_dict[init_id] = self.init_dict[init_id]
-
- return rc_dedup_dict.values()
-
- def iscsi_chap_auth(self, init_id, in_user, in_pass, out_user, out_pass,
- flags=0):
- if init_id not in self.init_dict.keys():
- raise LsmError(ErrorNumber.INVALID_INIT,
- "No such Initiator: %s" % init_id)
- if self.init_dict[init_id]['init_type'] != Initiator.TYPE_ISCSI:
- raise LsmError(ErrorNumber.UNSUPPORTED_INITIATOR_TYPE,
- "Initiator %s is not an iSCSI IQN" % init_id)
- # No iscsi chap query API yet
- return None
+ "Invalid access group ID: %s" % ag_id)
+ for sim_mask in self.mask_dict.values():
+ if sim_mask['volume_id'] == vol_id and \
+ sim_mask['access_group_id'] == ag_id:
+ del(self.mask_dict[sim_mask['id']])
+ return None

def fs(self):
return self.fs_dict.values()
diff --git a/lsm/lsm/simulator.py b/lsm/lsm/simulator.py
index 1ea1a45..4b52b8a 100644
--- a/lsm/lsm/simulator.py
+++ b/lsm/lsm/simulator.py
@@ -20,6 +20,8 @@ from lsm import (uri_parse, VERSION, Capabilities, Pool, INfs,

from simarray import SimArray

+from lsm_plugin_helper import LsmPluginHelper
+

class SimPlugin(INfs, IStorageAreaNetwork):
"""
@@ -76,6 +78,10 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def capabilities(self, system, flags=0):
rc = Capabilities()
rc.enable_all()
+ rc.set(Capabilities.MASK_INFOS_QUICK_SEARCH,
+ Capabilities.UNSUPPORTED)
+ rc.set(Capabilities.ACCESS_GROUPS_QUICK_SEARCH,
+ Capabilities.UNSUPPORTED)
return rc

def plugin_info(self, flags=0):
@@ -162,67 +168,46 @@ class SimPlugin(INfs, IStorageAreaNetwork):
def volume_offline(self, volume, flags=0):
return self.sim_array.volume_online(volume.id, flags)

- def access_group_list(self, flags=0):
- sim_ags = self.sim_array.ags()
- return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ def access_groups(self, search_key=None, search_value=None, flags=0):
+ if search_key is None:
+ sim_ags = self.sim_array.access_groups(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(a) for a in sim_ags]
+ else:
+ return LsmPluginHelper.access_group_search(
+ self, search_key, search_value, flags)

- def access_group_create(self, name, initiator_id, id_type, system_id,
- flags=0):
+ def access_group_create(self, access_group_name, init_ids, init_type,
+ system_id, flags=0):
sim_ag = self.sim_array.access_group_create(
- name, initiator_id, id_type, system_id, flags)
+ access_group_name, init_ids, init_type, system_id, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)

- def access_group_del(self, group, flags=0):
- return self.sim_array.access_group_del(group.id, flags)
+ def access_group_delete(self, access_group, flags=0):
+ return self.sim_array.access_group_delete(access_group.id, flags)

- def access_group_add_initiator(self, group, initiator_id, id_type,
- flags=0):
- sim_ag = self.sim_array.access_group_add_initiator(
- group.id, initiator_id, id_type, flags)
+ def access_group_edit(self, access_group, new_init_ids, flags=0):
+ sim_ag = self.sim_array.access_group_edit(
+ access_group.id, new_init_ids, flags)
return SimPlugin._sim_data_2_lsm(sim_ag)

- def access_group_del_initiator(self, group, initiator_id, flags=0):
- return self.sim_array.access_group_del_initiator(
- group.id, initiator_id, flags)
-
- def access_group_grant(self, group, volume, access, flags=0):
- return self.sim_array.access_group_grant(
- group.id, volume.id, access, flags)
-
- def access_group_revoke(self, group, volume, flags=0):
- return self.sim_array.access_group_revoke(
- group.id, volume.id, flags)
-
- def volumes_accessible_by_access_group(self, group, flags=0):
- sim_vols = self.sim_array.volumes_accessible_by_access_group(
- group.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- def access_groups_granted_to_volume(self, volume, flags=0):
- sim_vols = self.sim_array.access_groups_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
-
- def initiators(self, flags=0):
- return self.sim_array.inits(flags)
-
- def initiator_grant(self, initiator_id, initiator_type, volume, access,
- flags=0):
- return self.sim_array.initiator_grant(
- initiator_id, initiator_type, volume.id, access, flags)
-
- def initiator_revoke(self, initiator, volume, flags=0):
- return self.sim_array.initiator_revoke(initiator.id, volume.id, flags)
+ def mask_infos(self, search_key=None, search_value=None, flags=0):
+ if search_key is None:
+ sim_masks = self.sim_array.mask_infos(
+ search_key=None, search_value=None, flags=flags)
+ return [SimPlugin._sim_data_2_lsm(m) for m in sim_masks]
+ else:
+ return LsmPluginHelper.mask_info_search(
+ self, search_key, search_value, flags)

- def volumes_accessible_by_initiator(self, initiator, flags=0):
- sim_vols = self.sim_array.volumes_accessible_by_initiator(
- initiator.id, flags)
- return [SimPlugin._sim_data_2_lsm(v) for v in sim_vols]
+ def volume_mask(self, volume, access_group, access_type, flags=0):
+ sim_mask = self.sim_array.volume_mask(
+ volume.id, access_group.id, access_type, flags)
+ return SimPlugin._sim_data_2_lsm(sim_mask)

- def initiators_granted_to_volume(self, volume, flags=0):
- sim_inits = self.sim_array.initiators_granted_to_volume(
- volume.id, flags)
- return [SimPlugin._sim_data_2_lsm(i) for i in sim_inits]
+ def volume_unmask(self, volume=None, access_group=None, flags=0):
+ return self.sim_array.volume_unmask(
+ vol_id=volume.id, ag_id=access_group.id, flags=flags)

def iscsi_chap_auth(self, initiator, in_user, in_password,
out_user, out_password, flags=0):
--
1.8.3.1
Tony Asleson
2014-04-28 19:00:53 UTC
Permalink
Overall the patch looks pretty good, some comments:

1. Have you looked at SMI-S, nstor, targetd and ontap to make sure that
these changes will work with each of them? I know you were going to
work on a patch to demo that targetd could be written without the need
for initiator support.

2. access_group_edit

After reviewing this some more, I think we should keep separate
functions to add/remove individual initiators from the access group for
the following reasons:

* Each time the user wants to simply add or remove 1 initiator from the
command line they need to specify all of them +/- 1. If a user has a
number of initiators in a group it will be ugly to have that many on the
command line.

* A user can add/remove 1 or more initiators in a single call. What
happens if during processing you get a failure? How do you return
information specifying which operations completed, which failed? Do you
try to restore to previous state, what if that fails?

* On the plug-in side you cannot just remove all the initiators and add
them back as this would disrupt existing mappings. Thus for every
plug-in you need to:

Take the list that was passed in (NEW) and take a list of what you have
(CURR) and:

- For every item on the CURR list delete initiators that aren't in NEW
- For every item on the NEW list that isn't on CURR, add it

There are a number of ways to work this without making this an O(N*N)
operation, but still no where as simple as adding or removing one.

3. Test code updates would be appreciated too, so that we can ensure
that the new code is working as advertised against the simulator and
against actual arrays.

Thanks!

Regards,
Tony
Post by Gris Ge
Sorry for big patch. It's pretty hard to split them into pieces.
* Update Python API codes to compliant with API documents for
* Update lsm.AccessGroup
* Remove lsm.Initiator
* Add lsm.MaskInfo
* simulator plugin update for this change.
* Introduce LsmPluginHelper for searching/filtering.
* lsmcli update for support this change.
I just retype this patch set in three days after a data lose.
It surely has pretty errors. Please kindly let me know if you found anything
wrong or suspicious.
1. C/C++ codes.
2. Test codes. # I can do the python test codes part.
3. iSCSI CHAP.
# I will add a section in API document and provide patch before end of
# this week.
4. Query on lsm.Client.volumes()
# This will come alone with code-doc sync on lsm.Volume patch set.
* SMI-S -- smis.py # I will take this
* Simulator C -- simc_lsmplugin.c
* ONTAP -- ontap.py
* targetd -- targetd.py # I will take this
* IBM V7K -- ibmv7k.py
* NStor -- nstor.py
6. Add more search/filter support to other query methods.
Goal is allowing the same set of search_keys on every query methods.
1. Use shared ErrorNumber.
2. Removed lsm.MaskInfo.id property.
3. Change lsm.Client.volume_unmask() to take volume and access group only.
4. Renamed plugin_helper.py to lsm_plugin_helper.py.
# As it will not added into "lsm" namespace, we'd better give it a prefix.
5. Update rpm spec and automake files for new file lsm_plugin_helper.py.
Python API: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator
lsmcli: update volume masking related commands
simulator.py: update lsm.AccessGroup, add Lsm.MaskInfo, remove
lsm.Initiator
doc/man/lsmcli.1.in | 126 ++++------
libstoragemgmt.spec.in | 1 +
lsm/Makefile.am | 3 +-
lsm/lsm/__init__.py | 6 +-
lsm/lsm/_client.py | 555 ++++++++++++++++++++++++++++-------------
lsm/lsm/_cmdline.py | 398 +++++++++++------------------
lsm/lsm/_common.py | 31 ++-
lsm/lsm/_data.py | 166 ++++++------
lsm/lsm/lsm_plugin_helper.py | 72 ++++++
lsm/lsm/lsmcli_data_display.py | 149 ++++++++---
lsm/lsm/simarray.py | 367 +++++++++------------------
lsm/lsm/simulator.py | 89 +++----
12 files changed, 1046 insertions(+), 917 deletions(-)
create mode 100644 lsm/lsm/lsm_plugin_helper.py
Tony Asleson
2014-04-30 21:36:37 UTC
Permalink
Post by Gris Ge
Sorry for big patch. It's pretty hard to split them into pieces.
Some suggestions:

Add functionality and new tests and then remove the deprecated
functionality. This way the code base remains in a testable & usable
state. You can always add easier than change.

Example add:
- Add mask info class
- Add mask_info method and simulator support
- Add volume_mask with simulator support
- Add volume_unmask with simulator support
- Add mask method support for each plug-in
- Once everything looks good, remove deprecated functionality

When re-factoring existing code pick one thing and fix it throughout the
code base including all plug-ins.

Example re-factoring:
- Add generic filtering plug-in code base
- Modify each of the plug-ins to take filtering for one method, like
volumes, but keep the client API the same
- Modify client API and CLI
- Repeat for other methods etc.

The re-factoring gets a little involved because the C bits need to
happen in unison with the python bits to keep stuff working. I can
certainly help with that.

I would very much like to keep the code base tested and working through
changes instead of a big change and a number of commits to get it
working again if at all possible.

Thanks!

Regards,
Tony
Gris Ge
2014-05-01 02:50:19 UTC
Permalink
Post by Tony Asleson
I would very much like to keep the code base tested and working through
changes instead of a big change and a number of commits to get it
working again if at all possible.
Well noted.
Thanks for pointing out.
Post by Tony Asleson
Thanks!
Regards,
Tony
--
Gris Ge
Loading...