pystorcli2

StorCLI python module version 2.x

 1# -*- coding: utf-8 -*-
 2
 3# Copyright (c) 2018, Martin Dojcak <martin@dojcak.sk>
 4# Copyright (c) 2022, Rafael Leira & Naudit HPCN S.L. <rafael.leira@naudit.es>
 5# See LICENSE for details.
 6
 7'''StorCLI python module version 2.x
 8'''
 9
10from .version import __version__
11from .storcli import StorCLI
12from .controller import Controller, Controllers
13from .enclosure import Enclosure, Enclosures
14from .drive import DriveState, Drive, Drives
15from .virtualdrive import VirtualDrive, VirtualDrives
16
17__all__ = ['__version__', 'StorCLI', 'Controller', 'Controllers',
18           'Enclosure', 'Enclosures', 'DriveState', 'Drive', 'Drives', 'VirtualDrive', 'VirtualDrives']
__version__ = '0.6.4'
class StorCLI:
 29class StorCLI(object):
 30    """StorCLI command line wrapper
 31
 32    Instance of this class is storcli command line wrapper
 33
 34    Args:
 35        binary (str): storcli binary or full path to the binary
 36
 37    Properties:
 38        cache_enable (boolean): enable disable resposne cache (also setter)
 39        cache (dict): get / set raw cache content
 40
 41    Methods:
 42        run (dict): output data from command line
 43        check_response_status (): check ouput command line status from storcli
 44        clear_cache (): purge cache
 45
 46    TODO:
 47        * implement TTL for cache
 48
 49    """
 50    __singleton_instance = None
 51    __cache_lock = threading.Lock()
 52    __cache_enabled = False
 53    __response_cache: Dict[str, Any] = {}
 54    __cmdrunner = cmdRunner.CMDRunner()
 55
 56    def __new__(cls, *args, **kwargs):
 57        """Thread safe singleton
 58        """
 59        global _SINGLETON_STORCLI_MODULE_LOCK
 60        with _SINGLETON_STORCLI_MODULE_LOCK:
 61            if _SINGLETON_STORCLI_MODULE_ENABLE:
 62                if StorCLI.__singleton_instance is None:
 63                    StorCLI.__singleton_instance = super(
 64                        StorCLI, cls).__new__(cls)
 65                return StorCLI.__singleton_instance
 66            else:
 67                return super(StorCLI, cls).__new__(cls)
 68
 69    def __init__(self, binary='storcli64', cmdrunner: Optional[cmdRunner.CMDRunner] = None):
 70        """Constructor - create StorCLI object wrapper
 71
 72        Args:
 73            binary (str): storcli binary or full path to the binary
 74        """
 75
 76        if cmdrunner is not None:
 77            self._storcli = cmdrunner.binaryCheck(binary)
 78        else:
 79            self._storcli = self.__cmdrunner.binaryCheck(binary)
 80
 81        if cmdrunner is not None:
 82            self.__cmdrunner = cmdrunner
 83
 84        if not _SINGLETON_STORCLI_MODULE_ENABLE:
 85            self.__cache_lock = threading.Lock()
 86
 87    def set_cmdrunner(self, cmdrunner: cmdRunner.CMDRunner):
 88        """
 89        Set command runner object.
 90        This is only useful for testing.
 91        """
 92        self.__cmdrunner = cmdrunner
 93
 94    @property
 95    def cache_enable(self):
 96        """Enable/Disable resposne cache (atomic)
 97
 98        Returns:
 99            bool: true/false
100        """
101
102        return self.__cache_enabled
103
104    @cache_enable.setter
105    def cache_enable(self, value):
106        with self.__cache_lock:
107            self.__cache_enabled = value
108
109    def clear_cache(self):
110        """Clear cache (atomic)
111        """
112        with self.__cache_lock:
113            self.__response_cache = {}
114
115    @property
116    def cache(self):
117        """Get/Set raw cache
118
119        Args:
120            (dict): raw cache
121
122        Returns:
123            (dict): cache
124        """
125        return self.__response_cache
126
127    @cache.setter
128    def cache(self, value):
129        with self.__cache_lock:
130            self.__response_cache = value
131
132    @staticmethod
133    def check_response_status(cmd: List[str], out: Dict[str, Dict[int, Dict[str, Any]]], allow_error_codes: List[StorcliError]) -> bool:
134        """Check ouput command line status from storcli.
135
136        Args:
137            cmd (list of str): full command line
138            out (dict): output from command line
139            raise_on_error (bool): raise exception on error (default: True)
140
141        Returns:
142            bool: True if no error found in output. False if error found but allowed. Raise exception otherwise.
143
144        Raises:
145            StorCliCmdError: if error found in output and not allowed
146            StorCliCmdErrorCode: if error code found in output and not allowed
147        """
148        retcode = True
149        cmd_status = common.response_cmd(out)
150        if cmd_status['Status'] == 'Failure':
151            if 'Detailed Status' in cmd_status:
152                allowed_errors = True
153                # Check if the error code is allowed
154                for error in cmd_status['Detailed Status']:
155
156                    if 'ErrCd' in error:
157                        if StorcliError.get(error['ErrCd']) not in allow_error_codes:
158                            allowed_errors = False
159                    else:
160                        allowed_errors = False
161
162                    retcode = False
163                    if not allowed_errors:
164                        raise exc.StorCliCmdErrorCode(
165                            cmd, StorcliError.get(error['ErrCd']))
166
167                # Otherwise, raise an exception
168                if not allowed_errors:
169                    raise exc.StorCliCmdError(
170                        cmd, "{0}".format(cmd_status['Detailed Status']))
171            else:
172                # Try to get the error code using description
173                if 'Description' in cmd_status:
174                    error_code = StorcliError.get(cmd_status['Description'])
175
176                    if error_code != StorcliError.INVALID_STATUS:
177                        if error_code not in allow_error_codes:
178                            raise exc.StorCliCmdErrorCode(cmd, error_code)
179                        else:
180                            return False
181
182                raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))
183
184        return retcode
185
186    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, allow_error_codes: List[StorcliError] = [], **kwargs):
187        """Execute storcli command line with arguments.
188
189        Run command line and check output for errors.
190
191        Args:
192            args (list of str): cmd line arguments (without binary)
193            stdout (fd): controll subprocess stdout fd
194            stderr (fd): controll subporcess stderr fd
195            allow_error_codes (list of StorcliErrors): list of error codes to allow
196            **kwargs: arguments to subprocess run
197
198        Returns:
199            dict: output data from command line
200
201        Raises:
202            exc.StorCliCmdError
203            exc.StorCliCmdErrorCode
204            exc.StorCliRunTimeError
205            exc.StorCliRunTimeout
206        """
207        cmd = [self._storcli]
208        cmd.extend(args)
209        # output in JSON format
210        cmd.append('J')
211        cmd_cache_key = ''.join(cmd)
212
213        if self.cache_enable:
214            if cmd_cache_key in self.__response_cache:
215                return self.__response_cache[cmd_cache_key]
216
217        with self.__cache_lock:
218            try:
219                ret = self.__cmdrunner.run(
220                    args=cmd, universal_newlines=True, **kwargs)
221                try:
222                    ret_json = json.loads(ret.stdout)
223                    self.check_response_status(
224                        cmd, ret_json, allow_error_codes)
225                    if ret.returncode != 0:
226                        raise subprocess.CalledProcessError(
227                            ret.returncode, cmd, ret.stdout, ret.stderr)
228                    if self.cache_enable:
229                        self.__response_cache[cmd_cache_key] = ret_json
230                    return ret_json
231                except json.JSONDecodeError as err:
232                    # legacy handler (Ralequi: I don't know if this is still needed or what exactly it does)
233                    output = re.search('(^.*)Storage.*Command.*$',
234                                       ret.stdout, re.MULTILINE | re.DOTALL)
235                    if output:
236                        raise exc.StorCliCmdError(cmd, output.group(1))
237
238                    # Check if we can still parse the output
239                    parsed = {}
240                    for line in ret.stdout.splitlines():
241                        if '=' in line:
242                            key, value = line.split('=', 1)
243                            parsed[key.strip()] = value.strip()
244
245                    if 'Status' in parsed:
246                        return parsed
247                    else:
248                        raise exc.StorCliCmdError(cmd, str(err))
249
250            except subprocess.TimeoutExpired as err:
251                raise exc.StorCliRunTimeout(err)
252            except subprocess.SubprocessError as err:
253                raise exc.StorCliRunTimeError(err)
254
255    # Singleton stuff
256    @staticmethod
257    def __set_singleton(value):
258        global _SINGLETON_STORCLI_MODULE_ENABLE
259        global _SINGLETON_STORCLI_MODULE_LOCK
260        with _SINGLETON_STORCLI_MODULE_LOCK:
261            _SINGLETON_STORCLI_MODULE_ENABLE = value
262
263    @staticmethod
264    def enable_singleton():
265        """Enable StorCLI to be singleton on module level
266
267        Use StorCLI singleton across all objects. All pystorcli
268        class instances use their own StorCLI object. With enabled cache
269        we can speedup for example metric lookups.
270
271        """
272        StorCLI.__set_singleton(True)
273
274    @staticmethod
275    def disable_singleton():
276        """Disable StoreCLI class as signleton
277        """
278        StorCLI.__set_singleton(False)
279
280    @staticmethod
281    def is_singleton() -> bool:
282        """Check if singleton is enabled
283        """
284        return _SINGLETON_STORCLI_MODULE_ENABLE
285
286    @property
287    def full_version(self) -> str:
288        """Get storcli version as storcli returns
289        """
290        out = self.run(['show'])
291        version = common.response_cmd(out)['CLI Version']
292
293        return version
294
295    @property
296    def version(self) -> str:
297        """Get storcli version in a cleaner way
298        """
299        import re
300
301        # Remove duplicated 0s
302        first_clean = re.sub('0+', '0', self.full_version.split(' ')[0])
303
304        # Remove leading 0s
305        second_clean = re.sub('^0+', '', first_clean)
306
307        return second_clean
308
309    @property
310    def controllers(self) -> 'pystorcli2.controller.Controllers':
311        """Get list of controllers
312        """
313        from . import Controllers
314
315        return Controllers(binary=self._storcli)

StorCLI command line wrapper

Instance of this class is storcli command line wrapper

Args: binary (str): storcli binary or full path to the binary

Properties: cache_enable (boolean): enable disable resposne cache (also setter) cache (dict): get / set raw cache content

Methods: run (dict): output data from command line check_response_status (): check ouput command line status from storcli clear_cache (): purge cache

TODO: * implement TTL for cache

StorCLI( binary='storcli64', cmdrunner: Optional[pystorcli2.cmdRunner.CMDRunner] = None)
69    def __init__(self, binary='storcli64', cmdrunner: Optional[cmdRunner.CMDRunner] = None):
70        """Constructor - create StorCLI object wrapper
71
72        Args:
73            binary (str): storcli binary or full path to the binary
74        """
75
76        if cmdrunner is not None:
77            self._storcli = cmdrunner.binaryCheck(binary)
78        else:
79            self._storcli = self.__cmdrunner.binaryCheck(binary)
80
81        if cmdrunner is not None:
82            self.__cmdrunner = cmdrunner
83
84        if not _SINGLETON_STORCLI_MODULE_ENABLE:
85            self.__cache_lock = threading.Lock()

Constructor - create StorCLI object wrapper

Args: binary (str): storcli binary or full path to the binary

def set_cmdrunner(self, cmdrunner: pystorcli2.cmdRunner.CMDRunner):
87    def set_cmdrunner(self, cmdrunner: cmdRunner.CMDRunner):
88        """
89        Set command runner object.
90        This is only useful for testing.
91        """
92        self.__cmdrunner = cmdrunner

Set command runner object. This is only useful for testing.

cache_enable

Enable/Disable resposne cache (atomic)

Returns: bool: true/false

def clear_cache(self):
109    def clear_cache(self):
110        """Clear cache (atomic)
111        """
112        with self.__cache_lock:
113            self.__response_cache = {}

Clear cache (atomic)

cache

Get/Set raw cache

Args: (dict): raw cache

Returns: (dict): cache

@staticmethod
def check_response_status( cmd: List[str], out: Dict[str, Dict[int, Dict[str, Any]]], allow_error_codes: List[pystorcli2.errors.StorcliError]) -> bool:
132    @staticmethod
133    def check_response_status(cmd: List[str], out: Dict[str, Dict[int, Dict[str, Any]]], allow_error_codes: List[StorcliError]) -> bool:
134        """Check ouput command line status from storcli.
135
136        Args:
137            cmd (list of str): full command line
138            out (dict): output from command line
139            raise_on_error (bool): raise exception on error (default: True)
140
141        Returns:
142            bool: True if no error found in output. False if error found but allowed. Raise exception otherwise.
143
144        Raises:
145            StorCliCmdError: if error found in output and not allowed
146            StorCliCmdErrorCode: if error code found in output and not allowed
147        """
148        retcode = True
149        cmd_status = common.response_cmd(out)
150        if cmd_status['Status'] == 'Failure':
151            if 'Detailed Status' in cmd_status:
152                allowed_errors = True
153                # Check if the error code is allowed
154                for error in cmd_status['Detailed Status']:
155
156                    if 'ErrCd' in error:
157                        if StorcliError.get(error['ErrCd']) not in allow_error_codes:
158                            allowed_errors = False
159                    else:
160                        allowed_errors = False
161
162                    retcode = False
163                    if not allowed_errors:
164                        raise exc.StorCliCmdErrorCode(
165                            cmd, StorcliError.get(error['ErrCd']))
166
167                # Otherwise, raise an exception
168                if not allowed_errors:
169                    raise exc.StorCliCmdError(
170                        cmd, "{0}".format(cmd_status['Detailed Status']))
171            else:
172                # Try to get the error code using description
173                if 'Description' in cmd_status:
174                    error_code = StorcliError.get(cmd_status['Description'])
175
176                    if error_code != StorcliError.INVALID_STATUS:
177                        if error_code not in allow_error_codes:
178                            raise exc.StorCliCmdErrorCode(cmd, error_code)
179                        else:
180                            return False
181
182                raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))
183
184        return retcode

Check ouput command line status from storcli.

Args: cmd (list of str): full command line out (dict): output from command line raise_on_error (bool): raise exception on error (default: True)

Returns: bool: True if no error found in output. False if error found but allowed. Raise exception otherwise.

Raises: StorCliCmdError: if error found in output and not allowed StorCliCmdErrorCode: if error code found in output and not allowed

def run( self, args, stdout=-1, stderr=-1, allow_error_codes: List[pystorcli2.errors.StorcliError] = [], **kwargs):
186    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, allow_error_codes: List[StorcliError] = [], **kwargs):
187        """Execute storcli command line with arguments.
188
189        Run command line and check output for errors.
190
191        Args:
192            args (list of str): cmd line arguments (without binary)
193            stdout (fd): controll subprocess stdout fd
194            stderr (fd): controll subporcess stderr fd
195            allow_error_codes (list of StorcliErrors): list of error codes to allow
196            **kwargs: arguments to subprocess run
197
198        Returns:
199            dict: output data from command line
200
201        Raises:
202            exc.StorCliCmdError
203            exc.StorCliCmdErrorCode
204            exc.StorCliRunTimeError
205            exc.StorCliRunTimeout
206        """
207        cmd = [self._storcli]
208        cmd.extend(args)
209        # output in JSON format
210        cmd.append('J')
211        cmd_cache_key = ''.join(cmd)
212
213        if self.cache_enable:
214            if cmd_cache_key in self.__response_cache:
215                return self.__response_cache[cmd_cache_key]
216
217        with self.__cache_lock:
218            try:
219                ret = self.__cmdrunner.run(
220                    args=cmd, universal_newlines=True, **kwargs)
221                try:
222                    ret_json = json.loads(ret.stdout)
223                    self.check_response_status(
224                        cmd, ret_json, allow_error_codes)
225                    if ret.returncode != 0:
226                        raise subprocess.CalledProcessError(
227                            ret.returncode, cmd, ret.stdout, ret.stderr)
228                    if self.cache_enable:
229                        self.__response_cache[cmd_cache_key] = ret_json
230                    return ret_json
231                except json.JSONDecodeError as err:
232                    # legacy handler (Ralequi: I don't know if this is still needed or what exactly it does)
233                    output = re.search('(^.*)Storage.*Command.*$',
234                                       ret.stdout, re.MULTILINE | re.DOTALL)
235                    if output:
236                        raise exc.StorCliCmdError(cmd, output.group(1))
237
238                    # Check if we can still parse the output
239                    parsed = {}
240                    for line in ret.stdout.splitlines():
241                        if '=' in line:
242                            key, value = line.split('=', 1)
243                            parsed[key.strip()] = value.strip()
244
245                    if 'Status' in parsed:
246                        return parsed
247                    else:
248                        raise exc.StorCliCmdError(cmd, str(err))
249
250            except subprocess.TimeoutExpired as err:
251                raise exc.StorCliRunTimeout(err)
252            except subprocess.SubprocessError as err:
253                raise exc.StorCliRunTimeError(err)

Execute storcli command line with arguments.

Run command line and check output for errors.

Args: args (list of str): cmd line arguments (without binary) stdout (fd): controll subprocess stdout fd stderr (fd): controll subporcess stderr fd allow_error_codes (list of StorcliErrors): list of error codes to allow **kwargs: arguments to subprocess run

Returns: dict: output data from command line

Raises: exc.StorCliCmdError exc.StorCliCmdErrorCode exc.StorCliRunTimeError exc.StorCliRunTimeout

@staticmethod
def enable_singleton():
263    @staticmethod
264    def enable_singleton():
265        """Enable StorCLI to be singleton on module level
266
267        Use StorCLI singleton across all objects. All pystorcli
268        class instances use their own StorCLI object. With enabled cache
269        we can speedup for example metric lookups.
270
271        """
272        StorCLI.__set_singleton(True)

Enable StorCLI to be singleton on module level

Use StorCLI singleton across all objects. All pystorcli class instances use their own StorCLI object. With enabled cache we can speedup for example metric lookups.

@staticmethod
def disable_singleton():
274    @staticmethod
275    def disable_singleton():
276        """Disable StoreCLI class as signleton
277        """
278        StorCLI.__set_singleton(False)

Disable StoreCLI class as signleton

@staticmethod
def is_singleton() -> bool:
280    @staticmethod
281    def is_singleton() -> bool:
282        """Check if singleton is enabled
283        """
284        return _SINGLETON_STORCLI_MODULE_ENABLE

Check if singleton is enabled

full_version: str

Get storcli version as storcli returns

version: str

Get storcli version in a cleaner way

controllers: pystorcli2.Controllers

Get list of controllers

class Controller:
 25class Controller(object):
 26    """StorCLI Controller
 27
 28    Instance of this class represents controller in StorCLI hierarchy
 29
 30    Args:
 31        ctl_id (str): controller id
 32        binary (str): storcli binary or full path to the binary
 33
 34    Properties:
 35        id (str): controller id
 36        name (str): controller cmd name
 37        facts (dict): raw controller facts
 38        metrics (:obj:ControllerMetrics): controller metrics
 39        vds (list of :obj:virtualdrive.VirtualDrives): controller virtual drives
 40        encls (:obj:enclosure.Enclosures): controller enclosures
 41        autorebuild (dict): current auto rebuild state (also setter)
 42        foreignautoimport (dict): imports foreign configuration automatically at boot (also setter)
 43        patrolread (dict): current patrol read settings (also setter)
 44        cc (dict): current patrol read settings (also setter)
 45        has_foreign_configurations (bool): true if controller has foreign configurations
 46
 47    Methods:
 48        create_vd (:obj:VirtualDrive): create virtual drive
 49        set_patrolread (dict): configures patrol read state and schedule
 50        patrolread_start (dict): starts a patrol read on controller
 51        patrolread_pause (dict): pauses patrol read on controller
 52        patrolread_resume (dict): resumes patrol read on controller
 53        patrolread_stop (dict): stops patrol read if running on controller
 54        patrolread_running (bool): check if patrol read is running on controller
 55        set_cc (dict): configures consistency check mode and start time
 56        import_foreign_configurations (dict): imports the foreign configurations on controller
 57        delete_foreign_configurations (dict): deletes the foreign configuration on controller
 58
 59    TODO:
 60        Implement missing methods:
 61            * patrol read progress
 62    """
 63
 64    def __init__(self, ctl_id, binary='storcli64'):
 65        """Constructor - create StorCLI Controller object
 66
 67        Args:
 68            ctl_id (str): controller id
 69            binary (str): storcli binary or full path to the binary
 70        """
 71        self._ctl_id = ctl_id
 72        self._binary = binary
 73        self._storcli = StorCLI(binary)
 74        self._name = '/c{0}'.format(self._ctl_id)
 75
 76        self._exist()
 77
 78    def __str__(self):
 79        return '{0}'.format(common.response_data(self._run(['show'])))
 80
 81    def _run(self, args, allow_error_codes=[StorcliError.INCOMPLETE_FOREIGN_CONFIGURATION], **kwargs):
 82        args = args[:]
 83        args.insert(0, self._name)
 84        return self._storcli.run(args, allow_error_codes=allow_error_codes, **kwargs)
 85
 86    def _exist(self):
 87        try:
 88            self._run(['show'])
 89        except exc.StorCliCmdError:
 90            raise exc.StorCliMissingError(
 91                self.__class__.__name__, self._name) from None
 92
 93    @property
 94    def id(self):
 95        """ (str): controller id
 96        """
 97        return self._ctl_id
 98
 99    @property
100    def name(self):
101        """ (str): controller cmd name
102        """
103        return self._name
104
105    @property
106    def facts(self):
107        """ (dict): raw controller facts
108        """
109        args = [
110            'show',
111            'all'
112        ]
113        return common.response_data(self._run(args))
114
115    @property
116    def metrics(self):
117        """(:obj:ControllerMetrics): controller metrics
118        """
119        return ControllerMetrics(ctl=self)
120
121    @property
122    def vds(self):
123        """(:obj:virtualdrive.VirtualDrives): controllers virtual drives
124        """
125        return virtualdrive.VirtualDrives(ctl_id=self._ctl_id, binary=self._binary)
126
127    @property
128    def encls(self):
129        """(:obj:enclosure.Enclosures): controller enclosures
130        """
131        return enclosure.Enclosures(ctl_id=self._ctl_id, binary=self._binary)
132
133    @property
134    def drives_ids(self) -> List[str]:
135        """(list of str): list of drives ids in format (e:s)
136        """
137        drives = []
138        for encl in self.encls:
139            for id in encl.drives.ids:
140                drives.append("{enc}:{id}".format(enc=encl.id, id=id))
141
142        return drives
143
144    def create_vd(self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[virtualdrive.VirtualDrive]:
145        """Create virtual drive (raid) managed by current controller
146
147        Args:
148            name (str): virtual drive name
149            raid (str): virtual drive raid level (raid0, raid1, ...)
150            drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z)
151            strip (str, optional): virtual drive raid strip size
152
153        Returns:
154            (None): no virtual drive created with name
155            (:obj:virtualdrive.VirtualDrive)
156        """
157        args = [
158            'add',
159            'vd',
160            'r{0}'.format(raid),
161            'name={0}'.format(name),
162            'drives={0}'.format(drives),
163            'strip={0}'.format(strip)
164        ]
165
166        try:
167            if int(raid) >= 10 and PDperArray is None:
168                # Try to count the number of drives in the array
169                # The format of the drives argument is e:s|e:s-x|e:s-x,y;e:s-x,y,z
170
171                numDrives = common.count_drives(drives)
172
173                if numDrives % 2 != 0 and numDrives % 3 == 0:
174                    # In some scenarios, such as 9 drives with raid 60, 3 is a good pd number but 4 is not
175                    # Must check for similar scenarios
176                    # BTW we don't clearly understand what PDperArray is for and what exactly it does under the hood. More investigation is needed
177                    PDperArray = numDrives//3
178                else:
179                    PDperArray = numDrives//2
180
181        except ValueError:
182            pass
183
184        finally:
185            if raid == '00' and PDperArray is None:
186                PDperArray = 1
187
188        if PDperArray is not None:
189            args.append('PDperArray={0}'.format(PDperArray))
190
191        self._run(args)
192        for vd in self.vds:
193            if name == vd.name:
194                return vd
195        return None
196
197    @property
198    @common.lower
199    def autorebuild(self):
200        """Get/Set auto rebuild state
201
202        One of the following options can be set (str):
203            on - enables autorebuild
204            off - disables autorebuild
205
206        Returns:
207            (str): on / off
208        """
209        args = [
210            'show',
211            'autorebuild'
212        ]
213
214        prop = common.response_property(self._run(args))[0]
215        return prop['Value']
216
217    @autorebuild.setter
218    def autorebuild(self, value):
219        """
220        """
221        args = [
222            'set',
223            'autorebuild={0}'.format(value)
224        ]
225        return common.response_setter(self._run(args))
226
227    @property
228    @common.lower
229    def foreignautoimport(self):
230        """Get/Set auto foreign import configuration
231
232        One of the following options can be set (str):
233            on - enables foreignautoimport
234            off - disables foreignautoimport
235
236        Returns:
237            (str): on / off
238        """
239        args = [
240            'show',
241            'foreignautoimport'
242        ]
243        prop = common.response_property(self._run(args))[0]
244        return prop['Value']
245
246    @foreignautoimport.setter
247    def foreignautoimport(self, value):
248        """
249        """
250        args = [
251            'set',
252            'foreignautoimport={0}'.format(value)
253        ]
254        return common.response_setter(self._run(args))
255
256    @property
257    @common.lower
258    def patrolread(self):
259        """Get/Set patrol read
260
261        One of the following options can be set (str):
262            on - enables patrol read
263            off - disables patrol read
264
265        Returns:
266            (str): on / off
267        """
268        args = [
269            'show',
270            'patrolread'
271        ]
272
273        for pr in common.response_property(self._run(args)):
274            if pr['Ctrl_Prop'] == "PR Mode":
275                if pr['Value'] == 'Disable':
276                    return 'off'
277                else:
278                    return 'on'
279        return 'off'
280
281    @patrolread.setter
282    def patrolread(self, value):
283        """
284        """
285        return self.set_patrolread(value)
286
287    def set_patrolread(self, value, mode='manual'):
288        """Set patrol read
289
290        Args:
291            value (str): on / off to configure patrol read state
292            mode (str): auto | manual to configure patrol read schedule
293        """
294        args = [
295            'set',
296            'patrolread={0}'.format(value)
297        ]
298
299        if value == 'on':
300            args.append('mode={0}'.format(mode))
301
302        return common.response_setter(self._run(args))
303
304    def patrolread_start(self):
305        """Starts the patrol read operation of the controller
306
307        Returns:
308            (dict): response cmd data
309        """
310        args = [
311            'start',
312            'patrolread'
313        ]
314        return common.response_cmd(self._run(args))
315
316    def patrolread_stop(self):
317        """Stops the patrol read operation of the controller
318
319        Returns:
320            (dict): response cmd data
321        """
322        args = [
323            'stop',
324            'patrolread'
325        ]
326        return common.response_cmd(self._run(args))
327
328    def patrolread_pause(self):
329        """Pauses the patrol read operation of the controller
330
331        Returns:
332            (dict): response cmd data
333        """
334        args = [
335            'pause',
336            'patrolread'
337        ]
338        return common.response_cmd(self._run(args))
339
340    def patrolread_resume(self):
341        """Resumes the patrol read operation of the controller
342
343        Returns:
344            (dict): response cmd data
345        """
346        args = [
347            'resume',
348            'patrolread'
349        ]
350        return common.response_cmd(self._run(args))
351
352    @property
353    def patrolread_running(self):
354        """Check if patrol read is running on the controller
355
356        Returns:
357            (bool): true / false
358        """
359        args = [
360            'show',
361            'patrolread'
362        ]
363
364        status = ''
365        for pr in common.response_property(self._run(args)):
366            if pr['Ctrl_Prop'] == "PR Current State":
367                status = pr['Value']
368        return bool('Active' in status)
369
370    @property
371    @common.lower
372    def cc(self):
373        """Get/Set consistency chceck
374
375        One of the following options can be set (str):
376            seq  - sequential mode
377            conc - concurrent mode
378            off  - disables consistency check
379
380        Returns:
381            (str): on / off
382        """
383        args = [
384            'show',
385            'cc'
386        ]
387
388        for pr in common.response_property(self._run(args)):
389            if pr['Ctrl_Prop'] == "CC Operation Mode":
390                if pr['Value'] == 'Disabled':
391                    return 'off'
392                else:
393                    return 'on'
394        return 'off'
395
396    @cc.setter
397    def cc(self, value):
398        """
399        """
400        return self.set_cc(value)
401
402    def set_cc(self, value, starttime=None):
403        """Set consistency check
404
405        Args:
406            value (str):
407                seq  - sequential mode
408                conc - concurrent mode
409                off  - disables consistency check
410            starttime (str): Start time of a consistency check is yyyy/mm/dd hh format
411        """
412        args = [
413            'set',
414            'cc={0}'.format(value)
415        ]
416
417        if value in ('seq', 'conc'):
418            if starttime is None:
419                starttime = datetime.now().strftime('%Y/%m/%d %H')
420            args.append('starttime="{0}"'.format(starttime))
421
422        return common.response_setter(self._run(args))
423
424    def has_foreign_configurations(self, securitykey: Optional[str] = None) -> bool:
425        """(bool): true if controller has foreign configurations
426        """
427        args = [
428            '/fall',
429            'show'
430        ]
431
432        if securitykey:
433            args.append(f'securitykey={securitykey}')
434
435        try:
436            fc_data = common.response_data(self._run(args))
437            fcs = 0
438
439            if 'Total foreign Drive Groups' in fc_data:
440                fcs = int(fc_data['Total foreign Drive Groups'])
441            if 'Total Foreign PDs' in fc_data:
442                fcs += int(fc_data['Total Foreign PDs'])
443            if 'Total Locked Foreign PDs' in fc_data:
444                fcs += int(fc_data['Total Locked Foreign PDs'])
445
446            if fcs > 0:
447                return True
448        except KeyError:
449            pass
450        return False
451
452    def is_foreign_configuration_healthy(self, securitykey: Optional[str] = None) -> bool:
453        """(bool): true if controller has healthy foreign configurations
454        """
455
456        if not self.has_foreign_configurations(securitykey):
457            return True
458
459        args = [
460            '/fall',
461            'show'
462        ]
463
464        if securitykey:
465            args.append(f'securitykey={securitykey}')
466
467        try:
468            fc_data = common.response_data(
469                self._run(args, allow_error_codes=[]))
470        except exc.StorCliCmdErrorCode as e:
471            if e.error_code == StorcliError.INCOMPLETE_FOREIGN_CONFIGURATION:
472                return False
473
474            raise e
475
476        return True
477
478    def delete_foreign_configurations(self, securitykey: Optional[str] = None):
479        """Deletes foreign configurations
480
481        Returns:
482            (dict): response cmd data
483        """
484        args = [
485            '/fall',
486            'del'
487        ]
488
489        if securitykey:
490            args.append(f'securitykey={securitykey}')
491        return common.response_cmd(self._run(args))
492
493    def import_foreign_configurations(self, securitykey: Optional[str] = None):
494        """Imports foreign configurations
495
496        Returns:
497            (dict): response cmd data
498        """
499        args = [
500            '/fall',
501            'import'
502        ]
503        if securitykey:
504            args.append(f'securitykey={securitykey}')
505        return common.response_cmd(self._run(args))

StorCLI Controller

Instance of this class represents controller in StorCLI hierarchy

Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary

Properties: id (str): controller id name (str): controller cmd name facts (dict): raw controller facts metrics (:obj:ControllerMetrics): controller metrics vds (list of :obj:virtualdrive.VirtualDrives): controller virtual drives encls (:obj:enclosure.Enclosures): controller enclosures autorebuild (dict): current auto rebuild state (also setter) foreignautoimport (dict): imports foreign configuration automatically at boot (also setter) patrolread (dict): current patrol read settings (also setter) cc (dict): current patrol read settings (also setter) has_foreign_configurations (bool): true if controller has foreign configurations

Methods: create_vd (:obj:VirtualDrive): create virtual drive set_patrolread (dict): configures patrol read state and schedule patrolread_start (dict): starts a patrol read on controller patrolread_pause (dict): pauses patrol read on controller patrolread_resume (dict): resumes patrol read on controller patrolread_stop (dict): stops patrol read if running on controller patrolread_running (bool): check if patrol read is running on controller set_cc (dict): configures consistency check mode and start time import_foreign_configurations (dict): imports the foreign configurations on controller delete_foreign_configurations (dict): deletes the foreign configuration on controller

TODO: Implement missing methods: * patrol read progress

Controller(ctl_id, binary='storcli64')
64    def __init__(self, ctl_id, binary='storcli64'):
65        """Constructor - create StorCLI Controller object
66
67        Args:
68            ctl_id (str): controller id
69            binary (str): storcli binary or full path to the binary
70        """
71        self._ctl_id = ctl_id
72        self._binary = binary
73        self._storcli = StorCLI(binary)
74        self._name = '/c{0}'.format(self._ctl_id)
75
76        self._exist()

Constructor - create StorCLI Controller object

Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary

id

(str): controller id

name

(str): controller cmd name

facts

(dict): raw controller facts

metrics

(:obj:ControllerMetrics): controller metrics

vds

(:obj:virtualdrive.VirtualDrives): controllers virtual drives

encls

(:obj:enclosure.Enclosures): controller enclosures

drives_ids: List[str]

(list of str): list of drives ids in format (e:s)

def create_vd( self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[pystorcli2.VirtualDrive]:
144    def create_vd(self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[virtualdrive.VirtualDrive]:
145        """Create virtual drive (raid) managed by current controller
146
147        Args:
148            name (str): virtual drive name
149            raid (str): virtual drive raid level (raid0, raid1, ...)
150            drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z)
151            strip (str, optional): virtual drive raid strip size
152
153        Returns:
154            (None): no virtual drive created with name
155            (:obj:virtualdrive.VirtualDrive)
156        """
157        args = [
158            'add',
159            'vd',
160            'r{0}'.format(raid),
161            'name={0}'.format(name),
162            'drives={0}'.format(drives),
163            'strip={0}'.format(strip)
164        ]
165
166        try:
167            if int(raid) >= 10 and PDperArray is None:
168                # Try to count the number of drives in the array
169                # The format of the drives argument is e:s|e:s-x|e:s-x,y;e:s-x,y,z
170
171                numDrives = common.count_drives(drives)
172
173                if numDrives % 2 != 0 and numDrives % 3 == 0:
174                    # In some scenarios, such as 9 drives with raid 60, 3 is a good pd number but 4 is not
175                    # Must check for similar scenarios
176                    # BTW we don't clearly understand what PDperArray is for and what exactly it does under the hood. More investigation is needed
177                    PDperArray = numDrives//3
178                else:
179                    PDperArray = numDrives//2
180
181        except ValueError:
182            pass
183
184        finally:
185            if raid == '00' and PDperArray is None:
186                PDperArray = 1
187
188        if PDperArray is not None:
189            args.append('PDperArray={0}'.format(PDperArray))
190
191        self._run(args)
192        for vd in self.vds:
193            if name == vd.name:
194                return vd
195        return None

Create virtual drive (raid) managed by current controller

Args: name (str): virtual drive name raid (str): virtual drive raid level (raid0, raid1, ...) drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z) strip (str, optional): virtual drive raid strip size

Returns: (None): no virtual drive created with name (:obj:virtualdrive.VirtualDrive)

autorebuild

func effective wrapper

foreignautoimport

func effective wrapper

patrolread

func effective wrapper

def set_patrolread(self, value, mode='manual'):
287    def set_patrolread(self, value, mode='manual'):
288        """Set patrol read
289
290        Args:
291            value (str): on / off to configure patrol read state
292            mode (str): auto | manual to configure patrol read schedule
293        """
294        args = [
295            'set',
296            'patrolread={0}'.format(value)
297        ]
298
299        if value == 'on':
300            args.append('mode={0}'.format(mode))
301
302        return common.response_setter(self._run(args))

Set patrol read

Args: value (str): on / off to configure patrol read state mode (str): auto | manual to configure patrol read schedule

def patrolread_start(self):
304    def patrolread_start(self):
305        """Starts the patrol read operation of the controller
306
307        Returns:
308            (dict): response cmd data
309        """
310        args = [
311            'start',
312            'patrolread'
313        ]
314        return common.response_cmd(self._run(args))

Starts the patrol read operation of the controller

Returns: (dict): response cmd data

def patrolread_stop(self):
316    def patrolread_stop(self):
317        """Stops the patrol read operation of the controller
318
319        Returns:
320            (dict): response cmd data
321        """
322        args = [
323            'stop',
324            'patrolread'
325        ]
326        return common.response_cmd(self._run(args))

Stops the patrol read operation of the controller

Returns: (dict): response cmd data

def patrolread_pause(self):
328    def patrolread_pause(self):
329        """Pauses the patrol read operation of the controller
330
331        Returns:
332            (dict): response cmd data
333        """
334        args = [
335            'pause',
336            'patrolread'
337        ]
338        return common.response_cmd(self._run(args))

Pauses the patrol read operation of the controller

Returns: (dict): response cmd data

def patrolread_resume(self):
340    def patrolread_resume(self):
341        """Resumes the patrol read operation of the controller
342
343        Returns:
344            (dict): response cmd data
345        """
346        args = [
347            'resume',
348            'patrolread'
349        ]
350        return common.response_cmd(self._run(args))

Resumes the patrol read operation of the controller

Returns: (dict): response cmd data

patrolread_running

Check if patrol read is running on the controller

Returns: (bool): true / false

cc

func effective wrapper

def set_cc(self, value, starttime=None):
402    def set_cc(self, value, starttime=None):
403        """Set consistency check
404
405        Args:
406            value (str):
407                seq  - sequential mode
408                conc - concurrent mode
409                off  - disables consistency check
410            starttime (str): Start time of a consistency check is yyyy/mm/dd hh format
411        """
412        args = [
413            'set',
414            'cc={0}'.format(value)
415        ]
416
417        if value in ('seq', 'conc'):
418            if starttime is None:
419                starttime = datetime.now().strftime('%Y/%m/%d %H')
420            args.append('starttime="{0}"'.format(starttime))
421
422        return common.response_setter(self._run(args))

Set consistency check

Args: value (str): seq - sequential mode conc - concurrent mode off - disables consistency check starttime (str): Start time of a consistency check is yyyy/mm/dd hh format

def has_foreign_configurations(self, securitykey: Optional[str] = None) -> bool:
424    def has_foreign_configurations(self, securitykey: Optional[str] = None) -> bool:
425        """(bool): true if controller has foreign configurations
426        """
427        args = [
428            '/fall',
429            'show'
430        ]
431
432        if securitykey:
433            args.append(f'securitykey={securitykey}')
434
435        try:
436            fc_data = common.response_data(self._run(args))
437            fcs = 0
438
439            if 'Total foreign Drive Groups' in fc_data:
440                fcs = int(fc_data['Total foreign Drive Groups'])
441            if 'Total Foreign PDs' in fc_data:
442                fcs += int(fc_data['Total Foreign PDs'])
443            if 'Total Locked Foreign PDs' in fc_data:
444                fcs += int(fc_data['Total Locked Foreign PDs'])
445
446            if fcs > 0:
447                return True
448        except KeyError:
449            pass
450        return False

(bool): true if controller has foreign configurations

def is_foreign_configuration_healthy(self, securitykey: Optional[str] = None) -> bool:
452    def is_foreign_configuration_healthy(self, securitykey: Optional[str] = None) -> bool:
453        """(bool): true if controller has healthy foreign configurations
454        """
455
456        if not self.has_foreign_configurations(securitykey):
457            return True
458
459        args = [
460            '/fall',
461            'show'
462        ]
463
464        if securitykey:
465            args.append(f'securitykey={securitykey}')
466
467        try:
468            fc_data = common.response_data(
469                self._run(args, allow_error_codes=[]))
470        except exc.StorCliCmdErrorCode as e:
471            if e.error_code == StorcliError.INCOMPLETE_FOREIGN_CONFIGURATION:
472                return False
473
474            raise e
475
476        return True

(bool): true if controller has healthy foreign configurations

def delete_foreign_configurations(self, securitykey: Optional[str] = None):
478    def delete_foreign_configurations(self, securitykey: Optional[str] = None):
479        """Deletes foreign configurations
480
481        Returns:
482            (dict): response cmd data
483        """
484        args = [
485            '/fall',
486            'del'
487        ]
488
489        if securitykey:
490            args.append(f'securitykey={securitykey}')
491        return common.response_cmd(self._run(args))

Deletes foreign configurations

Returns: (dict): response cmd data

def import_foreign_configurations(self, securitykey: Optional[str] = None):
493    def import_foreign_configurations(self, securitykey: Optional[str] = None):
494        """Imports foreign configurations
495
496        Returns:
497            (dict): response cmd data
498        """
499        args = [
500            '/fall',
501            'import'
502        ]
503        if securitykey:
504            args.append(f'securitykey={securitykey}')
505        return common.response_cmd(self._run(args))

Imports foreign configurations

Returns: (dict): response cmd data

class Controllers:
508class Controllers(object):
509    """StorCLI Controllers
510
511    Instance of this class is iterable with :obj:Controller as item
512
513    Args:
514        binary (str): storcli binary or full path to the binary
515
516    Properties:
517        ids (list of str): list of controllers id
518
519    Methods:
520        get_clt (:obj:Controller): return controller object by id
521    """
522
523    def __init__(self, binary='storcli64'):
524        """Constructor - create StorCLI Controllers object
525
526        Args:
527            binary (str): storcli binary or full path to the binary
528        """
529        self._binary = binary
530        self._storcli = StorCLI(binary)
531
532    @ property
533    def _ctl_ids(self) -> List[int]:
534        out = self._storcli.run(['show'], allow_error_codes=[
535            StorcliError.INCOMPLETE_FOREIGN_CONFIGURATION])
536        response = common.response_data(out)
537
538        if "Number of Controllers" in response and response["Number of Controllers"] == 0:
539            return []
540        else:
541            return [ctl['Ctl'] for ctl in common.response_data_subkey(out, ['System Overview', 'IT System Overview'])]
542
543    @ property
544    def _ctls(self):
545        for ctl_id in self._ctl_ids:
546            yield Controller(ctl_id=ctl_id, binary=self._binary)
547
548    def __iter__(self):
549        return self._ctls
550
551    @ property
552    def ids(self):
553        """(list of str): controllers id
554        """
555        return self._ctl_ids
556
557    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
558        """Get controller object by id
559
560        Args:
561            ctl_id (str): controller id
562
563        Returns:
564            (None): no controller with id
565            (:obj:Controller): controller object
566        """
567        for ctl in self:
568            if ctl.id == ctl_id:
569                return ctl
570        return None

StorCLI Controllers

Instance of this class is iterable with :obj:Controller as item

Args: binary (str): storcli binary or full path to the binary

Properties: ids (list of str): list of controllers id

Methods: get_clt (:obj:Controller): return controller object by id

Controllers(binary='storcli64')
523    def __init__(self, binary='storcli64'):
524        """Constructor - create StorCLI Controllers object
525
526        Args:
527            binary (str): storcli binary or full path to the binary
528        """
529        self._binary = binary
530        self._storcli = StorCLI(binary)

Constructor - create StorCLI Controllers object

Args: binary (str): storcli binary or full path to the binary

ids

(list of str): controllers id

def get_ctl(self, ctl_id: int) -> Optional[pystorcli2.Controller]:
557    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
558        """Get controller object by id
559
560        Args:
561            ctl_id (str): controller id
562
563        Returns:
564            (None): no controller with id
565            (:obj:Controller): controller object
566        """
567        for ctl in self:
568            if ctl.id == ctl_id:
569                return ctl
570        return None

Get controller object by id

Args: ctl_id (str): controller id

Returns: (None): no controller with id (:obj:Controller): controller object

class Enclosure:
 20class Enclosure(object):
 21    """StorCLI enclosure
 22
 23    Instance of this class represents enclosure in StorCLI hierarchy
 24
 25    Args:
 26        ctl_id (str): controller id
 27        encl_id (str): enclosure id
 28        binary (str): storcli binary or full path to the binary
 29
 30    Properties:
 31        id (str): enclosure id
 32        name (str): enclosure cmd name
 33        facts (dict): raw enclosure facts
 34        ctl_id (str): enclosure controller
 35        ctl (:obj:controller.Controller): enclosure controller
 36        has_drives (bool): true if enclosure has drives
 37        drives (list of :obj:drive.Drive): enclosure drives
 38    """
 39
 40    def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'):
 41        """Constructor - create StorCLI Enclosure object
 42
 43        Args:
 44            ctl_id (str): controller id
 45            encl_id (str): enclosure id
 46            binary (str): storcli binary or full path to the binary
 47        """
 48        self._ctl_id: int = ctl_id
 49        self._encl_id: int = encl_id
 50        self._binary: str = binary
 51        self._storcli: StorCLI = StorCLI(binary)
 52        self._name: str = '/c{0}/e{1}'.format(self._ctl_id, self._encl_id)
 53
 54        self._exist()
 55
 56    def _run(self, args, **kwargs):
 57        args = args[:]
 58        args.insert(0, self._name)
 59        return self._storcli.run(args, **kwargs)
 60
 61    def _exist(self):
 62        try:
 63            self._run(['show'])
 64        except exc.StorCliCmdError:
 65            raise exc.StorCliMissingError(
 66                self.__class__.__name__, self._name) from None
 67
 68    @property
 69    def id(self) -> int:
 70        """(str): enclosure id
 71        """
 72        return self._encl_id
 73
 74    @property
 75    def name(self) -> str:
 76        """(str): enclosure cmd name
 77        """
 78        return self._name
 79
 80    @property
 81    def facts(self):
 82        """(dict): raw enclosure facts
 83        """
 84        args = [
 85            'show',
 86            'all'
 87        ]
 88        return common.response_data(self._run(args))
 89
 90    @property
 91    def ctl_id(self) -> int:
 92        """(str): enclosure controller id
 93        """
 94        return self._ctl_id
 95
 96    @property
 97    def ctl(self):
 98        """(:obj:controller.Controller): enclosure controller
 99        """
100        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
101
102    @property
103    def has_drives(self) -> bool:
104        """(bool): true if enclosure has drives
105        """
106        args = [
107            'show'
108        ]
109
110        pds = common.response_data(self._run(args))['Properties'][0]['PD']
111        if pds == 0:
112            return False
113        return True
114
115    @property
116    def _slot_ids(self) -> List[int]:
117        args = [
118            '/c{0}/e{1}/sall'.format(self._ctl_id, self._encl_id),
119            'show'
120        ]
121
122        if not self.has_drives:
123            return []
124
125        drives = common.response_data(self._storcli.run(args))[
126            'Drive Information']
127        return [int(drive['EID:Slt'].split(':')[1]) for drive in drives]
128
129    @property
130    def drives(self) -> drive.Drives:
131        """(list of :obj:drive.Drive): enclosure drives
132        """
133        return drive.Drives(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary)

StorCLI enclosure

Instance of this class represents enclosure in StorCLI hierarchy

Args: ctl_id (str): controller id encl_id (str): enclosure id binary (str): storcli binary or full path to the binary

Properties: id (str): enclosure id name (str): enclosure cmd name facts (dict): raw enclosure facts ctl_id (str): enclosure controller ctl (:obj:controller.Controller): enclosure controller has_drives (bool): true if enclosure has drives drives (list of :obj:drive.Drive): enclosure drives

Enclosure(ctl_id: int, encl_id: int, binary: str = 'storcli64')
40    def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'):
41        """Constructor - create StorCLI Enclosure object
42
43        Args:
44            ctl_id (str): controller id
45            encl_id (str): enclosure id
46            binary (str): storcli binary or full path to the binary
47        """
48        self._ctl_id: int = ctl_id
49        self._encl_id: int = encl_id
50        self._binary: str = binary
51        self._storcli: StorCLI = StorCLI(binary)
52        self._name: str = '/c{0}/e{1}'.format(self._ctl_id, self._encl_id)
53
54        self._exist()

Constructor - create StorCLI Enclosure object

Args: ctl_id (str): controller id encl_id (str): enclosure id binary (str): storcli binary or full path to the binary

id: int

(str): enclosure id

name: str

(str): enclosure cmd name

facts

(dict): raw enclosure facts

ctl_id: int

(str): enclosure controller id

ctl

(:obj:controller.Controller): enclosure controller

has_drives: bool

(bool): true if enclosure has drives

(list of :obj:drive.Drive): enclosure drives

class Enclosures:
136class Enclosures(object):
137    """StorCLI enclosures
138
139    Instance of this class is iterable with :obj:Enclosure as item
140
141    Args:
142        ctl_id (str): controller id
143        binary (str): storcli binary or full path to the binary
144
145    Properties:
146        ids (list of str): list of enclosures id
147        ctl_id (str): enclosures controller id
148        ctl (:obj:controller.Controller): enclosures controller
149
150
151    Methods:
152        get_encl (:obj:Enclosure): return enclosure object by id
153    """
154
155    def __init__(self, ctl_id: int, binary: str = 'storcli64'):
156        """Constructor - create StorCLI Enclosures object
157
158        Args:
159            ctl_id (str): controller id
160            binary (str): storcli binary or full path to the binary
161        """
162        self._ctl_id: int = ctl_id
163        self._binary: str = binary
164        self._storcli: StorCLI = StorCLI(binary)
165
166    @property
167    def _encl_ids(self) -> List[int]:
168        args = [
169            '/c{0}/eall'.format(self._ctl_id),
170            'show'
171        ]
172
173        out = self._storcli.run(args)
174        return [int(encl['EID']) for encl in common.response_data(out)['Properties']]
175
176    @property
177    def _encls(self):
178        for encl_id in self._encl_ids:
179            yield Enclosure(ctl_id=self._ctl_id, encl_id=encl_id, binary=self._binary)
180
181    def __iter__(self):
182        return self._encls
183
184    @property
185    def ids(self) -> List[int]:
186        """(list of str): list of enclosures id
187        """
188        return self._encl_ids
189
190    @property
191    def ctl_id(self) -> int:
192        """(str): enclosures controller id
193        """
194        return self._ctl_id
195
196    @property
197    def ctl(self):
198        """(:obj:controller.Controller): enclosures controller
199        """
200        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
201
202    def get_encl(self, encl_id: int) -> Optional[Enclosure]:
203        """Get enclosure object by id
204
205        Args:
206            encl_id (str): enclosure id
207
208        Returns:
209            (None): no enclosure with id
210            (:obj:Enclosure): enclosure object
211        """
212        for encl in self:
213            if encl.id == encl_id:
214                return encl
215        return None

StorCLI enclosures

Instance of this class is iterable with :obj:Enclosure as item

Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary

Properties: ids (list of str): list of enclosures id ctl_id (str): enclosures controller id ctl (:obj:controller.Controller): enclosures controller

Methods: get_encl (:obj:Enclosure): return enclosure object by id

Enclosures(ctl_id: int, binary: str = 'storcli64')
155    def __init__(self, ctl_id: int, binary: str = 'storcli64'):
156        """Constructor - create StorCLI Enclosures object
157
158        Args:
159            ctl_id (str): controller id
160            binary (str): storcli binary or full path to the binary
161        """
162        self._ctl_id: int = ctl_id
163        self._binary: str = binary
164        self._storcli: StorCLI = StorCLI(binary)

Constructor - create StorCLI Enclosures object

Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary

ids: List[int]

(list of str): list of enclosures id

ctl_id: int

(str): enclosures controller id

ctl

(:obj:controller.Controller): enclosures controller

def get_encl(self, encl_id: int) -> Optional[pystorcli2.Enclosure]:
202    def get_encl(self, encl_id: int) -> Optional[Enclosure]:
203        """Get enclosure object by id
204
205        Args:
206            encl_id (str): enclosure id
207
208        Returns:
209            (None): no enclosure with id
210            (:obj:Enclosure): enclosure object
211        """
212        for encl in self:
213            if encl.id == encl_id:
214                return encl
215        return None

Get enclosure object by id

Args: encl_id (str): enclosure id

Returns: (None): no enclosure with id (:obj:Enclosure): enclosure object

class DriveState(enum.Enum):
 13class DriveState(Enum):
 14    """Drive status
 15    """
 16    # From storcli 7.1704
 17    # EID=Enclosure Device ID|Slt=Slot No|DID=Device ID|DG=DriveGroup
 18    # DHS=Dedicated Hot Spare|UGood=Unconfigured Good|GHS=Global Hotspare
 19    # UBad=Unconfigured Bad|Sntze=Sanitize|Onln=Online|Offln=Offline|Intf=Interface
 20    # Med=Media Type|SED=Self Encryptive Drive|PI=Protection Info
 21    # SeSz=Sector Size|Sp=Spun|U=Up|D=Down|T=Transition|F=Foreign
 22    # UGUnsp=UGood Unsupported|UGShld=UGood shielded|HSPShld=Hotspare shielded
 23    # CFShld=Configured shielded|Cpybck=CopyBack|CBShld=Copyback Shielded
 24    # UBUnsp=UBad Unsupported|Rbld=Rebuild
 25
 26    DHS = 'Dedicated Hot Spare'
 27    UGood = 'Unconfigured Good'
 28    GHS = 'Global Hotspare'
 29    UBad = 'Unconfigured Bad'
 30    Sntze = 'Sanitize'
 31    Onln = 'Online'
 32    Offln = 'Offline'
 33    Failed = 'Failed'
 34    SED = 'Self Encryptive Drive'
 35    UGUnsp = 'UGood Unsupported'
 36    UGShld = 'UGood shielded'
 37    HSPShld = 'Hotspare shielded'
 38    CFShld = 'Configured shielded'
 39    Cpybck = 'CopyBack'
 40    CBShld = 'Copyback Shielded'
 41    UBUnsp = 'UBad Unsupported'
 42    Rbld = 'Rebuild'
 43    Missing = 'Missing'
 44    JBOD = 'JBOD'
 45
 46    def __str__(self) -> str:
 47        return self.value
 48
 49    def is_good(self) -> bool:
 50        """Check if drive is good according to status"""
 51        good_states = [
 52            DriveState.DHS,
 53            DriveState.UGood,
 54            DriveState.GHS,
 55            # DriveState.Sntze, ??
 56            DriveState.Onln,
 57            DriveState.SED,
 58            # DriveState.UGUnsp, ??
 59            DriveState.UGShld,
 60            DriveState.HSPShld,
 61            DriveState.CFShld,
 62            DriveState.Cpybck,
 63            DriveState.CBShld,
 64            DriveState.Rbld,
 65            DriveState.JBOD
 66        ]
 67
 68        return self in good_states
 69
 70    def is_configured(self) -> bool:
 71        """Check if drive is configured according to status"""
 72        configured_states = [
 73            DriveState.DHS,
 74            DriveState.GHS,
 75            # DriveState.Sntze, ??
 76            DriveState.Onln,
 77            DriveState.SED,
 78            # DriveState.UGShld, ??
 79            DriveState.HSPShld,
 80            DriveState.CFShld,
 81            DriveState.Cpybck,
 82            DriveState.CBShld,
 83            DriveState.Rbld,
 84            DriveState.JBOD
 85        ]
 86
 87        return self in configured_states
 88
 89    def is_settable(self) -> bool:
 90        """Check if this status can be directly set. Not all statuses can be set directly."""
 91        # online | offline | missing | good
 92
 93        settable_states = [
 94            DriveState.Onln,
 95            DriveState.Offln,
 96            DriveState.Missing,
 97            DriveState.UGood,
 98            DriveState.JBOD
 99        ]
100
101        return self in settable_states
102
103    def settable_str(self) -> str:
104        """Get string representation of settable status. Storcli uses different strings for set command than for show command."""
105        if self == DriveState.Onln:
106            return 'online'
107        elif self == DriveState.Offln:
108            return 'offline'
109        elif self == DriveState.Missing:
110            return 'missing'
111        elif self == DriveState.UGood:
112            return 'good'
113        elif self == DriveState.JBOD:
114            return 'jbod'
115        else:
116            raise ValueError('This status is not settable')
117
118    @staticmethod
119    def from_string(status: str) -> 'DriveState':
120        """Get DriveState from string"""
121
122        alias = {
123            'good': DriveState.UGood,
124            'bad': DriveState.UBad,
125            'dedicated': DriveState.DHS,
126            'hotspare': DriveState.GHS,
127            'unconfigured': DriveState.UGood,
128            'unconfigured(good)': DriveState.UGood,
129            'unconfigured(bad)': DriveState.UBad,
130        }
131
132        # check for direct match
133        for drive_status in DriveState:
134            if drive_status.name.lower() == status.lower() or drive_status.value.lower() == status.lower():
135                return drive_status
136
137        # check for alias
138        if status.lower() in alias:
139            return alias[status.lower()]
140
141        raise ValueError('Invalid drive status: {0}'.format(status))

Drive status

DHS = <DriveState.DHS: 'Dedicated Hot Spare'>
UGood = <DriveState.UGood: 'Unconfigured Good'>
GHS = <DriveState.GHS: 'Global Hotspare'>
UBad = <DriveState.UBad: 'Unconfigured Bad'>
Sntze = <DriveState.Sntze: 'Sanitize'>
Onln = <DriveState.Onln: 'Online'>
Offln = <DriveState.Offln: 'Offline'>
Failed = <DriveState.Failed: 'Failed'>
SED = <DriveState.SED: 'Self Encryptive Drive'>
UGUnsp = <DriveState.UGUnsp: 'UGood Unsupported'>
UGShld = <DriveState.UGShld: 'UGood shielded'>
HSPShld = <DriveState.HSPShld: 'Hotspare shielded'>
CFShld = <DriveState.CFShld: 'Configured shielded'>
Cpybck = <DriveState.Cpybck: 'CopyBack'>
CBShld = <DriveState.CBShld: 'Copyback Shielded'>
UBUnsp = <DriveState.UBUnsp: 'UBad Unsupported'>
Rbld = <DriveState.Rbld: 'Rebuild'>
Missing = <DriveState.Missing: 'Missing'>
JBOD = <DriveState.JBOD: 'JBOD'>
def is_good(self) -> bool:
49    def is_good(self) -> bool:
50        """Check if drive is good according to status"""
51        good_states = [
52            DriveState.DHS,
53            DriveState.UGood,
54            DriveState.GHS,
55            # DriveState.Sntze, ??
56            DriveState.Onln,
57            DriveState.SED,
58            # DriveState.UGUnsp, ??
59            DriveState.UGShld,
60            DriveState.HSPShld,
61            DriveState.CFShld,
62            DriveState.Cpybck,
63            DriveState.CBShld,
64            DriveState.Rbld,
65            DriveState.JBOD
66        ]
67
68        return self in good_states

Check if drive is good according to status

def is_configured(self) -> bool:
70    def is_configured(self) -> bool:
71        """Check if drive is configured according to status"""
72        configured_states = [
73            DriveState.DHS,
74            DriveState.GHS,
75            # DriveState.Sntze, ??
76            DriveState.Onln,
77            DriveState.SED,
78            # DriveState.UGShld, ??
79            DriveState.HSPShld,
80            DriveState.CFShld,
81            DriveState.Cpybck,
82            DriveState.CBShld,
83            DriveState.Rbld,
84            DriveState.JBOD
85        ]
86
87        return self in configured_states

Check if drive is configured according to status

def is_settable(self) -> bool:
 89    def is_settable(self) -> bool:
 90        """Check if this status can be directly set. Not all statuses can be set directly."""
 91        # online | offline | missing | good
 92
 93        settable_states = [
 94            DriveState.Onln,
 95            DriveState.Offln,
 96            DriveState.Missing,
 97            DriveState.UGood,
 98            DriveState.JBOD
 99        ]
100
101        return self in settable_states

Check if this status can be directly set. Not all statuses can be set directly.

def settable_str(self) -> str:
103    def settable_str(self) -> str:
104        """Get string representation of settable status. Storcli uses different strings for set command than for show command."""
105        if self == DriveState.Onln:
106            return 'online'
107        elif self == DriveState.Offln:
108            return 'offline'
109        elif self == DriveState.Missing:
110            return 'missing'
111        elif self == DriveState.UGood:
112            return 'good'
113        elif self == DriveState.JBOD:
114            return 'jbod'
115        else:
116            raise ValueError('This status is not settable')

Get string representation of settable status. Storcli uses different strings for set command than for show command.

@staticmethod
def from_string(status: str) -> pystorcli2.DriveState:
118    @staticmethod
119    def from_string(status: str) -> 'DriveState':
120        """Get DriveState from string"""
121
122        alias = {
123            'good': DriveState.UGood,
124            'bad': DriveState.UBad,
125            'dedicated': DriveState.DHS,
126            'hotspare': DriveState.GHS,
127            'unconfigured': DriveState.UGood,
128            'unconfigured(good)': DriveState.UGood,
129            'unconfigured(bad)': DriveState.UBad,
130        }
131
132        # check for direct match
133        for drive_status in DriveState:
134            if drive_status.name.lower() == status.lower() or drive_status.value.lower() == status.lower():
135                return drive_status
136
137        # check for alias
138        if status.lower() in alias:
139            return alias[status.lower()]
140
141        raise ValueError('Invalid drive status: {0}'.format(status))

Get DriveState from string

Inherited Members
enum.Enum
name
value
class Drive:
 25class Drive(object):
 26    """StorCLI Drive
 27
 28    Instance of this class represents drive in StorCLI hierarchy
 29
 30    Args:
 31        ctl_id (str): controller id
 32        encl_id (str): enclosure id
 33        slot_id (str): slot id
 34        binary (str): storcli binary or full path to the binary
 35
 36    Properties:
 37        id (str): drive id
 38        name (str): drive cmd name
 39        facts (dict): raw drive facts
 40        metrics (dict): drive metrics for monitoring
 41        size (str): drive size
 42        interface (str): SATA / SAS
 43        medium (str): SSD / HDD
 44        model (str): drive model informations
 45        serial (str): drive serial number
 46        wwn (str): drive wwn
 47        firmware (str): drive firmware version
 48        device_speed (str): drive speed
 49        linke_speed (str): drive connection link speed
 50        ctl_id (str): drive controller id
 51        ctl (:obj:controller.Controller): drive controller
 52        encl_id (str): drive enclosure
 53        encl (:obj:enclosure.Enclosure): drive enclosure
 54        phyerrorcounters (dict): drive error counters (also setter)
 55        state (str): drive state (also setter)
 56        spin (str): drive spin state (also setter)
 57
 58
 59    Methods:
 60        init_start (dict): starts the initialization process on a drive
 61        init_stop (dict): stops an initialization process running on a drive
 62        init_running (bool): check if initialization is running on a drive
 63        erase_start (dict): securely erases non-SED drive
 64        erase_stop (dict): stops an erase process running on a drive
 65        erase_running (bool): check if erase is running on a drive
 66        hotparedrive_create (dict): add drive to hotspares
 67        hotparedrive_delete (dict): delete drive from hotspare
 68
 69    TODO:
 70        Implement missing methods:
 71            * start rebuild
 72            * stop rebuild
 73            * pause rebuild
 74            * resume rebuild
 75            * rebuild running
 76    """
 77
 78    def __init__(self, ctl_id, encl_id, slot_id, binary='storcli64'):
 79        """Constructor - create StorCLI Drive object
 80
 81        Args:
 82            ctl_id (str): controller id
 83            encl_id (str): enclosure id
 84            slot_id (str): slot id
 85            binary (str): storcli binary or full path to the binary
 86        """
 87        self._ctl_id = ctl_id
 88        self._encl_id = encl_id
 89        self._slot_id = slot_id
 90        self._binary = binary
 91        self._storcli = StorCLI(binary)
 92        self._name = '/c{0}/e{1}/s{2}'.format(self._ctl_id,
 93                                              self._encl_id, self._slot_id)
 94
 95        self._exist()
 96
 97    @staticmethod
 98    def _response_properties(out):
 99        return common.response_data(out)['Drive Information'][0]
100
101    def _response_attributes(self, out):
102        detailed_info = ('Drive /c{0}/e{1}/s{2}'
103                         ' - Detailed Information'.format(self._ctl_id, self._encl_id, self._slot_id))
104        attr = 'Drive /c{0}/e{1}/s{2} Device attributes'.format(
105            self._ctl_id, self._encl_id, self._slot_id)
106        return common.response_data(out)[detailed_info][attr]
107
108    def _run(self, args, **kwargs):
109        args = args[:]
110        args.insert(0, self._name)
111        return self._storcli.run(args, **kwargs)
112
113    def _exist(self):
114        try:
115            self._run(['show'])
116        except exc.StorCliCmdError:
117            raise exc.StorCliMissingError(
118                self.__class__.__name__, self._name) from None
119
120    @property
121    def id(self):
122        """(str): drive id
123        """
124        return self._slot_id
125
126    @property
127    def name(self):
128        """(str): drive cmd name
129        """
130        return self._name
131
132    @property
133    def facts(self):
134        """(dict): raw drive facts
135        """
136        args = [
137            'show',
138            'all'
139        ]
140        return common.response_data(self._run(args))
141
142    @property
143    def metrics(self):
144        """(dict): drive metrics
145        """
146        return DriveMetrics(self)
147
148    @property
149    def size(self):
150        """(str): drive size
151        """
152        args = [
153            'show'
154        ]
155        return self._response_properties(self._run(args))['Size']
156
157    @property
158    @common.upper
159    def interface(self):
160        """(str): SATA / SAS
161        """
162        args = [
163            'show'
164        ]
165        return self._response_properties(self._run(args))['Intf']
166
167    @property
168    @common.upper
169    def medium(self):
170        """(str): SSD / HDD
171        """
172        args = [
173            'show'
174        ]
175        return self._response_properties(self._run(args))['Med']
176
177    @property
178    @common.upper
179    @common.strip
180    def model(self):
181        """(str): drive model informations
182        """
183        args = [
184            'show'
185        ]
186        return self._response_properties(self._run(args))['Model']
187
188    @property
189    @common.upper
190    @common.strip
191    def serial(self):
192        """(str): drive serial number
193        """
194        args = [
195            'show',
196            'all'
197        ]
198        return self._response_attributes(self._run(args))['SN']
199
200    @property
201    @common.upper
202    def wwn(self):
203        """(str): drive wwn
204        """
205        args = [
206            'show',
207            'all'
208        ]
209        return self._response_attributes(self._run(args))['WWN']
210
211    @property
212    @common.upper
213    def firmware(self):
214        """(str): drive firmware version
215        """
216        args = [
217            'show',
218            'all'
219        ]
220        return self._response_attributes(self._run(args))['Firmware Revision']
221
222    @property
223    def device_speed(self):
224        """(str): drive speed
225        """
226        args = [
227            'show',
228            'all'
229        ]
230        return self._response_attributes(self._run(args))['Device Speed']
231
232    @property
233    def link_speed(self):
234        """(str): drive connection link speed
235        """
236        args = [
237            'show',
238            'all'
239        ]
240        return self._response_attributes(self._run(args))['Link Speed']
241
242    @property
243    def ctl_id(self):
244        """(str): drive controller id
245        """
246        return self._ctl_id
247
248    @property
249    def ctl(self):
250        """(:obj:controller.Controller): drive controller
251        """
252        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
253
254    @property
255    def encl_id(self):
256        """(str): dirve enclosure id
257        """
258        return self._encl_id
259
260    @property
261    def encl(self):
262        """(:obj:enclosure.Enclosure): drive enclosure
263        """
264        return enclosure.Enclosure(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary)
265
266    @property
267    def vd_id(self) -> Union[None, int]:
268        """(int): drive virtual drive id if any
269        """
270        args = [
271            'show']
272        dg = self._response_properties(self._run(args))['DG']
273
274        if isinstance(dg, int):
275            return dg
276        else:
277            return None
278
279    @property
280    def vd(self) -> Union[None, virtualdrive.VirtualDrive]:
281        """(:obj:virtualdrive.VirtualDrive): get the virtual drive if any
282        """
283        if self.vd_id is None:
284            return None
285        else:
286            return virtualdrive.VirtualDrive(self._ctl_id, self.vd_id, self._binary)
287
288    def init_start(self):
289        """Start initialization of a drive
290
291        Returns:
292            (dict): resposne cmd data
293        """
294        args = [
295            'start',
296            'initialization'
297        ]
298        return common.response_cmd(self._run(args))
299
300    def init_stop(self):
301        """Stop initialization on a drive
302
303        A stopped initialization process cannot be resumed.
304
305        Returns:
306            (dict): resposne cmd data
307        """
308        args = [
309            'stop',
310            'initialization'
311        ]
312        return common.response_cmd(self._run(args))
313
314    @property
315    def init_running(self):
316        """Check if initialization process is running on a drive
317
318        Returns:
319            (bool): true / false
320        """
321        args = [
322            'show',
323            'initialization'
324        ]
325
326        status = common.response_data(self._run(args))[0]['Status']
327        return bool(status == 'In progress')
328
329    def erase_start(self, mode='simple'):
330        """Securely erases non-SED drives with specified erase pattern
331
332        Args:
333            mode (str):
334                simple		-	Single pass, single pattern write
335                normal		-	Three pass, three pattern write
336                thorough	-	Nine pass, repeats the normal write 3 times
337                standard	-	Applicable only for DFF's
338                threepass	-	Three pass, pass1 random pattern write, pass2,3 write zero, verify
339                crypto 	-	Applicable only for ISE capable drives
340                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
341
342        Returns:
343            (dict): resposne cmd data
344        """
345        args = [
346            'start',
347            'erase',
348            '{0}'.format(mode)
349        ]
350        return common.response_cmd(self._run(args))
351
352    def erase_stop(self):
353        """Stops the erase operation of a drive
354
355        Returns:
356            (dict): resposne cmd data
357        """
358        args = [
359            'stop',
360            'erase'
361        ]
362        return common.response_cmd(self._run(args))
363
364    @property
365    def erase_running(self):
366        """Check if erase process is running on a drive
367
368        Returns:
369            (bool): true / false
370        """
371        args = [
372            'show',
373            'erase'
374        ]
375
376        status = common.response_data(self._run(args))[0]['Status']
377        return bool(status == 'In progress')
378
379    @property
380    def phyerrorcounters(self):
381        """Get/Reset the drive phyerrorcounters
382
383        Reset drive error counters with (str) 0
384        """
385        args = [
386            'show',
387            'phyerrorcounters'
388        ]
389        return common.response_data(self._run(args))[self._name]
390
391    @phyerrorcounters.setter
392    def phyerrorcounters_reset(self):
393        """
394        """
395        args = [
396            'reset',
397            'phyerrorcounters'
398        ]
399        return common.response_cmd(self._run(args))
400
401    @property
402    def state(self) -> DriveState:
403        """Get/Set drive state
404        """
405        args = [
406            'show'
407        ]
408
409        state = self._response_properties(self._run(args))['State']
410
411        return DriveState.from_string(state)
412
413    @state.setter
414    def state(self, value: Union[str, DriveState]):
415        """ Set drive state
416        """
417
418        return self.set_state(value, force=False)
419
420    def set_state(self, value: Union[str, DriveState], force: bool = False):
421        """ Set drive state
422        """
423        # if DriveState, get the string value
424        if isinstance(value, str):
425            value = DriveState.from_string(value)
426
427        value = value.settable_str()
428
429        args = [
430            'set',
431            '{0}'.format(value)
432        ]
433
434        if force:
435            args.append('force')
436
437        return common.response_setter(self._run(args))
438
439    @property
440    def spin(self):
441        """Get/Set drive spin status
442
443        One of the following states can be set (str):
444            up - spins up and set to unconfigured good
445            down - spins down an unconfigured drive and prepares it for removal
446
447        Returns:
448            (str): up / down
449        """
450        args = [
451            'show'
452        ]
453
454        spin = self._response_properties(self._run(args))['Sp']
455        if spin == 'U':
456            return 'up'
457        return 'down'
458
459    @spin.setter
460    def spin(self, value):
461        """
462        """
463        if value == 'up':
464            spin = 'spinup'
465        elif value == 'down':
466            spin = 'spindown'
467        else:
468            spin = value
469
470        args = [
471            '{0}'.format(spin)
472        ]
473        return common.response_setter(self._run(args))
474
475    def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False):
476        """Creates a hotspare drive
477
478        Args:
479            dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...)
480            enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with.
481                                 If this option is specified, affinity is set; if it is not specified,
482                                 there is no affinity.NOTE Affinity cannot be removed once it is set
483                                 for a hotspare drive.
484            nonrevertible (bool): sets the drive as a nonrevertible hotspare
485
486        Returns:
487            (dict): resposne cmd data
488        """
489        args = [
490            'add',
491            'hotsparedrive'
492        ]
493
494        if dgs:
495            args.append("dgs={0}".format(dgs))
496        if enclaffinity:
497            args.append('enclaffinity')
498        if nonrevertible:
499            args.append('nonrevertible')
500        return common.response_cmd(self._run(args))
501
502    def hotparedrive_delete(self):
503        """Deletes drive from hotspares
504
505        Returns:
506            (dict): resposne cmd data
507        """
508        args = [
509            'delete',
510            'hotsparedrive'
511        ]
512        return common.response_cmd(self._run(args))

StorCLI Drive

Instance of this class represents drive in StorCLI hierarchy

Args: ctl_id (str): controller id encl_id (str): enclosure id slot_id (str): slot id binary (str): storcli binary or full path to the binary

Properties: id (str): drive id name (str): drive cmd name facts (dict): raw drive facts metrics (dict): drive metrics for monitoring size (str): drive size interface (str): SATA / SAS medium (str): SSD / HDD model (str): drive model informations serial (str): drive serial number wwn (str): drive wwn firmware (str): drive firmware version device_speed (str): drive speed linke_speed (str): drive connection link speed ctl_id (str): drive controller id ctl (:obj:controller.Controller): drive controller encl_id (str): drive enclosure encl (:obj:enclosure.Enclosure): drive enclosure phyerrorcounters (dict): drive error counters (also setter) state (str): drive state (also setter) spin (str): drive spin state (also setter)

Methods: init_start (dict): starts the initialization process on a drive init_stop (dict): stops an initialization process running on a drive init_running (bool): check if initialization is running on a drive erase_start (dict): securely erases non-SED drive erase_stop (dict): stops an erase process running on a drive erase_running (bool): check if erase is running on a drive hotparedrive_create (dict): add drive to hotspares hotparedrive_delete (dict): delete drive from hotspare

TODO: Implement missing methods: * start rebuild * stop rebuild * pause rebuild * resume rebuild * rebuild running

Drive(ctl_id, encl_id, slot_id, binary='storcli64')
78    def __init__(self, ctl_id, encl_id, slot_id, binary='storcli64'):
79        """Constructor - create StorCLI Drive object
80
81        Args:
82            ctl_id (str): controller id
83            encl_id (str): enclosure id
84            slot_id (str): slot id
85            binary (str): storcli binary or full path to the binary
86        """
87        self._ctl_id = ctl_id
88        self._encl_id = encl_id
89        self._slot_id = slot_id
90        self._binary = binary
91        self._storcli = StorCLI(binary)
92        self._name = '/c{0}/e{1}/s{2}'.format(self._ctl_id,
93                                              self._encl_id, self._slot_id)
94
95        self._exist()

Constructor - create StorCLI Drive object

Args: ctl_id (str): controller id encl_id (str): enclosure id slot_id (str): slot id binary (str): storcli binary or full path to the binary

id

(str): drive id

name

(str): drive cmd name

facts

(dict): raw drive facts

metrics

(dict): drive metrics

size

(str): drive size

interface

func effective wrapper

medium

func effective wrapper

model

func effective wrapper

serial

func effective wrapper

wwn

func effective wrapper

firmware

func effective wrapper

device_speed

(str): drive speed

ctl_id

(str): drive controller id

ctl

(:obj:controller.Controller): drive controller

encl_id

(str): dirve enclosure id

encl

(:obj:enclosure.Enclosure): drive enclosure

vd_id: Optional[int]

(int): drive virtual drive id if any

vd: Optional[pystorcli2.VirtualDrive]

(:obj:virtualdrive.VirtualDrive): get the virtual drive if any

def init_start(self):
288    def init_start(self):
289        """Start initialization of a drive
290
291        Returns:
292            (dict): resposne cmd data
293        """
294        args = [
295            'start',
296            'initialization'
297        ]
298        return common.response_cmd(self._run(args))

Start initialization of a drive

Returns: (dict): resposne cmd data

def init_stop(self):
300    def init_stop(self):
301        """Stop initialization on a drive
302
303        A stopped initialization process cannot be resumed.
304
305        Returns:
306            (dict): resposne cmd data
307        """
308        args = [
309            'stop',
310            'initialization'
311        ]
312        return common.response_cmd(self._run(args))

Stop initialization on a drive

A stopped initialization process cannot be resumed.

Returns: (dict): resposne cmd data

init_running

Check if initialization process is running on a drive

Returns: (bool): true / false

def erase_start(self, mode='simple'):
329    def erase_start(self, mode='simple'):
330        """Securely erases non-SED drives with specified erase pattern
331
332        Args:
333            mode (str):
334                simple		-	Single pass, single pattern write
335                normal		-	Three pass, three pattern write
336                thorough	-	Nine pass, repeats the normal write 3 times
337                standard	-	Applicable only for DFF's
338                threepass	-	Three pass, pass1 random pattern write, pass2,3 write zero, verify
339                crypto 	-	Applicable only for ISE capable drives
340                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
341
342        Returns:
343            (dict): resposne cmd data
344        """
345        args = [
346            'start',
347            'erase',
348            '{0}'.format(mode)
349        ]
350        return common.response_cmd(self._run(args))

Securely erases non-SED drives with specified erase pattern

Args: mode (str): simple - Single pass, single pattern write normal - Three pass, three pattern write thorough - Nine pass, repeats the normal write 3 times standard - Applicable only for DFF's threepass - Three pass, pass1 random pattern write, pass2,3 write zero, verify crypto - Applicable only for ISE capable drives PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.

Returns: (dict): resposne cmd data

def erase_stop(self):
352    def erase_stop(self):
353        """Stops the erase operation of a drive
354
355        Returns:
356            (dict): resposne cmd data
357        """
358        args = [
359            'stop',
360            'erase'
361        ]
362        return common.response_cmd(self._run(args))

Stops the erase operation of a drive

Returns: (dict): resposne cmd data

erase_running

Check if erase process is running on a drive

Returns: (bool): true / false

phyerrorcounters

Get/Reset the drive phyerrorcounters

Reset drive error counters with (str) 0

phyerrorcounters_reset

Get/Reset the drive phyerrorcounters

Reset drive error counters with (str) 0

Get/Set drive state

def set_state( self, value: Union[str, pystorcli2.DriveState], force: bool = False):
420    def set_state(self, value: Union[str, DriveState], force: bool = False):
421        """ Set drive state
422        """
423        # if DriveState, get the string value
424        if isinstance(value, str):
425            value = DriveState.from_string(value)
426
427        value = value.settable_str()
428
429        args = [
430            'set',
431            '{0}'.format(value)
432        ]
433
434        if force:
435            args.append('force')
436
437        return common.response_setter(self._run(args))

Set drive state

spin

Get/Set drive spin status

One of the following states can be set (str): up - spins up and set to unconfigured good down - spins down an unconfigured drive and prepares it for removal

Returns: (str): up / down

def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False):
475    def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False):
476        """Creates a hotspare drive
477
478        Args:
479            dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...)
480            enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with.
481                                 If this option is specified, affinity is set; if it is not specified,
482                                 there is no affinity.NOTE Affinity cannot be removed once it is set
483                                 for a hotspare drive.
484            nonrevertible (bool): sets the drive as a nonrevertible hotspare
485
486        Returns:
487            (dict): resposne cmd data
488        """
489        args = [
490            'add',
491            'hotsparedrive'
492        ]
493
494        if dgs:
495            args.append("dgs={0}".format(dgs))
496        if enclaffinity:
497            args.append('enclaffinity')
498        if nonrevertible:
499            args.append('nonrevertible')
500        return common.response_cmd(self._run(args))

Creates a hotspare drive

Args: dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...) enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with. If this option is specified, affinity is set; if it is not specified, there is no affinity.NOTE Affinity cannot be removed once it is set for a hotspare drive. nonrevertible (bool): sets the drive as a nonrevertible hotspare

Returns: (dict): resposne cmd data

def hotparedrive_delete(self):
502    def hotparedrive_delete(self):
503        """Deletes drive from hotspares
504
505        Returns:
506            (dict): resposne cmd data
507        """
508        args = [
509            'delete',
510            'hotsparedrive'
511        ]
512        return common.response_cmd(self._run(args))

Deletes drive from hotspares

Returns: (dict): resposne cmd data

class Drives:
515class Drives(object):
516    """StorCLI drives
517
518    Instance of this class is iterable with :obj:Drive as item
519
520    Args:
521        ctl_id (str): controller id
522        encl_id (str): enclosure id
523        binary (str): storcli binary or full path to the binary
524
525    Properties:
526        ids (list of str): list of drives id
527        ctl_id (str): controller id where drives are located
528        encl_id (str): enclosure id where drives are located
529        ctl (:obj:controller.Controller): controller
530        encl (:obj:Enclosure): enclosure
531
532
533    Methods:
534        get_drive (:obj:Enclosure): return drive object by id
535        get_drive_range_ids (list of int): return list of drive ids in range
536        get_drive_range (:obj:Drives): return drives object in range
537    """
538
539    def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'):
540        """Constructor - create StorCLI Enclosures object
541
542        Args:
543            ctl_id (str): controller id
544            binary (str): storcli binary or full path to the binary
545        """
546        self._ctl_id: int = ctl_id
547        self._encl_id: int = encl_id
548        self._binary: str = binary
549        self._storcli: StorCLI = StorCLI(binary)
550
551    @property
552    def _drive_ids(self) -> List[int]:
553        args = [
554            '/c{0}/e{1}/sall'.format(self._ctl_id, self._encl_id),
555            'show'
556        ]
557
558        if not self.encl.has_drives:
559            return []
560
561        drives = common.response_data(self._storcli.run(args))[
562            'Drive Information']
563        return [int(drive['EID:Slt'].split(':')[1]) for drive in drives]
564
565    @property
566    def _drives(self):
567        for drive_id in self._drive_ids:
568            yield Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
569
570    def __iter__(self):
571        return self._drives
572
573    @property
574    def ids(self) -> List[int]:
575        """(list of str): list of enclosures id
576        """
577        return self._drive_ids
578
579    @property
580    def ctl_id(self) -> int:
581        """(str): enclosures controller id
582        """
583        return self._ctl_id
584
585    @property
586    def ctl(self):
587        """(:obj:controller.Controller): enclosures controller
588        """
589        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
590
591    @property
592    def encl_id(self) -> int:
593        """(str): enclosure id
594        """
595        return self._encl_id
596
597    @property
598    def encl(self):
599        """(:obj:Enclosure): enclosure
600        """
601        return enclosure.Enclosure(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary)
602
603    def get_drive(self, drive_id: int) -> Optional[Drive]:
604        """Get drive object by id
605
606        Args:
607            drive_id (str): drive id
608
609        Returns:
610            (None): no drive with id
611            (:obj:Drive): drive object
612        """
613        if drive_id in self._drive_ids:
614            return Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
615        else:
616            return None
617
618    def __getitem__(self, drive_id: int) -> Optional[Drive]:
619        return self.get_drive(drive_id)
620
621    def get_drive_range_ids(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None) -> List[int]:
622        """Get drive range list in the current enclosure
623
624        Args:
625            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
626            drive_id_end (Optional[int]): end of the range
627        """
628
629        if drive_id_end:
630            # check that drive_id_begin is integer, if not raise exception
631            if not isinstance(drive_id_begin, int):
632                raise ValueError('drive_id_begin must be an integer')
633
634            # otherwise convert to string
635            drive_id_begin = '{0}-{1}'.format(drive_id_begin, drive_id_end)
636
637        # if drive_id_begin is an integer, convert to string
638        if isinstance(drive_id_begin, int):
639            drive_id_begin = str(drive_id_begin)
640
641        # get the list of drives
642        drive_ids: List[int] = []
643        for drive_id in drive_id_begin.split(','):
644            if '-' in drive_id:
645                range_begin = drive_id.split('-')[0]
646                range_end = drive_id.split('-')[1]
647                drive_ids.extend(
648                    range(int(range_begin), int(range_end) + 1))
649            else:
650                drive_ids.append(int(drive_id))
651
652        return drive_ids
653
654    def get_drive_range(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None):
655        """Get drive range in the current enclosure
656
657        Args:
658            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
659            drive_id_end (Optional[int]): end of the range
660        """
661        drive_ids = self.get_drive_range_ids(drive_id_begin, drive_id_end)
662
663        for drive_id in drive_ids:
664            yield Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)

StorCLI drives

Instance of this class is iterable with :obj:Drive as item

Args: ctl_id (str): controller id encl_id (str): enclosure id binary (str): storcli binary or full path to the binary

Properties: ids (list of str): list of drives id ctl_id (str): controller id where drives are located encl_id (str): enclosure id where drives are located ctl (:obj:controller.Controller): controller encl (:obj:Enclosure): enclosure

Methods: get_drive (:obj:Enclosure): return drive object by id get_drive_range_ids (list of int): return list of drive ids in range get_drive_range (:obj:Drives): return drives object in range

Drives(ctl_id: int, encl_id: int, binary: str = 'storcli64')
539    def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'):
540        """Constructor - create StorCLI Enclosures object
541
542        Args:
543            ctl_id (str): controller id
544            binary (str): storcli binary or full path to the binary
545        """
546        self._ctl_id: int = ctl_id
547        self._encl_id: int = encl_id
548        self._binary: str = binary
549        self._storcli: StorCLI = StorCLI(binary)

Constructor - create StorCLI Enclosures object

Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary

ids: List[int]

(list of str): list of enclosures id

ctl_id: int

(str): enclosures controller id

ctl

(:obj:controller.Controller): enclosures controller

encl_id: int

(str): enclosure id

encl

(:obj:Enclosure): enclosure

def get_drive(self, drive_id: int) -> Optional[pystorcli2.Drive]:
603    def get_drive(self, drive_id: int) -> Optional[Drive]:
604        """Get drive object by id
605
606        Args:
607            drive_id (str): drive id
608
609        Returns:
610            (None): no drive with id
611            (:obj:Drive): drive object
612        """
613        if drive_id in self._drive_ids:
614            return Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
615        else:
616            return None

Get drive object by id

Args: drive_id (str): drive id

Returns: (None): no drive with id (:obj:Drive): drive object

def get_drive_range_ids( self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None) -> List[int]:
621    def get_drive_range_ids(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None) -> List[int]:
622        """Get drive range list in the current enclosure
623
624        Args:
625            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
626            drive_id_end (Optional[int]): end of the range
627        """
628
629        if drive_id_end:
630            # check that drive_id_begin is integer, if not raise exception
631            if not isinstance(drive_id_begin, int):
632                raise ValueError('drive_id_begin must be an integer')
633
634            # otherwise convert to string
635            drive_id_begin = '{0}-{1}'.format(drive_id_begin, drive_id_end)
636
637        # if drive_id_begin is an integer, convert to string
638        if isinstance(drive_id_begin, int):
639            drive_id_begin = str(drive_id_begin)
640
641        # get the list of drives
642        drive_ids: List[int] = []
643        for drive_id in drive_id_begin.split(','):
644            if '-' in drive_id:
645                range_begin = drive_id.split('-')[0]
646                range_end = drive_id.split('-')[1]
647                drive_ids.extend(
648                    range(int(range_begin), int(range_end) + 1))
649            else:
650                drive_ids.append(int(drive_id))
651
652        return drive_ids

Get drive range list in the current enclosure

Args: drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer drive_id_end (Optional[int]): end of the range

def get_drive_range( self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None):
654    def get_drive_range(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None):
655        """Get drive range in the current enclosure
656
657        Args:
658            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
659            drive_id_end (Optional[int]): end of the range
660        """
661        drive_ids = self.get_drive_range_ids(drive_id_begin, drive_id_end)
662
663        for drive_id in drive_ids:
664            yield Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)

Get drive range in the current enclosure

Args: drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer drive_id_end (Optional[int]): end of the range

class VirtualDrive:
 23class VirtualDrive(object):
 24    """StorCLI VirtualDrive
 25
 26    Instance of this class represents virtual drive (RAID) in StorCLI hierarchy
 27
 28    Args:
 29        ctl_id (str): controller id
 30        vd_id (str): virtual drive id
 31        binary (str): storcli binary or full path to the binary
 32
 33    Properties:
 34        id (str): virtual drive id
 35        facts (dict): raw virtual drive facts
 36        metrics (dict): virtual drive metrics
 37        raid (str): vitual drive raid level
 38        size (str): virtual drive size
 39        state (VDState): virtual drive state
 40        access (VDAccess): virtual drive acess (RO,RW,...) (also setter)
 41        strip (str): virtual drive strip size
 42        os_exposed (bool): virtual drive exposed to the OS
 43        os_name (str): virtual drive device path (/dev/...)
 44        ctl_id (str): virtual drive controller
 45        ctl (:obj:controller.Controller): virtual drive controller
 46        drives (list of :obj:drive.Drive): virtual drive drives
 47        name (str): virtual drive name (also setter)
 48        bootdrive (str): virtual drive bootdrive (also setter)
 49        pdcache (str): current disk cache policy on a virtual drive (also setter)
 50        wrcache (str): write cache policy on a virtual drive (also setter)
 51        rdcache (str): read cache policy on a virtual drive (also setter)
 52        iopolicy (str): I/O policy on a virtual drive (also setter)
 53        autobgi (str):virtual drive auto background initialization setting (also setter)
 54
 55    Methods:
 56        init_start (dict): starts the initialization process on a virtual drive
 57        init_stop (dict): stops an initialization process running on a virtual drive
 58        init_running (bool): check if initialization is running on a virtual drive
 59        erase_start (dict): securely erases non-SED virtual drive
 60        erase_stop (dict): stops an erase process running on a virtual drive
 61        erase_running (bool): check if erase is running on a virtual drive
 62        erase_progress (str): % progress of erase on a virtual drive
 63        delete (dict): delete virtual drive
 64        migrate_start (dict): starts the migration process on a virtual drive
 65        migrate_running (bool): check if migrate is running on a virtual drive
 66        cc_start (dict): starts a consistency check a virtual drive
 67        cc_pause (dict): pauses consistency check on a virtual drive
 68        cc_resume (dict): resumes consistency check on a virtual drive
 69        cc_stop (dict): stops consistency check if running on a virtual drive
 70        cc_running (bool): check if consistency check is running on a virtual drive
 71    """
 72
 73    def __init__(self, ctl_id, vd_id, binary='storcli64'):
 74        """Constructor - create StorCLI VirtualDrive object
 75
 76        Args:
 77            ctl_id (str): controller id
 78            vd_id (str): virtual drive id
 79            binary (str): storcli binary or full path to the binary
 80        """
 81        self._ctl_id = ctl_id
 82        self._vd_id = vd_id
 83        self._binary = binary
 84        self._storcli = StorCLI(binary)
 85        self._name = '/c{0}/v{1}'.format(self._ctl_id, self._vd_id)
 86
 87        self._exist()
 88
 89    def _run(self, args, **kwargs):
 90        args = args[:]
 91        args.insert(0, self._name)
 92        return self._storcli.run(args, **kwargs)
 93
 94    def _exist(self):
 95        try:
 96            self._run(['show'])
 97        except exc.StorCliCmdError:
 98            raise exc.StorCliMissingError(
 99                self.__class__.__name__, self._name) from None
100
101    @staticmethod
102    def _response_properties(out):
103        return common.response_data(out)['Virtual Drives'][0]
104
105    def _response_properties_all(self, out):
106        return common.response_data(out)['VD{0} Properties'.format(self._vd_id)]
107
108    @staticmethod
109    def _response_operation_status(out):
110        return common.response_data(out)['VD Operation Status'][0]
111
112    @property
113    def id(self):
114        """(str): virtual drive id
115        """
116        return self._vd_id
117
118    @property
119    def facts(self):
120        """(dict): raw virtual drive facts
121        """
122        args = [
123            'show',
124            'all'
125        ]
126        return common.response_data(self._run(args))
127
128    @property
129    def metrics(self):
130        """(:obj:VirtualDriveMetrics): virtual drive metrics
131        """
132        return VirtualDriveMetrics(self)
133
134    @property
135    @common.lower
136    def raid(self):
137        """(str): virtual drive raid level
138        """
139        args = [
140            'show'
141        ]
142
143        return self._response_properties(self._run(args))['TYPE']
144
145    @property
146    def size(self):
147        """(str): virtual drive size
148        """
149        args = [
150            'show'
151        ]
152        return self._response_properties(self._run(args))['Size']
153
154    @property
155    def state(self) -> VDState:
156        """(VDState): virtual drive state (optimal | recovery | offline | degraded | degraded_partially)
157        """
158        args = [
159            'show'
160        ]
161        state = self._response_properties(self._run(args))['State']
162
163        return VDState.from_string(state)
164
165    @property
166    def access(self) -> VDAccess:
167        """(VDAccess): virtual drive acess (RO,RW,...)
168        """
169        args = [
170            'show'
171        ]
172        access = self._response_properties(self._run(args))['Access']
173
174        return VDAccess.from_string(access)
175
176    @access.setter
177    def access(self, access: VDAccess):
178        args = [
179            'set',
180            'accesspolicy={}'.format(access.value)
181        ]
182        self._run(args)
183
184    @property
185    def strip(self):
186        """(str): virtual drive strip size
187        """
188        args = [
189            'show',
190            'all'
191        ]
192
193        size = self._response_properties_all(self._run(args))['Strip Size']
194        return size.split()[0]
195
196    @property
197    def os_exposed(self):
198        """(bool): virtual drive exposed to the OS
199        """
200        args = [
201            'show',
202            'all'
203        ]
204
205        exposed = self._response_properties_all(self._run(args))[
206            'Exposed to OS']
207        return bool(exposed == 'Yes')
208
209    @property
210    @common.lower
211    def os_name(self):
212        """(str): virtual drive device path (/dev/...)
213        """
214        args = [
215            'show',
216            'all'
217        ]
218        return self._response_properties_all(self._run(args))['OS Drive Name']
219
220    @property
221    def ctl_id(self):
222        """(str): virtual drive controller id
223        """
224        return self._ctl_id
225
226    @property
227    def ctl(self):
228        """(:obj:controller.Controller): virtual drive controller
229        """
230        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
231
232    @property
233    def drives(self):
234        """(list of :obj:Drive): drives
235        """
236        args = [
237            'show',
238            'all'
239        ]
240
241        drives = []
242        pds = common.response_data(self._run(args))[
243            'PDs for VD {0}'.format(self._vd_id)]
244        for pd in pds:
245            drive_encl_id, drive_slot_id = pd['EID:Slt'].split(':')
246            drives.append(
247                drive.Drive(
248                    ctl_id=self._ctl_id,
249                    encl_id=drive_encl_id,
250                    slot_id=drive_slot_id,
251                    binary=self._binary
252                )
253            )
254        return drives
255
256    @property
257    def name(self):
258        """Get/Set virtual drive name
259
260        The name is restricted to 15 characters.
261
262        Returns:
263            (str): raid name
264        """
265        args = [
266            'show',
267        ]
268
269        properties = self._response_properties(self._run(args))
270        return properties['Name']
271
272    @name.setter
273    def name(self, value):
274        """
275        """
276        args = [
277            'set',
278            'name={0}'.format(value)
279        ]
280        return common.response_setter(self._run(args))
281
282    @property
283    def bootdrive(self):
284        """Get/Set virtual drive as Boot Drive
285
286        One of the following options can be set (str):
287            on - enable boot virtual drive
288            off - disable boot virtual dirve
289
290        Returns:
291            (str): on / off
292        """
293        args = [
294            '/c{0}'.format(self._ctl_id),
295            'show',
296            'bootdrive'
297        ]
298
299        for vd in common.response_property(self._storcli.run(args)):
300            if vd['Value'] == 'VD:{0}'.format(self._vd_id):
301                return 'on'
302        return 'off'
303
304    @bootdrive.setter
305    def bootdrive(self, value):
306        """
307        """
308        args = [
309            'set',
310            'bootdrive={0}'.format(value)
311        ]
312        return common.response_setter(self._run(args))
313
314    @property
315    def pdcache(self):
316        """Get/Set PD Cache Setting
317
318        One of the following options can be set (str):
319            on - enables PD Caching
320            off - disables PD Caching
321            default - default PD Caching
322
323        Returns:
324            (str): on / off
325        """
326        args = [
327            'show',
328            'all'
329        ]
330
331        properties = self._response_properties_all(self._run(args))
332        if properties['Disk Cache Policy'] == 'Enabled':
333            return 'on'
334        elif 'Default' in properties['Disk Cache Policy']:
335            return 'default'
336        return 'off'
337
338    @pdcache.setter
339    def pdcache(self, value):
340        """
341        """
342        args = [
343            'set',
344            'pdcache={0}'.format(value)
345        ]
346        return common.response_setter(self._run(args))
347
348    @property
349    def wrcache(self):
350        """Get/Set Write cache setting
351
352        One of the following options can be set (str):
353            wt - write Through
354            wb - write Back
355            awb - write Back even in case of bad BBU also
356
357        Returns:
358            (str): wt / wb / awb
359        """
360        args = [
361            'show',
362        ]
363
364        properties = self._response_properties(self._run(args))
365        if 'AWB' in properties['Cache']:
366            return 'awb'
367        elif 'WB' in properties['Cache']:
368            return 'wb'
369        return 'wt'
370
371    @wrcache.setter
372    def wrcache(self, value):
373        """
374        """
375        args = [
376            'set',
377            'wrcache={0}'.format(value)
378        ]
379        return common.response_setter(self._run(args))
380
381    @property
382    def rdcache(self):
383        """Get/Set Read cache setting
384
385        One of the following options can be set (str):
386            ra - Read Ahead
387            nora - No Read Ahead
388
389        Returns:
390            (str): ra / nora
391        """
392        args = [
393            'show',
394        ]
395
396        properties = self._response_properties(self._run(args))
397        if properties['Cache'][0:2] == 'NR':
398            return 'nora'
399        return 'ra'
400
401    @rdcache.setter
402    def rdcache(self, value):
403        """
404        """
405        args = [
406            'set',
407            'rdcache={0}'.format(value)
408        ]
409        return common.response_setter(self._run(args))
410
411    @property
412    def iopolicy(self):
413        """Get/Set iopolicy setting
414
415        One of the following options can be set (str):
416            cached - IOs are cached
417            direct - IOs are not cached
418
419        Returns:
420            (str): cached / direct
421        """
422        args = [
423            'show',
424        ]
425
426        properties = self._response_properties(self._run(args))
427        if properties['Cache'][-1] == 'D':
428            return 'direct'
429        return 'cached'
430
431    @iopolicy.setter
432    def iopolicy(self, value):
433        """
434        """
435        args = [
436            'set',
437            'iopolicy={0}'.format(value)
438        ]
439        return common.response_setter(self._run(args))
440
441    @property
442    @common.lower
443    def autobgi(self):
444        """Get/Set auto background initialization
445
446        One of the following options can be set (str):
447            on - enables autobgi
448            off - disables autobgi
449
450        Returns:
451            (str): on / off
452        """
453        args = [
454            'show',
455            'autobgi'
456        ]
457        return self._response_operation_status(self._run(args))['AutoBGI']
458
459    @autobgi.setter
460    def autobgi(self, value):
461        """
462        """
463        args = [
464            'set',
465            'autobgi={0}'.format(value)
466        ]
467        return common.response_setter(self._run(args))
468
469    def init_start(self, full=False, force=False):
470        """Starts the initialization of a virtual drive
471
472        Args:
473            full (bool, optional): if specified then it is the full init otherwise it is Fast init
474            force (bool, optional): must be set if there was before some user data
475
476        Returns:
477            (dict): resposne cmd data
478        """
479        args = [
480            'start',
481            'init'
482        ]
483
484        if full:
485            args.append('full')
486        if force:
487            args.append('force')
488        return common.response_cmd(self._run(args))
489
490    def init_stop(self):
491        """Stops the initialization of a virtual drive
492
493        A stopped initialization process cannot be resumed.
494
495        Returns:
496            (dict): resposne cmd data
497        """
498        args = [
499            'stop',
500            'init'
501        ]
502        return common.response_cmd(self._run(args))
503
504    @property
505    def init_running(self):
506        """Check if initialization is running on a virtual drive
507
508        Returns:
509            (bool): true / false
510        """
511        args = [
512            'show',
513            'init'
514        ]
515
516        status = self._response_operation_status(self._run(args))['Status']
517        return bool(status == 'In progress')
518
519    def erase_start(self, mode='simple'):
520        """Securely erases non-SED drives with specified erase pattern
521
522        Args:
523            mode (str, optional):
524                simple		-	Single pass, single pattern write
525                normal		-	Three pass, three pattern write
526                thorough	-	Nine pass, repeats the normal write 3 times
527                standard	-	Applicable only for DFF's
528                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
529
530        Returns:
531            (dict): resposne cmd data
532        """
533        args = [
534            'start',
535            'erase',
536            '{0}'.format(mode)
537        ]
538        return common.response_cmd(self._run(args))
539
540    def erase_stop(self):
541        """Stops the erase operation of a virtual drive
542
543        Returns:
544            (dict): resposne cmd data
545        """
546        args = [
547            'stop',
548            'erase'
549        ]
550        return common.response_cmd(self._run(args))
551
552    @property
553    def erase_running(self):
554        """Check if erase is running on a virtual drive
555
556        Returns:
557            (bool): true / false
558        """
559        args = [
560            'show',
561            'erase'
562        ]
563
564        status = self._response_operation_status(self._run(args))['Status']
565        return bool(status == 'In progress')
566
567    @property
568    def erase_progress(self):
569        """Show virtual drive erase progress in percentage
570
571        Returns:
572            (str): progress in percentage
573        """
574
575        args = [
576            'show',
577            'erase'
578        ]
579
580        progress = self._response_operation_status(self._run(args))[
581            'Progress%']
582        if progress == '-':
583            return "100"
584        return progress
585
586    def delete(self, force=False):
587        """Deletes a particular virtual drive
588
589        Args:
590            force (bool, optional): If you delete a virtual drive with a valid MBR
591                                    without erasing the data and then create a new
592                                    virtual drive using the same set of physical drives
593                                    and the same RAID level as the deleted virtual drive,
594                                    the old unerased MBR still exists at block0 of the
595                                    new virtual drive, which makes it a virtual drive with
596                                    valid user data. Therefore, you must provide the
597                                    force option to delete this newly created virtual drive.
598
599        Returns:
600            (dict): resposne cmd data
601        """
602        args = [
603            'del'
604        ]
605
606        if force:
607            args.append('force')
608        return common.response_cmd(self._run(args))
609
610    def migrate_start(self, option, drives, raid=None, force=False):
611        """Starts migration on the virtual drive
612
613        Args:
614            option (str):
615                            add - adds the specified drives to the migrated raid
616                            remove - removes the specified drives from the migrated raid
617            drives (str): specifies the list drives which needs to be added
618                          or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y])
619            raid - raid level to which migration needs to be done (raid0, raid1, ...)
620            force - if specified, then migration will start even if any drive in the DG is secured
621
622        Returns:
623           (dict): resposne cmd data
624        """
625        if not raid:
626            raid = self.raid
627        args = [
628            'start',
629            'migrate',
630            'type={0}'.format(raid),
631            'option={0}'.format(option),
632            'drives={0}'.format(drives)
633        ]
634        if force:
635            args.append('force')
636        return common.response_cmd(self._run(args))
637
638    @property
639    def migrate_running(self):
640        """Check if migration is running on a virtual drive
641
642        Returns:
643            (bool): true / false
644        """
645        args = [
646            'show',
647            'migrate'
648        ]
649
650        status = self._response_operation_status(self._run(args))['Status']
651        return bool(status == 'In progress')
652
653    def cc_start(self, force=False):
654        """Starts a consistency check operation for a virtual drive
655
656        Args:
657            force - if specified, then consistency check will start even on an uninitialized drive
658
659        Returns:
660            (dict): resposne cmd data
661        """
662        args = [
663            'start',
664            'cc'
665        ]
666        if force:
667            args.append('force')
668        return common.response_cmd(self._run(args))
669
670    def cc_stop(self):
671        """Stops the consistency check operation of a virtual drive
672
673        Returns:
674            (dict): resposne cmd data
675        """
676        args = [
677            'stop',
678            'cc'
679        ]
680        return common.response_cmd(self._run(args))
681
682    def cc_pause(self):
683        """Pauses the consistency check operation of a virtual drive
684
685        Returns:
686            (dict): resposne cmd data
687        """
688        args = [
689            'pause',
690            'cc'
691        ]
692        return common.response_cmd(self._run(args))
693
694    def cc_resume(self):
695        """Resumes the consistency check operation of a virtual drive
696
697        Returns:
698            (dict): resposne cmd data
699        """
700        args = [
701            'resume',
702            'cc'
703        ]
704        return common.response_cmd(self._run(args))
705
706    @property
707    def cc_running(self):
708        """Check if consistency check is running on a virtual drive
709
710        Returns:
711            (bool): true / false
712        """
713        args = [
714            'show',
715            'cc'
716        ]
717
718        status = self._response_operation_status(self._run(args))['Status']
719        return bool(status == 'In progress')

StorCLI VirtualDrive

Instance of this class represents virtual drive (RAID) in StorCLI hierarchy

Args: ctl_id (str): controller id vd_id (str): virtual drive id binary (str): storcli binary or full path to the binary

Properties: id (str): virtual drive id facts (dict): raw virtual drive facts metrics (dict): virtual drive metrics raid (str): vitual drive raid level size (str): virtual drive size state (VDState): virtual drive state access (VDAccess): virtual drive acess (RO,RW,...) (also setter) strip (str): virtual drive strip size os_exposed (bool): virtual drive exposed to the OS os_name (str): virtual drive device path (/dev/...) ctl_id (str): virtual drive controller ctl (:obj:controller.Controller): virtual drive controller drives (list of :obj:drive.Drive): virtual drive drives name (str): virtual drive name (also setter) bootdrive (str): virtual drive bootdrive (also setter) pdcache (str): current disk cache policy on a virtual drive (also setter) wrcache (str): write cache policy on a virtual drive (also setter) rdcache (str): read cache policy on a virtual drive (also setter) iopolicy (str): I/O policy on a virtual drive (also setter) autobgi (str):virtual drive auto background initialization setting (also setter)

Methods: init_start (dict): starts the initialization process on a virtual drive init_stop (dict): stops an initialization process running on a virtual drive init_running (bool): check if initialization is running on a virtual drive erase_start (dict): securely erases non-SED virtual drive erase_stop (dict): stops an erase process running on a virtual drive erase_running (bool): check if erase is running on a virtual drive erase_progress (str): % progress of erase on a virtual drive delete (dict): delete virtual drive migrate_start (dict): starts the migration process on a virtual drive migrate_running (bool): check if migrate is running on a virtual drive cc_start (dict): starts a consistency check a virtual drive cc_pause (dict): pauses consistency check on a virtual drive cc_resume (dict): resumes consistency check on a virtual drive cc_stop (dict): stops consistency check if running on a virtual drive cc_running (bool): check if consistency check is running on a virtual drive

VirtualDrive(ctl_id, vd_id, binary='storcli64')
73    def __init__(self, ctl_id, vd_id, binary='storcli64'):
74        """Constructor - create StorCLI VirtualDrive object
75
76        Args:
77            ctl_id (str): controller id
78            vd_id (str): virtual drive id
79            binary (str): storcli binary or full path to the binary
80        """
81        self._ctl_id = ctl_id
82        self._vd_id = vd_id
83        self._binary = binary
84        self._storcli = StorCLI(binary)
85        self._name = '/c{0}/v{1}'.format(self._ctl_id, self._vd_id)
86
87        self._exist()

Constructor - create StorCLI VirtualDrive object

Args: ctl_id (str): controller id vd_id (str): virtual drive id binary (str): storcli binary or full path to the binary

id

(str): virtual drive id

facts

(dict): raw virtual drive facts

metrics

(:obj:VirtualDriveMetrics): virtual drive metrics

raid

func effective wrapper

size

(str): virtual drive size

state: pystorcli2.virtualdrive.state.VDState

(VDState): virtual drive state (optimal | recovery | offline | degraded | degraded_partially)

access: pystorcli2.virtualdrive.access.VDAccess

(VDAccess): virtual drive acess (RO,RW,...)

strip

(str): virtual drive strip size

os_exposed

(bool): virtual drive exposed to the OS

os_name

func effective wrapper

ctl_id

(str): virtual drive controller id

ctl

(:obj:controller.Controller): virtual drive controller

drives

(list of :obj:Drive): drives

name

Get/Set virtual drive name

The name is restricted to 15 characters.

Returns: (str): raid name

bootdrive

Get/Set virtual drive as Boot Drive

One of the following options can be set (str): on - enable boot virtual drive off - disable boot virtual dirve

Returns: (str): on / off

pdcache

Get/Set PD Cache Setting

One of the following options can be set (str): on - enables PD Caching off - disables PD Caching default - default PD Caching

Returns: (str): on / off

wrcache

Get/Set Write cache setting

One of the following options can be set (str): wt - write Through wb - write Back awb - write Back even in case of bad BBU also

Returns: (str): wt / wb / awb

rdcache

Get/Set Read cache setting

One of the following options can be set (str): ra - Read Ahead nora - No Read Ahead

Returns: (str): ra / nora

iopolicy

Get/Set iopolicy setting

One of the following options can be set (str): cached - IOs are cached direct - IOs are not cached

Returns: (str): cached / direct

autobgi

func effective wrapper

def init_start(self, full=False, force=False):
469    def init_start(self, full=False, force=False):
470        """Starts the initialization of a virtual drive
471
472        Args:
473            full (bool, optional): if specified then it is the full init otherwise it is Fast init
474            force (bool, optional): must be set if there was before some user data
475
476        Returns:
477            (dict): resposne cmd data
478        """
479        args = [
480            'start',
481            'init'
482        ]
483
484        if full:
485            args.append('full')
486        if force:
487            args.append('force')
488        return common.response_cmd(self._run(args))

Starts the initialization of a virtual drive

Args: full (bool, optional): if specified then it is the full init otherwise it is Fast init force (bool, optional): must be set if there was before some user data

Returns: (dict): resposne cmd data

def init_stop(self):
490    def init_stop(self):
491        """Stops the initialization of a virtual drive
492
493        A stopped initialization process cannot be resumed.
494
495        Returns:
496            (dict): resposne cmd data
497        """
498        args = [
499            'stop',
500            'init'
501        ]
502        return common.response_cmd(self._run(args))

Stops the initialization of a virtual drive

A stopped initialization process cannot be resumed.

Returns: (dict): resposne cmd data

init_running

Check if initialization is running on a virtual drive

Returns: (bool): true / false

def erase_start(self, mode='simple'):
519    def erase_start(self, mode='simple'):
520        """Securely erases non-SED drives with specified erase pattern
521
522        Args:
523            mode (str, optional):
524                simple		-	Single pass, single pattern write
525                normal		-	Three pass, three pattern write
526                thorough	-	Nine pass, repeats the normal write 3 times
527                standard	-	Applicable only for DFF's
528                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
529
530        Returns:
531            (dict): resposne cmd data
532        """
533        args = [
534            'start',
535            'erase',
536            '{0}'.format(mode)
537        ]
538        return common.response_cmd(self._run(args))

Securely erases non-SED drives with specified erase pattern

Args: mode (str, optional): simple - Single pass, single pattern write normal - Three pass, three pattern write thorough - Nine pass, repeats the normal write 3 times standard - Applicable only for DFF's PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.

Returns: (dict): resposne cmd data

def erase_stop(self):
540    def erase_stop(self):
541        """Stops the erase operation of a virtual drive
542
543        Returns:
544            (dict): resposne cmd data
545        """
546        args = [
547            'stop',
548            'erase'
549        ]
550        return common.response_cmd(self._run(args))

Stops the erase operation of a virtual drive

Returns: (dict): resposne cmd data

erase_running

Check if erase is running on a virtual drive

Returns: (bool): true / false

erase_progress

Show virtual drive erase progress in percentage

Returns: (str): progress in percentage

def delete(self, force=False):
586    def delete(self, force=False):
587        """Deletes a particular virtual drive
588
589        Args:
590            force (bool, optional): If you delete a virtual drive with a valid MBR
591                                    without erasing the data and then create a new
592                                    virtual drive using the same set of physical drives
593                                    and the same RAID level as the deleted virtual drive,
594                                    the old unerased MBR still exists at block0 of the
595                                    new virtual drive, which makes it a virtual drive with
596                                    valid user data. Therefore, you must provide the
597                                    force option to delete this newly created virtual drive.
598
599        Returns:
600            (dict): resposne cmd data
601        """
602        args = [
603            'del'
604        ]
605
606        if force:
607            args.append('force')
608        return common.response_cmd(self._run(args))

Deletes a particular virtual drive

Args: force (bool, optional): If you delete a virtual drive with a valid MBR without erasing the data and then create a new virtual drive using the same set of physical drives and the same RAID level as the deleted virtual drive, the old unerased MBR still exists at block0 of the new virtual drive, which makes it a virtual drive with valid user data. Therefore, you must provide the force option to delete this newly created virtual drive.

Returns: (dict): resposne cmd data

def migrate_start(self, option, drives, raid=None, force=False):
610    def migrate_start(self, option, drives, raid=None, force=False):
611        """Starts migration on the virtual drive
612
613        Args:
614            option (str):
615                            add - adds the specified drives to the migrated raid
616                            remove - removes the specified drives from the migrated raid
617            drives (str): specifies the list drives which needs to be added
618                          or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y])
619            raid - raid level to which migration needs to be done (raid0, raid1, ...)
620            force - if specified, then migration will start even if any drive in the DG is secured
621
622        Returns:
623           (dict): resposne cmd data
624        """
625        if not raid:
626            raid = self.raid
627        args = [
628            'start',
629            'migrate',
630            'type={0}'.format(raid),
631            'option={0}'.format(option),
632            'drives={0}'.format(drives)
633        ]
634        if force:
635            args.append('force')
636        return common.response_cmd(self._run(args))

Starts migration on the virtual drive

Args: option (str): add - adds the specified drives to the migrated raid remove - removes the specified drives from the migrated raid drives (str): specifies the list drives which needs to be added or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y]) raid - raid level to which migration needs to be done (raid0, raid1, ...) force - if specified, then migration will start even if any drive in the DG is secured

Returns: (dict): resposne cmd data

migrate_running

Check if migration is running on a virtual drive

Returns: (bool): true / false

def cc_start(self, force=False):
653    def cc_start(self, force=False):
654        """Starts a consistency check operation for a virtual drive
655
656        Args:
657            force - if specified, then consistency check will start even on an uninitialized drive
658
659        Returns:
660            (dict): resposne cmd data
661        """
662        args = [
663            'start',
664            'cc'
665        ]
666        if force:
667            args.append('force')
668        return common.response_cmd(self._run(args))

Starts a consistency check operation for a virtual drive

Args: force - if specified, then consistency check will start even on an uninitialized drive

Returns: (dict): resposne cmd data

def cc_stop(self):
670    def cc_stop(self):
671        """Stops the consistency check operation of a virtual drive
672
673        Returns:
674            (dict): resposne cmd data
675        """
676        args = [
677            'stop',
678            'cc'
679        ]
680        return common.response_cmd(self._run(args))

Stops the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

def cc_pause(self):
682    def cc_pause(self):
683        """Pauses the consistency check operation of a virtual drive
684
685        Returns:
686            (dict): resposne cmd data
687        """
688        args = [
689            'pause',
690            'cc'
691        ]
692        return common.response_cmd(self._run(args))

Pauses the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

def cc_resume(self):
694    def cc_resume(self):
695        """Resumes the consistency check operation of a virtual drive
696
697        Returns:
698            (dict): resposne cmd data
699        """
700        args = [
701            'resume',
702            'cc'
703        ]
704        return common.response_cmd(self._run(args))

Resumes the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

cc_running

Check if consistency check is running on a virtual drive

Returns: (bool): true / false

class VirtualDrives:
722class VirtualDrives(object):
723    """StorCLI virtual drives
724
725    Instance of this class is iterable with :obj:VirtualDrive as item
726
727    Args:
728        ctl_id (str): controller id
729        binary (str): storcli binary or full path to the binary
730
731    Properties:
732        has_vds (bool): true if there are vds
733        ids (list of str): list of virtual drives id
734        ctl_id (str): virtual drives controller id
735        ctl (:obj:controller.Controller): virtual drives controller
736
737
738    Methods:
739        has_vd (bool): true if there are virtual drives
740        get_vd (:obj:VirtualDrive): get virtual drive object by id
741        get_named_vd (:obj:VirtualDrive): get virtual drive object by name
742
743    """
744
745    def __init__(self, ctl_id, binary='storcli64'):
746        """Constructor - create StorCLI VirtualDrives object
747
748        Args:
749            ctl_id (str): controller id
750            binary (str): storcli binary or full path to the binary
751        """
752        self._ctl_id = ctl_id
753        self._binary = binary
754        self._storecli = StorCLI(binary)
755
756    @property
757    def _vd_ids(self):
758        args = [
759            '/c{0}'.format(self._ctl_id),
760            'show'
761        ]
762        data = common.response_data(self._storecli.run(args))
763        if 'VD LIST' in data:
764            return [vd['DG/VD'].split('/')[1] for vd in data['VD LIST']]
765        return []
766
767    @property
768    def _vds(self):
769        for vd_id in self._vd_ids:
770            yield VirtualDrive(ctl_id=self._ctl_id, vd_id=vd_id, binary=self._binary)
771
772    def __iter__(self):
773        return self._vds
774
775    @property
776    def ids(self):
777        """(list of str): list of virtual drives id
778        """
779        return self._vd_ids
780
781    @property
782    def ctl_id(self):
783        """(str): virtual drives controller id
784        """
785        return self._ctl_id
786
787    @property
788    def ctl(self):
789        """(:obj:controller.Controller): virtual drives controller
790        """
791        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
792
793    @property
794    def has_vds(self):
795        """(bool): true if there are virtual drives
796        """
797        if self.ids:
798            return True
799        return False
800
801    def get_vd(self, vd_id):
802        """Get virtual drive object by id
803
804        Args:
805            vd_id (str): virtual drive id
806
807        Returns:
808            (None): no virtual drive with id
809            (:obj:VirtualDrive): virtual drive object
810        """
811        for vd in self:
812            if vd.id == vd_id:
813                return vd
814        return None
815
816    def get_named_vd(self, vd_name):
817        """Get virtual drive object by name
818
819        Args:
820            vd_name (str): virtual drive name
821
822        Returns:
823            (None): no virtual drive with name
824            (:obj:VirtualDrive): virtual drive object
825        """
826        for vd in self:
827            if vd.name == vd_name:
828                return vd
829        return None

StorCLI virtual drives

Instance of this class is iterable with :obj:VirtualDrive as item

Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary

Properties: has_vds (bool): true if there are vds ids (list of str): list of virtual drives id ctl_id (str): virtual drives controller id ctl (:obj:controller.Controller): virtual drives controller

Methods: has_vd (bool): true if there are virtual drives get_vd (:obj:VirtualDrive): get virtual drive object by id get_named_vd (:obj:VirtualDrive): get virtual drive object by name

VirtualDrives(ctl_id, binary='storcli64')
745    def __init__(self, ctl_id, binary='storcli64'):
746        """Constructor - create StorCLI VirtualDrives object
747
748        Args:
749            ctl_id (str): controller id
750            binary (str): storcli binary or full path to the binary
751        """
752        self._ctl_id = ctl_id
753        self._binary = binary
754        self._storecli = StorCLI(binary)

Constructor - create StorCLI VirtualDrives object

Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary

ids

(list of str): list of virtual drives id

ctl_id

(str): virtual drives controller id

ctl

(:obj:controller.Controller): virtual drives controller

has_vds

(bool): true if there are virtual drives

def get_vd(self, vd_id):
801    def get_vd(self, vd_id):
802        """Get virtual drive object by id
803
804        Args:
805            vd_id (str): virtual drive id
806
807        Returns:
808            (None): no virtual drive with id
809            (:obj:VirtualDrive): virtual drive object
810        """
811        for vd in self:
812            if vd.id == vd_id:
813                return vd
814        return None

Get virtual drive object by id

Args: vd_id (str): virtual drive id

Returns: (None): no virtual drive with id (:obj:VirtualDrive): virtual drive object

def get_named_vd(self, vd_name):
816    def get_named_vd(self, vd_name):
817        """Get virtual drive object by name
818
819        Args:
820            vd_name (str): virtual drive name
821
822        Returns:
823            (None): no virtual drive with name
824            (:obj:VirtualDrive): virtual drive object
825        """
826        for vd in self:
827            if vd.name == vd_name:
828                return vd
829        return None

Get virtual drive object by name

Args: vd_name (str): virtual drive name

Returns: (None): no virtual drive with name (:obj:VirtualDrive): virtual drive object