# Copyright (C) 2011-2016 Red Hat, Inc.
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development LP
# 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, see .
#
# Author: tasleson
# Gris Ge
# Joe Handzik
from abc import ABCMeta as _ABCMeta
import re
import binascii
from six import with_metaclass
try:
import simplejson as json
except ImportError:
import json
from json.decoder import WHITESPACE
from lsm._common import get_class, default_property, ErrorNumber, LsmError
import six
class DataEncoder(json.JSONEncoder):
"""
Custom json encoder for objects derived form ILsmData
"""
def default(self, my_class):
if not isinstance(my_class, IData):
raise ValueError('incorrect class type:' + str(type(my_class)))
else:
return my_class._to_dict()
class DataDecoder(json.JSONDecoder):
"""
Custom json decoder for objects derived from ILsmData
"""
@staticmethod
def __process_dict(d):
"""
Processes a dictionary
"""
rc = {}
if 'class' in d:
rc = IData._factory(d)
else:
for (k, v) in d.items():
rc[k] = DataDecoder.__decode(v)
return rc
@staticmethod
def __process_list(l):
"""
Processes a list
"""
rc = []
for elem, value in enumerate(l):
if type(value) is list:
rc.append(DataDecoder.__process_list(value))
elif type(value) is dict:
rc.append(DataDecoder.__process_dict(value))
else:
rc.append(value)
return rc
@staticmethod
def __decode(e):
"""
Decodes the parsed json
"""
if type(e) is dict:
return DataDecoder.__process_dict(e)
elif type(e) is list:
return DataDecoder.__process_list(e)
else:
return e
def decode(self, json_string, _w=WHITESPACE.match):
return DataDecoder.__decode(json.loads(json_string))
class IData(with_metaclass(_ABCMeta, object)):
"""
Base class functionality of serializable
classes.
"""
def _to_dict(self):
"""
Represent the class as a dictionary
"""
rc = {'class': self.__class__.__name__}
# If one of the attributes is another IData we will
# process that too, is there a better way to handle this?
for (k, v) in list(self.__dict__.items()):
if isinstance(v, IData):
rc[k[1:]] = v._to_dict()
else:
rc[k[1:]] = v
return rc
@staticmethod
def _factory(d):
"""
Factory for creating the appropriate class given a dictionary.
This only works for objects that inherit from IData
"""
if 'class' in d:
class_name = d['class']
del d['class']
c = get_class(__name__ + '.' + class_name)
# If any of the parameters are themselves an IData process them
for k, v in list(d.items()):
if isinstance(v, dict) and 'class' in v:
d['_' + k] = IData._factory(d.pop(k))
else:
d['_' + k] = d.pop(k)
return c(**d)
def __str__(self):
"""
Used for human string representation.
"""
return str(self._to_dict())
@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")
@default_property('num_of_blocks', doc="Total number of blocks")
@default_property('status', doc="Enumerated status")
@default_property('system_id', doc="System identifier")
@default_property("plugin_data", doc="Private plugin data")
class Disk(IData):
"""
Represents a disk.
"""
SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
# We use '-1' to indicate we failed to get the requested number.
# For example, when block found is undetectable, we use '-1' instead of
# confusing 0.
BLOCK_COUNT_NOT_FOUND = -1
BLOCK_SIZE_NOT_FOUND = -1
TYPE_UNKNOWN = 0
TYPE_OTHER = 1
TYPE_ATA = 3 # IDE disk which is seldomly used.
TYPE_SATA = 4
TYPE_SAS = 5
TYPE_FC = 6
TYPE_SOP = 7 # SCSI over PCIe(SSD)
TYPE_SCSI = 8
TYPE_LUN = 9 # Remote LUN was treated as a disk.
# Due to complexity of disk types, we are defining these beside DMTF
# standards:
TYPE_NL_SAS = 51 # Near-Line SAS==SATA disk + SAS port.
# in DMTF CIM 2.34.0+ CIM_DiskDrive['DiskType'], they also defined
# SSD and HYBRID disk type. We use it as fallback.
TYPE_HDD = 52 # Normal HDD
TYPE_SSD = 53 # Solid State Drive
TYPE_HYBRID = 54 # uses a combination of HDD and SSD
STATUS_UNKNOWN = 1 << 0
STATUS_OK = 1 << 1
STATUS_OTHER = 1 << 2
STATUS_PREDICTIVE_FAILURE = 1 << 3
STATUS_ERROR = 1 << 4
STATUS_REMOVED = 1 << 5
STATUS_STARTING = 1 << 6
STATUS_STOPPING = 1 << 7
STATUS_STOPPED = 1 << 8
STATUS_INITIALIZING = 1 << 9
STATUS_MAINTENANCE_MODE = 1 << 10
# In maintenance for bad sector scan, integrity check and etc
# It might be combined with STATUS_OK or
# STATUS_STOPPED for online maintenance or offline maintenance.
STATUS_SPARE_DISK = 1 << 11
# Indicate disk is a spare disk.
STATUS_RECONSTRUCT = 1 << 12
# Indicate disk is reconstructing data.
STATUS_FREE = 1 << 13
# New in version 1.2, indicate the whole disk is not holding any data or
# acting as a dedicate spare disk.
# This disk could be assigned as a dedicated spare disk or used for
# creating pool.
# If any spare disk(like those on NetApp ONTAP) does not require
# any explicit action when assigning to pool, it should be treated as
# free disk and marked as STATUS_FREE|STATUS_SPARE_DISK.
RPM_NO_SUPPORT = -2
RPM_UNKNOWN = -1
RPM_NON_ROTATING_MEDIUM = 0
RPM_ROTATING_UNKNOWN_SPEED = 1
LINK_TYPE_NO_SUPPORT = -2
LINK_TYPE_UNKNOWN = -1
LINK_TYPE_FC = 0
LINK_TYPE_SSA = 2
LINK_TYPE_SBP = 3
LINK_TYPE_SRP = 4
LINK_TYPE_ISCSI = 5
LINK_TYPE_SAS = 6
LINK_TYPE_ADT = 7
LINK_TYPE_ATA = 8
LINK_TYPE_USB = 9
LINK_TYPE_SOP = 10
LINK_TYPE_PCIE = 11
LED_STATUS_UNKNOWN = 1 << 0
LED_STATUS_IDENT_ON = 1 << 1
LED_STATUS_IDENT_OFF = 1 << 2
LED_STATUS_IDENT_UNKNOWN = 1 << 3
LED_STATUS_FAULT_ON = 1 << 4
LED_STATUS_FAULT_OFF = 1 << 5
LED_STATUS_FAULT_UNKNOWN = 1 << 6
LINK_SPEED_UNKNOWN = 0
HEALTH_STATUS_UNKNOWN = -1
HEALTH_STATUS_FAIL = 0
HEALTH_STATUS_WARN = 1
HEALTH_STATUS_GOOD = 2
def __init__(self, _id, _name, _disk_type, _block_size, _num_of_blocks,
_status, _system_id, _plugin_data=None, _vpd83='',
_location='', _rpm=RPM_NO_SUPPORT,
_link_type=LINK_TYPE_NO_SUPPORT):
self._id = _id
self._name = _name
self._disk_type = _disk_type
self._block_size = _block_size
self._num_of_blocks = _num_of_blocks
self._status = _status
self._system_id = _system_id
self._plugin_data = _plugin_data
if _vpd83 and not Volume.vpd83_verify(_vpd83):
raise LsmError(ErrorNumber.INVALID_ARGUMENT,
"Incorrect format of VPD 0x83 NAA(3) string: '%s', "
"expecting 32 or 16 lower case hex characters" %
_vpd83)
self._vpd83 = _vpd83
self._location = _location
self._rpm = _rpm
self._link_type = _link_type
@property
def size_bytes(self):
"""
Disk size in bytes.
"""
return self.block_size * self.num_of_blocks
@property
def vpd83(self):
"""
String. SCSI VPD83 ID. New in version 1.3.
Only available for DAS(direct attached storage) systems.
The VPD83 ID could be used in 'lsm.SCSI.disk_paths_of_vpd83()'
when physical disk is exposed to OS directly.
"""
if self._vpd83 == '':
raise LsmError(
ErrorNumber.NO_SUPPORT,
"Disk.vpd83 is not supported by current disk or plugin")
return self._vpd83
@property
def location(self):
"""
String. Disk location in storage topology. New in version 1.3.
"""
if self._location == '':
raise LsmError(ErrorNumber.NO_SUPPORT,
"Disk.location property is not supported by this "
"plugin yet")
return self._location
@property
def rpm(self):
"""
Integer. New in version 1.3.
Disk rotation speed - revolutions per minute(RPM):
-1 (LSM_DISK_RPM_UNKNOWN):
Unknown RPM
0 (LSM_DISK_RPM_NON_ROTATING_MEDIUM):
Non-rotating medium (e.g., SSD)
1 (LSM_DISK_RPM_ROTATING_UNKNOWN_SPEED):
Rotational disk with unknown speed
>1:
Normal rotational disk (e.g., HDD)
"""
if self._rpm == Disk.RPM_NO_SUPPORT:
raise LsmError(ErrorNumber.NO_SUPPORT,
"Disk.rpm is not supported by this plugin yet")
return self._rpm
@property
def link_type(self):
"""
Integer. New in version 1.3.
Link type, possible values are:
lsm.Disk.LINK_TYPE_UNKNOWN
Failed to detect link type
lsm.Disk.LINK_TYPE_FC
Fibre Channel
lsm.Disk.LINK_TYPE_SSA
Serial Storage Architecture, Old IBM tech.
lsm.Disk.LINK_TYPE_SBP
Serial Bus Protocol, used by IEEE 1394.
lsm.Disk.LINK_TYPE_SRP
SCSI RDMA Protocol
lsm.Disk.LINK_TYPE_ISCSI
Internet Small Computer System Interface
lsm.Disk.LINK_TYPE_SAS
Serial Attached SCSI
lsm.Disk.LINK_TYPE_ADT
Automation/Drive Interface Transport
Protocol, often used by Tape.
lsm.Disk.LINK_TYPE_ATA
PATA/IDE or SATA.
lsm.Disk.LINK_TYPE_USB
USB disk.
lsm.Disk.LINK_TYPE_SOP
SCSI over PCI-E
lsm.Disk.LINK_TYPE_PCIE
PCI-E, e.g. NVMe
"""
if self._link_type == Disk.LINK_TYPE_NO_SUPPORT:
raise LsmError(ErrorNumber.NO_SUPPORT,
"Disk.link_type is not supported by this plugin "
"yet")
return self._link_type
def __str__(self):
return self.name
# Lets do this once outside of the class to minimize the number of
# times it needs to be compiled.
_vol_regex_vpd83 = re.compile('(?:^6[0-9a-f]{31})|(?:^[235][0-9a-f]{15})$')
@default_property('id', doc="Unique identifier")
@default_property('name', doc="User given name")
@default_property('vpd83', doc="Vital product page 0x83 identifier")
@default_property('block_size', doc="Volume block size")
@default_property('num_of_blocks', doc="Number of blocks")
@default_property('admin_state', doc="Enabled or disabled by administrator")
@default_property('system_id', doc="System identifier")
@default_property('pool_id', doc="Pool identifier")
@default_property("plugin_data", doc="Private plugin data")
class Volume(IData):
"""
Represents a volume.
"""
SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']
# Replication types
REPLICATE_UNKNOWN = -1
REPLICATE_CLONE = 2
REPLICATE_COPY = 3
REPLICATE_MIRROR_SYNC = 4
REPLICATE_MIRROR_ASYNC = 5
# Provisioning types
PROVISION_UNKNOWN = -1
PROVISION_THIN = 1
PROVISION_FULL = 2
PROVISION_DEFAULT = 3
ADMIN_STATE_DISABLED = 0
ADMIN_STATE_ENABLED = 1
RAID_TYPE_UNKNOWN = -1
# The plugin failed to detect the volume's RAID type.
RAID_TYPE_RAID0 = 0
# Stripe
RAID_TYPE_RAID1 = 1
# Mirror for two disks. For 4 disks or more, they are RAID10.
RAID_TYPE_RAID3 = 3
# Byte-level striping with dedicated parity
RAID_TYPE_RAID4 = 4
# Block-level striping with dedicated parity
RAID_TYPE_RAID5 = 5
# Block-level striping with distributed parity
RAID_TYPE_RAID6 = 6
# Block-level striping with two distributed parities, aka, RAID-DP
RAID_TYPE_RAID10 = 10
# Stripe of mirrors
RAID_TYPE_RAID15 = 15
# Parity of mirrors
RAID_TYPE_RAID16 = 16
# Dual parity of mirrors
RAID_TYPE_RAID50 = 50
# Stripe of parities
RAID_TYPE_RAID60 = 60
# Stripe of dual parities
RAID_TYPE_RAID51 = 51
# Mirror of parities
RAID_TYPE_RAID61 = 61
# Mirror of dual parities
RAID_TYPE_JBOD = 20
# Just bunch of disks, no parity, no striping.
RAID_TYPE_MIXED = 21
# This volume contains multiple RAID settings.
RAID_TYPE_OTHER = 22
# Vendor specific RAID type
STRIP_SIZE_UNKNOWN = 0
DISK_COUNT_UNKNOWN = 0
MIN_IO_SIZE_UNKNOWN = 0
OPT_IO_SIZE_UNKNOWN = 0
VCR_STRIP_SIZE_DEFAULT = 0
WRITE_CACHE_POLICY_UNKNOWN = 1
WRITE_CACHE_POLICY_WRITE_BACK = 2
WRITE_CACHE_POLICY_AUTO = 3
WRITE_CACHE_POLICY_WRITE_THROUGH = 4
WRITE_CACHE_STATUS_UNKNOWN = 1
WRITE_CACHE_STATUS_WRITE_BACK = 2
WRITE_CACHE_STATUS_WRITE_THROUGH = 3
READ_CACHE_POLICY_UNKNOWN = 1
READ_CACHE_POLICY_ENABLED = 2
READ_CACHE_POLICY_DISABLED = 3
READ_CACHE_STATUS_UNKNOWN = 1
READ_CACHE_STATUS_ENABLED = 2
READ_CACHE_STATUS_DISABLED = 3
PHYSICAL_DISK_CACHE_UNKNOWN = 1
PHYSICAL_DISK_CACHE_ENABLED = 2
PHYSICAL_DISK_CACHE_DISABLED = 3
PHYSICAL_DISK_CACHE_USE_DISK_SETTING = 4
def __init__(self, _id, _name, _vpd83, _block_size, _num_of_blocks,
_admin_state, _system_id, _pool_id, _plugin_data=None):
self._id = _id # Identifier
self._name = _name # Human recognisable name
if _vpd83 and not Volume.vpd83_verify(_vpd83):
raise LsmError(ErrorNumber.INVALID_ARGUMENT,
"Incorrect format of VPD 0x83 NAA(3) string: '%s', "
"expecting 32 or 16 lower case hex characters" %
_vpd83)
self._vpd83 = _vpd83 # SCSI page 83 unique ID
self._block_size = _block_size # Block size
self._num_of_blocks = _num_of_blocks # Number of blocks
self._admin_state = _admin_state # enable or disabled by admin
self._system_id = _system_id # System id this volume belongs
self._pool_id = _pool_id # Pool id this volume belongs
self._plugin_data = _plugin_data
@property
def size_bytes(self):
"""
Volume size in bytes.
"""
return self.block_size * self.num_of_blocks
def __str__(self):
return self.name
@staticmethod
def vpd83_verify(vpd):
"""
Returns True if string is valid vpd 0x83 representation
"""
if vpd and _vol_regex_vpd83.match(vpd):
return True
return False
@default_property('id', doc="Unique identifier")
@default_property('name', doc="User defined system name")
@default_property('status', doc="Enumerated status of system")
@default_property('status_info', doc="Detail status information of system")
@default_property("plugin_data", doc="Private plugin data")
class System(IData):
STATUS_UNKNOWN = 1 << 0
STATUS_OK = 1 << 1
STATUS_ERROR = 1 << 2
STATUS_DEGRADED = 1 << 3
STATUS_PREDICTIVE_FAILURE = 1 << 4
STATUS_OTHER = 1 << 5
MODE_NO_SUPPORT = -2
MODE_UNKNOWN = -1
MODE_HARDWARE_RAID = 0
MODE_HBA = 1
READ_CACHE_PCT_NO_SUPPORT = -2
READ_CACHE_PCT_UNKNOWN = -1
def __init__(self, _id, _name, _status, _status_info, _plugin_data=None,
_fw_version='', _mode=None, _read_cache_pct=None):
self._id = _id
self._name = _name
self._status = _status
self._status_info = _status_info
self._plugin_data = _plugin_data
self._fw_version = _fw_version
if _read_cache_pct is None:
self._read_cache_pct = System.READ_CACHE_PCT_NO_SUPPORT
else:
self._read_cache_pct = _read_cache_pct
if _mode is None:
self._mode = System.MODE_NO_SUPPORT
else:
self._mode = _mode
@property
def fw_version(self):
"""
String. Firmware version string. New in version 1.3.
On some system, it might contain multiple version strings, example:
"Package: 23.32.0-0009, FW: 3.440.05-3712"
"""
if self._fw_version == '':
raise LsmError(ErrorNumber.NO_SUPPORT,
"System.fw_version() is not supported by this "
"plugin yet")
return self._fw_version
@property
def mode(self):
"""
Integer(enumerated value). System mode. New in version 1.3.
Only available for HW RAID systems at this time.
Possible values:
* lsm.System.MODE_HARDWARE_RAID
The logical volume(aka, RAIDed virtual disk) can be exposed
to OS while hardware RAID card is handling the RAID
algorithm. Physical disk can not be exposed to OS directly.
* lsm.System.MODE_HBA
The physical disks can be exposed to OS directly.
SCSI enclosure service might be exposed to OS also.
"""
if self._mode == System.MODE_NO_SUPPORT:
raise LsmError(ErrorNumber.NO_SUPPORT,
"System.mode is not supported by this plugin yet")
return self._mode
@property
def read_cache_pct(self):
"""
Integer. Read cache percentage. New in version 1.3.
Possible values:
* 0-100
The read cache percentage. The write cache percentage will
then be 100 - read_cache_pct
"""
if self._read_cache_pct == System.READ_CACHE_PCT_NO_SUPPORT:
raise LsmError(ErrorNumber.NO_SUPPORT,
"System.read_cache_pct is not supported by this "
"plugin yet")
return self._read_cache_pct
@default_property('id', doc="Unique identifier")
@default_property('name', doc="User supplied name")
@default_property('total_space', doc="Total space in bytes")
@default_property('free_space', doc="Free space in bytes")
@default_property('status', doc="Enumerated status")
@default_property('status_info', doc="Text explaining status")
@default_property('system_id', doc="System identifier")
@default_property("plugin_data", doc="Plug-in private data")
@default_property("element_type", doc="What pool can be used for")
@default_property("unsupported_actions",
doc="What cannot be done with this pool")
class Pool(IData):
"""
Pool specific information
"""
SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
TOTAL_SPACE_NOT_FOUND = -1
FREE_SPACE_NOT_FOUND = -1
# Element Type indicate what kind of element could this pool create:
# * Another Pool
# * Volume (aka, LUN)
# * System Reserved Pool.
ELEMENT_TYPE_POOL = 1 << 1
ELEMENT_TYPE_VOLUME = 1 << 2
ELEMENT_TYPE_FS = 1 << 3
ELEMENT_TYPE_DELTA = 1 << 4
ELEMENT_TYPE_VOLUME_FULL = 1 << 5
ELEMENT_TYPE_VOLUME_THIN = 1 << 6
ELEMENT_TYPE_SYS_RESERVED = 1 << 10 # Reserved for system use
# Unsupported actions, what pool cannot be used for
UNSUPPORTED_VOLUME_GROW = 1 << 0
UNSUPPORTED_VOLUME_SHRINK = 1 << 1
# Pool status could be any combination of these status.
STATUS_UNKNOWN = 1 << 0
STATUS_OK = 1 << 1
STATUS_OTHER = 1 << 2
STATUS_DEGRADED = 1 << 4
STATUS_ERROR = 1 << 5
STATUS_STOPPED = 1 << 9
STATUS_RECONSTRUCTING = 1 << 12
STATUS_VERIFYING = 1 << 13
STATUS_INITIALIZING = 1 << 14
STATUS_GROWING = 1 << 15
MEMBER_TYPE_UNKNOWN = 0
MEMBER_TYPE_OTHER = 1
MEMBER_TYPE_DISK = 2
MEMBER_TYPE_POOL = 3
def __init__(self, _id, _name, _element_type, _unsupported_actions,
_total_space, _free_space,
_status, _status_info, _system_id, _plugin_data=None):
self._id = _id # Identifier
self._name = _name # Human recognisable name
self._element_type = _element_type # What pool can be used to create
# What pool cannot be used for
self._unsupported_actions = _unsupported_actions
self._total_space = _total_space # Total size
self._free_space = _free_space # Free space available
self._status = _status # Status of pool.
self._status_info = _status_info # Additional status text of pool
self._system_id = _system_id # System id this pool belongs
self._plugin_data = _plugin_data # Plugin private data
@default_property('id', doc="Unique identifier")
@default_property('name', doc="File system name")
@default_property('total_space', doc="Total space in bytes")
@default_property('free_space', doc="Free space available")
@default_property('pool_id', doc="What pool the file system resides on")
@default_property('system_id', doc="System ID")
@default_property("plugin_data", doc="Private plugin data")
class FileSystem(IData):
SUPPORTED_SEARCH_KEYS = ['id', 'system_id', 'pool_id']
def __init__(self, _id, _name, _total_space, _free_space, _pool_id,
_system_id, _plugin_data=None):
self._id = _id
self._name = _name
self._total_space = _total_space
self._free_space = _free_space
self._pool_id = _pool_id
self._system_id = _system_id
self._plugin_data = _plugin_data
@default_property('id', doc="Unique identifier")
@default_property('name', doc="Snapshot name")
@default_property('ts', doc="Time stamp the snapshot was created")
@default_property("plugin_data", doc="Private plugin data")
class FsSnapshot(IData):
def __init__(self, _id, _name, _ts, _plugin_data=None):
self._id = _id
self._name = _name
self._ts = int(_ts)
self._plugin_data = _plugin_data
@default_property('id', doc="Unique identifier")
@default_property('fs_id', doc="Filesystem that is exported")
@default_property('export_path', doc="Export path")
@default_property('auth', doc="Authentication type")
@default_property('root', doc="List of hosts with no_root_squash")
@default_property('rw', doc="List of hosts with Read & Write privileges")
@default_property('ro', doc="List of hosts with Read only privileges")
@default_property('anonuid', doc="UID for anonymous user id")
@default_property('anongid', doc="GID for anonymous group id")
@default_property('options', doc="String containing advanced options")
@default_property('plugin_data', doc="Plugin private data")
class NfsExport(IData):
SUPPORTED_SEARCH_KEYS = ['id', 'fs_id']
ANON_UID_GID_NA = -1
ANON_UID_GID_ERROR = -2
def __init__(self, _id, _fs_id, _export_path, _auth, _root, _rw, _ro,
_anonuid, _anongid, _options, _plugin_data=None):
assert (_fs_id is not None)
assert (_export_path is not None)
self._id = _id
self._fs_id = _fs_id # File system exported
self._export_path = _export_path # Export path
self._auth = _auth # Authentication type
self._root = _root # List of hosts with no_root_squash
self._rw = _rw # List of hosts with read/write
self._ro = _ro # List of hosts with read/only
self._anonuid = _anonuid # uid for anonymous user id
self._anongid = _anongid # gid for anonymous group id
self._options = _options # NFS options
self._plugin_data = _plugin_data
@default_property('src_block', doc="Source logical block address")
@default_property('dest_block', doc="Destination logical block address")
@default_property('block_count', doc="Number of blocks")
class BlockRange(IData):
def __init__(self, _src_block, _dest_block, _block_count):
self._src_block = _src_block
self._dest_block = _dest_block
self._block_count = _block_count
@default_property('id', doc="Unique instance identifier")
@default_property('name', doc="Access group name")
@default_property('init_ids', doc="List of initiator IDs")
@default_property('init_type', doc="Initiator type")
@default_property('system_id', doc="System identifier")
@default_property('plugin_data', doc="Plugin private data")
class AccessGroup(IData):
SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
INIT_TYPE_UNKNOWN = 0
INIT_TYPE_OTHER = 1
INIT_TYPE_WWPN = 2
INIT_TYPE_ISCSI_IQN = 5
INIT_TYPE_ISCSI_WWPN_MIXED = 7
def __init__(self, _id, _name, _init_ids, _init_type, _system_id,
_plugin_data=None):
self._id = _id
self._name = _name # AccessGroup name
# A list of Initiator ID strings.
self._init_ids = AccessGroup._standardize_init_list(_init_ids)
self._init_type = _init_type
self._system_id = _system_id # System id this group belongs
self._plugin_data = _plugin_data
@staticmethod
def _standardize_init_list(init_ids):
rc = []
for i in init_ids:
valid, init_type, init_id = AccessGroup.initiator_id_verify(i)
if valid:
rc.append(init_id)
else:
raise LsmError(ErrorNumber.INVALID_ARGUMENT,
"Invalid initiator ID %s" % i)
return rc
_regex_wwpn = re.compile(r"""
^(0x|0X)?([0-9A-Fa-f]{2})
(([\.:\-])?[0-9A-Fa-f]{2}){7}$
""", re.X)
@staticmethod
def initiator_id_verify(init_id, init_type=None, raise_exception=False):
"""
Public method which can be used to verify an initiator id
:param init_id:
:param init_type:
:param raise_exception: Will throw a LsmError INVALID_ARGUMENT if
not a valid initiator address
:return:(Bool, init_type, init_id) Note: init_id will be returned in
normalized format if it's a WWPN
"""
if init_id.startswith('iqn') or init_id.startswith('eui') or\
init_id.startswith('naa'):
if init_type is None or \
init_type == AccessGroup.INIT_TYPE_ISCSI_IQN:
return True, AccessGroup.INIT_TYPE_ISCSI_IQN, init_id
if AccessGroup._regex_wwpn.match(str(init_id)):
if init_type is None or \
init_type == AccessGroup.INIT_TYPE_WWPN:
return (True, AccessGroup.INIT_TYPE_WWPN,
AccessGroup._wwpn_to_lsm_type(init_id))
if raise_exception:
raise LsmError(ErrorNumber.INVALID_ARGUMENT,
"Initiator id '%s' is invalid" % init_id)
return False, None, None
@staticmethod
def _wwpn_to_lsm_type(wwpn, raise_error=True):
"""
Convert provided WWPN string into LSM standard one:
LSM WWPN format:
^(?:[0-9a-f]{2}:){7}[0-9a-f]{2}$
LSM WWPN Example:
10:00:00:00:c9:95:2f:de
Acceptable WWPN format is:
^[0x|0X]{0,1}(:?[0-9A-Fa-f]{2}[\.\-:]{0,1}){7}[0-9A-Fa-f]{2}$
Acceptable WWPN example:
10:00:00:00:c9:95:2f:de
10:00:00:00:C9:95:2F:DE
10-00-00-00-C9-95-2F-DE
10-00-00-00-c9-95-2f-de
10.00.00.00.C9.95.2F.DE
10.00.00.00.c9.95.2f.de
0x10000000c9952fde
0X10000000C9952FDE
10000000c9952fde
10000000C9952FDE
Return the LSM WWPN
Return None if raise_error is False and not a valid WWPN.
"""
if AccessGroup._regex_wwpn.match(str(wwpn)):
s = str(wwpn)
s = s.lower()
s = re.sub(r'0x', '', s)
s = re.sub(r'[^0-9a-f]', '', s)
s = ":".join(re.findall(r'..', s))
return s
if raise_error:
raise LsmError(ErrorNumber.INVALID_ARGUMENT,
"Invalid WWPN Initiator: %s" % wwpn)
return None
@default_property('id', doc="Unique instance identifier")
@default_property('port_type', doc="Target port type")
@default_property('service_address', doc="Target port service address")
@default_property('network_address', doc="Target port network address")
@default_property('physical_address', doc="Target port physical address")
@default_property('physical_name', doc="Target port physical port name")
@default_property('system_id', doc="System identifier")
@default_property('plugin_data', doc="Plugin private data")
class TargetPort(IData):
SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
TYPE_OTHER = 1
TYPE_FC = 2
TYPE_FCOE = 3
TYPE_ISCSI = 4
def __init__(self, _id, _port_type, _service_address,
_network_address, _physical_address, _physical_name,
_system_id, _plugin_data=None):
self._id = _id
self._port_type = _port_type
self._service_address = _service_address
# service_address:
# The address used by upper layer like FC and iSCSI:
# FC and FCoE: WWPN
# iSCSI: IQN
# String. Lower case, split with : every two digits if WWPN.
self._network_address = _network_address
# network_address:
# The address used by network layer like FC and TCP/IP:
# FC/FCoE: WWPN
# iSCSI: IPv4:Port
# [IPv6]:Port
# String. Lower case, split with : every two digits if WWPN.
self._physical_address = _physical_address
# physical_address:
# The address used by physical layer like FC-0 and MAC:
# FC: WWPN
# FCoE: WWPN
# iSCSI: MAC
# String. Lower case, split with : every two digits.
self._physical_name = _physical_name
# physical_name
# The name of physical port. Administrator could use this name to
# locate the port on storage system.
# String.
self._system_id = _system_id
self._plugin_data = _plugin_data
class Capabilities(IData):
UNSUPPORTED = 0
SUPPORTED = 1
_NUM = 512 # Indicate the maximum capability integer
_CAP_NUM_BEGIN = 20 # Indicate the first capability integer
# Block operations
VOLUMES = 20
VOLUME_CREATE = 21
VOLUME_RESIZE = 22
VOLUME_REPLICATE = 23
VOLUME_REPLICATE_CLONE = 24
VOLUME_REPLICATE_COPY = 25
VOLUME_REPLICATE_MIRROR_ASYNC = 26
VOLUME_REPLICATE_MIRROR_SYNC = 27
VOLUME_COPY_RANGE_BLOCK_SIZE = 28
VOLUME_COPY_RANGE = 29
VOLUME_COPY_RANGE_CLONE = 30
VOLUME_COPY_RANGE_COPY = 31
VOLUME_DELETE = 33
VOLUME_ENABLE = 34
VOLUME_DISABLE = 35
VOLUME_MASK = 36
VOLUME_UNMASK = 37
ACCESS_GROUPS = 38
ACCESS_GROUP_CREATE_WWPN = 39
ACCESS_GROUP_DELETE = 40
ACCESS_GROUP_INITIATOR_ADD_WWPN = 41
# For empty access group, this indicate it can add WWPN into it.
ACCESS_GROUP_INITIATOR_DELETE = 42
VOLUMES_ACCESSIBLE_BY_ACCESS_GROUP = 43
ACCESS_GROUPS_GRANTED_TO_VOLUME = 44
VOLUME_CHILD_DEPENDENCY = 45
VOLUME_CHILD_DEPENDENCY_RM = 46
ACCESS_GROUP_CREATE_ISCSI_IQN = 47
ACCESS_GROUP_INITIATOR_ADD_ISCSI_IQN = 48
# For empty access group, this indicate it can add iSCSI IQN into it.
VOLUME_ISCSI_CHAP_AUTHENTICATION = 53
VOLUME_RAID_INFO = 54
VOLUME_THIN = 55
BATTERIES = 56
VOLUME_CACHE_INFO = 57
VOLUME_PHYSICAL_DISK_CACHE_UPDATE = 58
VOLUME_PHYSICAL_DISK_CACHE_UPDATE_SYSTEM_LEVEL = 59
VOLUME_WRITE_CACHE_POLICY_UPDATE_WRITE_BACK = 60
VOLUME_WRITE_CACHE_POLICY_UPDATE_AUTO = 61
VOLUME_WRITE_CACHE_POLICY_UPDATE_WRITE_THROUGH = 62
VOLUME_WRITE_CACHE_POLICY_UPDATE_IMPACT_READ = 63
VOLUME_WRITE_CACHE_POLICY_UPDATE_WB_IMPACT_OTHER = 64
VOLUME_READ_CACHE_POLICY_UPDATE = 65
VOLUME_READ_CACHE_POLICY_UPDATE_IMPACT_WRITE = 66
# File system
FS = 100
FS_DELETE = 101
FS_RESIZE = 102
FS_CREATE = 103
FS_CLONE = 104
FILE_CLONE = 105
FS_SNAPSHOTS = 106
FS_SNAPSHOT_CREATE = 107
FS_SNAPSHOT_DELETE = 109
FS_SNAPSHOT_RESTORE = 110
FS_SNAPSHOT_RESTORE_SPECIFIC_FILES = 111
FS_CHILD_DEPENDENCY = 112
FS_CHILD_DEPENDENCY_RM = 113
FS_CHILD_DEPENDENCY_RM_SPECIFIC_FILES = 114
# NFS
EXPORT_AUTH = 120
EXPORTS = 121
EXPORT_FS = 122
EXPORT_REMOVE = 123
EXPORT_CUSTOM_PATH = 124
SYS_READ_CACHE_PCT_UPDATE = 158
SYS_READ_CACHE_PCT_GET = 159
SYS_FW_VERSION_GET = 160
SYS_MODE_GET = 161
DISK_LOCATION = 163
DISK_RPM = 164
DISK_LINK_TYPE = 165
VOLUME_LED = 171
POOLS_QUICK_SEARCH = 210
VOLUMES_QUICK_SEARCH = 211
DISKS_QUICK_SEARCH = 212
ACCESS_GROUPS_QUICK_SEARCH = 213
FS_QUICK_SEARCH = 214
NFS_EXPORTS_QUICK_SEARCH = 215
TARGET_PORTS = 216
TARGET_PORTS_QUICK_SEARCH = 217
DISKS = 220
POOL_MEMBER_INFO = 221
VOLUME_RAID_CREATE = 222
DISK_VPD83_GET = 223
def _to_dict(self):
return {'class': self.__class__.__name__,
'cap': ''.join(['%02x' % b for b in self._cap])}
def __init__(self, _cap=None):
if _cap is not None:
self._cap = bytearray(binascii.unhexlify(_cap))
else:
self._cap = bytearray(Capabilities._NUM)
def supported(self, capability):
return self.get(capability) == Capabilities.SUPPORTED
def get(self, capability):
if capability >= len(self._cap):
return Capabilities.UNSUPPORTED
return self._cap[capability]
@staticmethod
def _lsm_cap_to_str_dict():
"""
Return a dict containing all valid capability:
integer => string name
"""
lsm_cap_to_str_conv = dict()
for c_str, c_int in list(Capabilities.__dict__.items()):
if isinstance(c_str, six.string_types) and type(c_int) == int and \
c_str[0] != '_' and \
Capabilities._CAP_NUM_BEGIN <= c_int <= Capabilities._NUM:
lsm_cap_to_str_conv[c_int] = c_str
return lsm_cap_to_str_conv
def get_supported(self, all_cap=False):
"""
Returns a hash of the supported capabilities in the form
constant, name
"""
all_caps = Capabilities._lsm_cap_to_str_dict()
if all_cap:
return all_caps
rc = {}
for i in list(all_caps.keys()):
if self._cap[i] == Capabilities.SUPPORTED:
if i in all_caps:
rc[i] = all_caps[i]
return rc
def set(self, capability, value=SUPPORTED):
self._cap[capability] = value
def enable_all(self):
for i in range(len(self._cap)):
self._cap[i] = Capabilities.SUPPORTED
@default_property('id', doc="Unique identifier")
@default_property('name', doc="User given name")
@default_property('type', doc="Cache hardware type")
@default_property('status', doc='Battery status')
@default_property('system_id', doc="System identifier")
@default_property("plugin_data", doc="Private plugin data")
class Battery(IData):
SUPPORTED_SEARCH_KEYS = ['id', 'system_id']
TYPE_UNKNOWN = 1
TYPE_OTHER = 2
TYPE_CHEMICAL = 3
TYPE_CAPACITOR = 4
STATUS_UNKNOWN = 1 << 0
STATUS_OTHER = 1 << 1
STATUS_OK = 1 << 2
STATUS_DISCHARGING = 1 << 3
STATUS_CHARGING = 1 << 4
STATUS_LEARNING = 1 << 5
STATUS_DEGRADED = 1 << 6
STATUS_ERROR = 1 << 7
def __init__(self, _id, _name, _type, _status, _system_id,
_plugin_data=None):
self._id = _id
self._name = _name
self._type = _type
self._status = _status
self._system_id = _system_id
self._plugin_data = _plugin_data
if __name__ == '__main__':
# TODO Need some unit tests that encode/decode all the types with nested
pass