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.1'
class StorCLI:
 27class StorCLI(object):
 28    """StorCLI command line wrapper
 29
 30    Instance of this class is storcli command line wrapper
 31
 32    Args:
 33        binary (str): storcli binary or full path to the binary
 34
 35    Properties:
 36        cache_enable (boolean): enable disable resposne cache (also setter)
 37        cache (dict): get / set raw cache content
 38
 39    Methods:
 40        run (dict): output data from command line
 41        check_response_status (): check ouput command line status from storcli
 42        clear_cache (): purge cache
 43
 44    TODO:
 45        * implement TTL for cache
 46
 47    """
 48    __singleton_instance = None
 49    __cache_lock = threading.Lock()
 50    __cache_enabled = False
 51    __response_cache = {}
 52    __cmdrunner = None
 53
 54    def __new__(cls, *args, **kwargs):
 55        """Thread safe singleton
 56        """
 57        global _SINGLETON_STORCLI_MODULE_LOCK
 58        with _SINGLETON_STORCLI_MODULE_LOCK:
 59            if _SINGLETON_STORCLI_MODULE_ENABLE:
 60                if StorCLI.__singleton_instance is None:
 61                    StorCLI.__singleton_instance = super(
 62                        StorCLI, cls).__new__(cls)
 63                return StorCLI.__singleton_instance
 64            else:
 65                return super(StorCLI, cls).__new__(cls)
 66
 67    def __init__(self, binary='storcli64', cmdrunner: cmdRunner.CMDRunner = None):
 68        """Constructor - create StorCLI object wrapper
 69
 70        Args:
 71            binary (str): storcli binary or full path to the binary
 72        """
 73
 74        if not _SINGLETON_STORCLI_MODULE_ENABLE or (_SINGLETON_STORCLI_MODULE_ENABLE and (not hasattr(self, '_StorCLI__cmdrunner') or self.__cmdrunner is None)):
 75            # Do not override __cmdrunner if it is already set in singleton mode
 76            if cmdrunner is None:
 77                if self.__cmdrunner is None:
 78                    self.__cmdrunner = cmdRunner.CMDRunner()
 79            else:
 80                self.__cmdrunner = cmdrunner
 81
 82        if _SINGLETON_STORCLI_MODULE_ENABLE:
 83            if not hasattr(self, '_storcli'):
 84                # do not override _storcli in singleton if already exist
 85                self._storcli = self.__cmdrunner.binaryCheck(binary)
 86
 87        if not _SINGLETON_STORCLI_MODULE_ENABLE:
 88            # dont share singleton lock and binary
 89            self._storcli = self.__cmdrunner.binaryCheck(binary)
 90            self.__cache_lock = threading.Lock()
 91
 92    def set_cmdrunner(self, cmdrunner: cmdRunner.CMDRunner):
 93        """
 94        Set command runner object.
 95        This is only useful for testing.
 96        """
 97        self.__cmdrunner = cmdrunner
 98
 99    @property
100    def cache_enable(self):
101        """Enable/Disable resposne cache (atomic)
102
103        Returns:
104            bool: true/false
105        """
106
107        return self.__cache_enabled
108
109    @cache_enable.setter
110    def cache_enable(self, value):
111        with self.__cache_lock:
112            self.__cache_enabled = value
113
114    def clear_cache(self):
115        """Clear cache (atomic)
116        """
117        with self.__cache_lock:
118            self.__response_cache = {}
119
120    @property
121    def cache(self):
122        """Get/Set raw cache
123
124        Args:
125            (dict): raw cache
126
127        Returns:
128            (dict): cache
129        """
130        return self.__response_cache
131
132    @cache.setter
133    def cache(self, value):
134        with self.__cache_lock:
135            self.__response_cache = value
136
137    @staticmethod
138    def check_response_status(cmd, out):
139        """Check ouput command line status from storcli.
140
141        Args:
142            cmd (list of str): full command line
143            out (dict): output from command line
144
145        Raises:
146            StorCliCmdError
147        """
148        cmd_status = common.response_cmd(out)
149        if cmd_status['Status'] == 'Failure':
150            if 'Detailed Status' in cmd_status:
151                raise exc.StorCliCmdError(
152                    cmd, "{0}".format(cmd_status['Detailed Status']))
153            else:
154                raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))
155
156    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
157        """Execute storcli command line with arguments.
158
159        Run command line and check output for errors.
160
161        Args:
162            args (list of str): cmd line arguments (without binary)
163            stdout (fd): controll subprocess stdout fd
164            stderr (fd): controll subporcess stderr fd
165            **kwargs: arguments to subprocess run
166
167        Returns:
168            dict: output data from command line
169
170        Raises:
171            exc.StorCliCmdError
172            exc.StorCliRunTimeError
173            exc.StorCliRunTimeout
174        """
175        cmd = [self._storcli]
176        cmd.extend(args)
177        # output in JSON format
178        cmd.append('J')
179        cmd_cache_key = ''.join(cmd)
180
181        if self.cache_enable:
182            if cmd_cache_key in self.__response_cache:
183                return self.__response_cache[cmd_cache_key]
184
185        with self.__cache_lock:
186            try:
187                ret = self.__cmdrunner.run(
188                    args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs)
189                try:
190                    ret_json = json.loads(ret.stdout)
191                    self.check_response_status(cmd, ret_json)
192                    ret.check_returncode()
193                    if self.cache_enable:
194                        self.__response_cache[cmd_cache_key] = ret_json
195                    return ret_json
196                except json.JSONDecodeError as err:
197                    # legacy handler (Ralequi: I don't know if this is still needed or what exactly it does)
198                    output = re.search('(^.*)Storage.*Command.*$',
199                                       ret.stdout, re.MULTILINE | re.DOTALL)
200                    if output:
201                        raise exc.StorCliCmdError(cmd, output.group(1))
202
203                    # Check if we can still parse the output
204                    parsed = {}
205                    for line in ret.stdout.splitlines():
206                        if '=' in line:
207                            key, value = line.split('=', 1)
208                            parsed[key.strip()] = value.strip()
209
210                    if 'Status' in parsed:
211                        return parsed
212                    else:
213                        raise exc.StorCliCmdError(cmd, str(err))
214
215            except subprocess.TimeoutExpired as err:
216                raise exc.StorCliRunTimeout(err)
217            except subprocess.SubprocessError as err:
218                raise exc.StorCliRunTimeError(err)
219
220    # Singleton stuff
221    @staticmethod
222    def __set_singleton(value):
223        global _SINGLETON_STORCLI_MODULE_ENABLE
224        global _SINGLETON_STORCLI_MODULE_LOCK
225        with _SINGLETON_STORCLI_MODULE_LOCK:
226            _SINGLETON_STORCLI_MODULE_ENABLE = value
227
228    @staticmethod
229    def enable_singleton():
230        """Enable StorCLI to be singleton on module level
231
232        Use StorCLI singleton across all objects. All pystorcli 
233        class instances use their own StorCLI object. With enabled cache
234        we can speedup for example metric lookups.
235
236        """
237        StorCLI.__set_singleton(True)
238
239    @staticmethod
240    def disable_singleton():
241        """Disable StoreCLI class as signleton
242        """
243        StorCLI.__set_singleton(False)
244
245    @staticmethod
246    def is_singleton() -> bool:
247        """Check if singleton is enabled
248        """
249        return _SINGLETON_STORCLI_MODULE_ENABLE
250
251    @property
252    def full_version(self) -> str:
253        """Get storcli version as storcli returns
254        """
255        out = self.run(['show'])
256        version = common.response_cmd(out)['CLI Version']
257
258        return version
259
260    @property
261    def version(self) -> str:
262        """Get storcli version in a cleaner way
263        """
264        import re
265
266        # Remove duplicated 0s
267        first_clean = re.sub('0+', '0', self.full_version.split(' ')[0])
268
269        # Remove leading 0s
270        second_clean = re.sub('^0+', '', first_clean)
271
272        return second_clean
273
274    @property
275    def controllers(self) -> 'pystorcli2.controller.Controllers':
276        """Get list of controllers
277        """
278        from . import Controllers
279
280        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: pystorcli2.cmdRunner.CMDRunner = None)
67    def __init__(self, binary='storcli64', cmdrunner: cmdRunner.CMDRunner = None):
68        """Constructor - create StorCLI object wrapper
69
70        Args:
71            binary (str): storcli binary or full path to the binary
72        """
73
74        if not _SINGLETON_STORCLI_MODULE_ENABLE or (_SINGLETON_STORCLI_MODULE_ENABLE and (not hasattr(self, '_StorCLI__cmdrunner') or self.__cmdrunner is None)):
75            # Do not override __cmdrunner if it is already set in singleton mode
76            if cmdrunner is None:
77                if self.__cmdrunner is None:
78                    self.__cmdrunner = cmdRunner.CMDRunner()
79            else:
80                self.__cmdrunner = cmdrunner
81
82        if _SINGLETON_STORCLI_MODULE_ENABLE:
83            if not hasattr(self, '_storcli'):
84                # do not override _storcli in singleton if already exist
85                self._storcli = self.__cmdrunner.binaryCheck(binary)
86
87        if not _SINGLETON_STORCLI_MODULE_ENABLE:
88            # dont share singleton lock and binary
89            self._storcli = self.__cmdrunner.binaryCheck(binary)
90            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):
92    def set_cmdrunner(self, cmdrunner: cmdRunner.CMDRunner):
93        """
94        Set command runner object.
95        This is only useful for testing.
96        """
97        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):
114    def clear_cache(self):
115        """Clear cache (atomic)
116        """
117        with self.__cache_lock:
118            self.__response_cache = {}

Clear cache (atomic)

cache

Get/Set raw cache

Args: (dict): raw cache

Returns: (dict): cache

@staticmethod
def check_response_status(cmd, out):
137    @staticmethod
138    def check_response_status(cmd, out):
139        """Check ouput command line status from storcli.
140
141        Args:
142            cmd (list of str): full command line
143            out (dict): output from command line
144
145        Raises:
146            StorCliCmdError
147        """
148        cmd_status = common.response_cmd(out)
149        if cmd_status['Status'] == 'Failure':
150            if 'Detailed Status' in cmd_status:
151                raise exc.StorCliCmdError(
152                    cmd, "{0}".format(cmd_status['Detailed Status']))
153            else:
154                raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))

Check ouput command line status from storcli.

Args: cmd (list of str): full command line out (dict): output from command line

Raises: StorCliCmdError

def run(self, args, stdout=-1, stderr=-1, **kwargs):
156    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
157        """Execute storcli command line with arguments.
158
159        Run command line and check output for errors.
160
161        Args:
162            args (list of str): cmd line arguments (without binary)
163            stdout (fd): controll subprocess stdout fd
164            stderr (fd): controll subporcess stderr fd
165            **kwargs: arguments to subprocess run
166
167        Returns:
168            dict: output data from command line
169
170        Raises:
171            exc.StorCliCmdError
172            exc.StorCliRunTimeError
173            exc.StorCliRunTimeout
174        """
175        cmd = [self._storcli]
176        cmd.extend(args)
177        # output in JSON format
178        cmd.append('J')
179        cmd_cache_key = ''.join(cmd)
180
181        if self.cache_enable:
182            if cmd_cache_key in self.__response_cache:
183                return self.__response_cache[cmd_cache_key]
184
185        with self.__cache_lock:
186            try:
187                ret = self.__cmdrunner.run(
188                    args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs)
189                try:
190                    ret_json = json.loads(ret.stdout)
191                    self.check_response_status(cmd, ret_json)
192                    ret.check_returncode()
193                    if self.cache_enable:
194                        self.__response_cache[cmd_cache_key] = ret_json
195                    return ret_json
196                except json.JSONDecodeError as err:
197                    # legacy handler (Ralequi: I don't know if this is still needed or what exactly it does)
198                    output = re.search('(^.*)Storage.*Command.*$',
199                                       ret.stdout, re.MULTILINE | re.DOTALL)
200                    if output:
201                        raise exc.StorCliCmdError(cmd, output.group(1))
202
203                    # Check if we can still parse the output
204                    parsed = {}
205                    for line in ret.stdout.splitlines():
206                        if '=' in line:
207                            key, value = line.split('=', 1)
208                            parsed[key.strip()] = value.strip()
209
210                    if 'Status' in parsed:
211                        return parsed
212                    else:
213                        raise exc.StorCliCmdError(cmd, str(err))
214
215            except subprocess.TimeoutExpired as err:
216                raise exc.StorCliRunTimeout(err)
217            except subprocess.SubprocessError as err:
218                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 **kwargs: arguments to subprocess run

Returns: dict: output data from command line

Raises: exc.StorCliCmdError exc.StorCliRunTimeError exc.StorCliRunTimeout

@staticmethod
def enable_singleton():
228    @staticmethod
229    def enable_singleton():
230        """Enable StorCLI to be singleton on module level
231
232        Use StorCLI singleton across all objects. All pystorcli 
233        class instances use their own StorCLI object. With enabled cache
234        we can speedup for example metric lookups.
235
236        """
237        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():
239    @staticmethod
240    def disable_singleton():
241        """Disable StoreCLI class as signleton
242        """
243        StorCLI.__set_singleton(False)

Disable StoreCLI class as signleton

@staticmethod
def is_singleton() -> bool:
245    @staticmethod
246    def is_singleton() -> bool:
247        """Check if singleton is enabled
248        """
249        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:
188class Controller(object):
189    """StorCLI Controller
190
191    Instance of this class represents controller in StorCLI hierarchy
192
193    Args:
194        ctl_id (str): controller id
195        binary (str): storcli binary or full path to the binary
196
197    Properties:
198        id (str): controller id
199        name (str): controller cmd name
200        facts (dict): raw controller facts
201        metrics (:obj:ControllerMetrics): controller metrics
202        vds (list of :obj:virtualdrive.VirtualDrives): controller virtual drives
203        encls (:obj:enclosure.Enclosures): controller enclosures
204        autorebuild (dict): current auto rebuild state (also setter)
205        foreignautoimport (dict): imports foreign configuration automatically at boot (also setter)
206        patrolread (dict): current patrol read settings (also setter)
207
208    Methods:
209        create_vd (:obj:VirtualDrive): create virtual drive
210        set_patrolread (dict): configures patrol read state and schedule
211        patrolread_start (dict): starts a patrol read on controller
212        patrolread_pause (dict): pauses patrol read on controller
213        patrolread_resume (dict): resumes patrol read on controller
214        patrolread_stop (dict): stops patrol read if running on controller
215        patrolread_running (bool): check if patrol read is running on controller
216
217    TODO:
218        Implement missing methods:
219            * patrol read progress
220    """
221
222    def __init__(self, ctl_id, binary='storcli64'):
223        """Constructor - create StorCLI Controller object
224
225        Args:
226            ctl_id (str): controller id
227            binary (str): storcli binary or full path to the binary
228        """
229        self._ctl_id = ctl_id
230        self._binary = binary
231        self._storcli = StorCLI(binary)
232        self._name = '/c{0}'.format(self._ctl_id)
233
234        self._exist()
235
236    def __str__(self):
237        return '{0}'.format(common.response_data(self._run(['show'])))
238
239    def _run(self, args, **kwargs):
240        args = args[:]
241        args.insert(0, self._name)
242        return self._storcli.run(args, **kwargs)
243
244    def _exist(self):
245        try:
246            self._run(['show'])
247        except exc.StorCliCmdError:
248            raise exc.StorCliMissingError(
249                self.__class__.__name__, self._name) from None
250
251    @property
252    def id(self):
253        """ (str): controller id
254        """
255        return self._ctl_id
256
257    @property
258    def name(self):
259        """ (str): controller cmd name
260        """
261        return self._name
262
263    @property
264    def facts(self):
265        """ (dict): raw controller facts
266        """
267        args = [
268            'show',
269            'all'
270        ]
271        return common.response_data(self._run(args))
272
273    @property
274    def metrics(self):
275        """(:obj:ControllerMetrics): controller metrics
276        """
277        return ControllerMetrics(ctl=self)
278
279    @property
280    def vds(self):
281        """(:obj:virtualdrive.VirtualDrives): controllers virtual drives
282        """
283        return virtualdrive.VirtualDrives(ctl_id=self._ctl_id, binary=self._binary)
284
285    @property
286    def encls(self):
287        """(:obj:enclosure.Enclosures): controller enclosures
288        """
289        return enclosure.Enclosures(ctl_id=self._ctl_id, binary=self._binary)
290
291    @property
292    def drives_ids(self) -> List[str]:
293        """(list of str): list of drives ids in format (e:s)
294        """
295        drives = []
296        for encl in self.encls:
297            for id in encl.drives.ids:
298                drives.append("{enc}:{id}".format(enc=encl.id, id=id))
299
300        return drives
301
302    def create_vd(self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[virtualdrive.VirtualDrive]:
303        """Create virtual drive (raid) managed by current controller
304
305        Args:
306            name (str): virtual drive name
307            raid (str): virtual drive raid level (raid0, raid1, ...)
308            drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z)
309            strip (str, optional): virtual drive raid strip size
310
311        Returns:
312            (None): no virtual drive created with name
313            (:obj:virtualdrive.VirtualDrive)
314        """
315        args = [
316            'add',
317            'vd',
318            'r{0}'.format(raid),
319            'name={0}'.format(name),
320            'drives={0}'.format(drives),
321            'strip={0}'.format(strip)
322        ]
323
324        try:
325            if int(raid) >= 10 and PDperArray is None:
326                # Try to count the number of drives in the array
327                # The format of the drives argument is e:s|e:s-x|e:s-x,y;e:s-x,y,z
328
329                numDrives = common.count_drives(drives)
330
331                if numDrives % 2 != 0 and numDrives % 3 == 0:
332                    # In some scenarios, such as 9 drives with raid 60, 3 is a good pd number but 4 is not
333                    # Must check for similar scenarios
334                    # BTW we don't clearly understand what PDperArray is for and what exactly it does under the hood. More investigation is needed
335                    PDperArray = numDrives//3
336                else:
337                    PDperArray = numDrives//2
338
339        except ValueError:
340            pass
341
342        finally:
343            if raid == '00' and PDperArray is None:
344                PDperArray = 1
345
346        if PDperArray is not None:
347            args.append('PDperArray={0}'.format(PDperArray))
348
349        self._run(args)
350        for vd in self.vds:
351            if name == vd.name:
352                return vd
353        return None
354
355    @property
356    @common.lower
357    def autorebuild(self):
358        """Get/Set auto rebuild state
359
360        One of the following options can be set (str):
361            on - enables autorebuild
362            off - disables autorebuild
363
364        Returns:
365            (str): on / off
366        """
367        args = [
368            'show',
369            'autorebuild'
370        ]
371
372        prop = common.response_property(self._run(args))[0]
373        return prop['Value']
374
375    @autorebuild.setter
376    def autorebuild(self, value):
377        """
378        """
379        args = [
380            'set',
381            'autorebuild={0}'.format(value)
382        ]
383        return common.response_setter(self._run(args))
384
385    @property
386    @common.lower
387    def foreignautoimport(self):
388        """Get/Set auto foreign import configuration
389
390        One of the following options can be set (str):
391            on - enables foreignautoimport
392            off - disables foreignautoimport
393
394        Returns:
395            (str): on / off
396        """
397        args = [
398            'show',
399            'foreignautoimport'
400        ]
401        prop = common.response_property(self._run(args))[0]
402        return prop['Value']
403
404    @foreignautoimport.setter
405    def foreignautoimport(self, value):
406        """
407        """
408        args = [
409            'set',
410            'foreignautoimport={0}'.format(value)
411        ]
412        return common.response_setter(self._run(args))
413
414    @property
415    @common.lower
416    def patrolread(self):
417        """Get/Set patrol read
418
419        One of the following options can be set (str):
420            on - enables patrol read
421            off - disables patrol read
422
423        Returns:
424            (str): on / off
425        """
426        args = [
427            'show',
428            'patrolread'
429        ]
430
431        for pr in common.response_property(self._run(args)):
432            if pr['Ctrl_Prop'] == "PR Mode":
433                if pr['Value'] == 'Disable':
434                    return 'off'
435                else:
436                    return 'on'
437        return 'off'
438
439
440    @patrolread.setter
441    def patrolread(self, value):
442        """
443        """
444        return self.set_patrolread(value)
445
446
447    def set_patrolread(self, value, mode='manual'):
448        """Set patrol read
449
450        Args:
451            value (str): on / off to configure patrol read state
452            mode (str): auto | manual to configure patrol read schedule
453        """
454        args = [
455            'set',
456            'patrolread={0}'.format(value)
457        ]
458
459        if value == 'on':
460            args.append('mode={0}'.format(mode))
461
462        return common.response_setter(self._run(args))
463
464    def patrolread_start(self):
465        """Starts the patrol read operation of the controller
466
467        Returns:
468            (dict): response cmd data
469        """
470        args = [
471            'start',
472            'patrolread'
473        ]
474        return common.response_cmd(self._run(args))
475
476    def patrolread_stop(self):
477        """Stops the patrol read operation of the controller
478
479        Returns:
480            (dict): response cmd data
481        """
482        args = [
483            'stop',
484            'patrolread'
485        ]
486        return common.response_cmd(self._run(args))
487
488    def patrolread_pause(self):
489        """Pauses the patrol read operation of the controller
490
491        Returns:
492            (dict): response cmd data
493        """
494        args = [
495            'pause',
496            'patrolread'
497        ]
498        return common.response_cmd(self._run(args))
499
500    def patrolread_resume(self):
501        """Resumes the patrol read operation of the controller
502
503        Returns:
504            (dict): response cmd data
505        """
506        args = [
507            'resume',
508            'patrolread'
509        ]
510        return common.response_cmd(self._run(args))
511
512    @property
513    def patrolread_running(self):
514        """Check if patrol read is running on the controller
515
516        Returns:
517            (bool): true / false
518        """
519        args = [
520            'show',
521            'patrolread'
522        ]
523
524        status = ''
525        for pr in common.response_property(self._run(args)):
526            if pr['Ctrl_Prop'] == "PR Current State":
527                status = pr['Value']
528        return bool('Active' in status)

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)

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

TODO: Implement missing methods: * patrol read progress

Controller(ctl_id, binary='storcli64')
222    def __init__(self, ctl_id, binary='storcli64'):
223        """Constructor - create StorCLI Controller object
224
225        Args:
226            ctl_id (str): controller id
227            binary (str): storcli binary or full path to the binary
228        """
229        self._ctl_id = ctl_id
230        self._binary = binary
231        self._storcli = StorCLI(binary)
232        self._name = '/c{0}'.format(self._ctl_id)
233
234        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]:
302    def create_vd(self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[virtualdrive.VirtualDrive]:
303        """Create virtual drive (raid) managed by current controller
304
305        Args:
306            name (str): virtual drive name
307            raid (str): virtual drive raid level (raid0, raid1, ...)
308            drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z)
309            strip (str, optional): virtual drive raid strip size
310
311        Returns:
312            (None): no virtual drive created with name
313            (:obj:virtualdrive.VirtualDrive)
314        """
315        args = [
316            'add',
317            'vd',
318            'r{0}'.format(raid),
319            'name={0}'.format(name),
320            'drives={0}'.format(drives),
321            'strip={0}'.format(strip)
322        ]
323
324        try:
325            if int(raid) >= 10 and PDperArray is None:
326                # Try to count the number of drives in the array
327                # The format of the drives argument is e:s|e:s-x|e:s-x,y;e:s-x,y,z
328
329                numDrives = common.count_drives(drives)
330
331                if numDrives % 2 != 0 and numDrives % 3 == 0:
332                    # In some scenarios, such as 9 drives with raid 60, 3 is a good pd number but 4 is not
333                    # Must check for similar scenarios
334                    # BTW we don't clearly understand what PDperArray is for and what exactly it does under the hood. More investigation is needed
335                    PDperArray = numDrives//3
336                else:
337                    PDperArray = numDrives//2
338
339        except ValueError:
340            pass
341
342        finally:
343            if raid == '00' and PDperArray is None:
344                PDperArray = 1
345
346        if PDperArray is not None:
347            args.append('PDperArray={0}'.format(PDperArray))
348
349        self._run(args)
350        for vd in self.vds:
351            if name == vd.name:
352                return vd
353        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'):
447    def set_patrolread(self, value, mode='manual'):
448        """Set patrol read
449
450        Args:
451            value (str): on / off to configure patrol read state
452            mode (str): auto | manual to configure patrol read schedule
453        """
454        args = [
455            'set',
456            'patrolread={0}'.format(value)
457        ]
458
459        if value == 'on':
460            args.append('mode={0}'.format(mode))
461
462        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):
464    def patrolread_start(self):
465        """Starts the patrol read operation of the controller
466
467        Returns:
468            (dict): response cmd data
469        """
470        args = [
471            'start',
472            'patrolread'
473        ]
474        return common.response_cmd(self._run(args))

Starts the patrol read operation of the controller

Returns: (dict): response cmd data

def patrolread_stop(self):
476    def patrolread_stop(self):
477        """Stops the patrol read operation of the controller
478
479        Returns:
480            (dict): response cmd data
481        """
482        args = [
483            'stop',
484            'patrolread'
485        ]
486        return common.response_cmd(self._run(args))

Stops the patrol read operation of the controller

Returns: (dict): response cmd data

def patrolread_pause(self):
488    def patrolread_pause(self):
489        """Pauses the patrol read operation of the controller
490
491        Returns:
492            (dict): response cmd data
493        """
494        args = [
495            'pause',
496            'patrolread'
497        ]
498        return common.response_cmd(self._run(args))

Pauses the patrol read operation of the controller

Returns: (dict): response cmd data

def patrolread_resume(self):
500    def patrolread_resume(self):
501        """Resumes the patrol read operation of the controller
502
503        Returns:
504            (dict): response cmd data
505        """
506        args = [
507            'resume',
508            'patrolread'
509        ]
510        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

class Controllers:
531class Controllers(object):
532    """StorCLI Controllers
533
534    Instance of this class is iterable with :obj:Controller as item
535
536    Args:
537        binary (str): storcli binary or full path to the binary
538
539    Properties:
540        ids (list of str): list of controllers id
541
542    Methods:
543        get_clt (:obj:Controller): return controller object by id
544    """
545
546    def __init__(self, binary='storcli64'):
547        """Constructor - create StorCLI Controllers object
548
549        Args:
550            binary (str): storcli binary or full path to the binary
551        """
552        self._binary = binary
553        self._storcli = StorCLI(binary)
554
555    @ property
556    def _ctl_ids(self) -> List[str]:
557        out = self._storcli.run(['show'])
558        response = common.response_data(out)
559
560        if "Number of Controllers" in response and response["Number of Controllers"] == 0:
561            return []
562        else:
563            return [ctl['Ctl'] for ctl in common.response_data_subkey(out, ['System Overview', 'IT System Overview'])]
564
565    @ property
566    def _ctls(self):
567        for ctl_id in self._ctl_ids:
568            yield Controller(ctl_id=ctl_id, binary=self._binary)
569
570    def __iter__(self):
571        return self._ctls
572
573    @ property
574    def ids(self):
575        """(list of str): controllers id
576        """
577        return self._ctl_ids
578
579    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
580        """Get controller object by id
581
582        Args:
583            ctl_id (str): controller id
584
585        Returns:
586            (None): no controller with id
587            (:obj:Controller): controller object
588        """
589        for ctl in self:
590            if ctl.id == ctl_id:
591                return ctl
592        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')
546    def __init__(self, binary='storcli64'):
547        """Constructor - create StorCLI Controllers object
548
549        Args:
550            binary (str): storcli binary or full path to the binary
551        """
552        self._binary = binary
553        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]:
579    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
580        """Get controller object by id
581
582        Args:
583            ctl_id (str): controller id
584
585        Returns:
586            (None): no controller with id
587            (:obj:Controller): controller object
588        """
589        for ctl in self:
590            if ctl.id == ctl_id:
591                return ctl
592        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):
 22class DriveState(Enum):
 23    """Drive status
 24    """
 25    # From storcli 7.1704
 26    # EID=Enclosure Device ID|Slt=Slot No|DID=Device ID|DG=DriveGroup
 27    # DHS=Dedicated Hot Spare|UGood=Unconfigured Good|GHS=Global Hotspare
 28    # UBad=Unconfigured Bad|Sntze=Sanitize|Onln=Online|Offln=Offline|Intf=Interface
 29    # Med=Media Type|SED=Self Encryptive Drive|PI=Protection Info
 30    # SeSz=Sector Size|Sp=Spun|U=Up|D=Down|T=Transition|F=Foreign
 31    # UGUnsp=UGood Unsupported|UGShld=UGood shielded|HSPShld=Hotspare shielded
 32    # CFShld=Configured shielded|Cpybck=CopyBack|CBShld=Copyback Shielded
 33    # UBUnsp=UBad Unsupported|Rbld=Rebuild
 34
 35    DHS = 'Dedicated Hot Spare'
 36    UGood = 'Unconfigured Good'
 37    GHS = 'Global Hotspare'
 38    UBad = 'Unconfigured Bad'
 39    Sntze = 'Sanitize'
 40    Onln = 'Online'
 41    Offln = 'Offline'
 42    Failed = 'Failed'
 43    SED = 'Self Encryptive Drive'
 44    UGUnsp = 'UGood Unsupported'
 45    UGShld = 'UGood shielded'
 46    HSPShld = 'Hotspare shielded'
 47    CFShld = 'Configured shielded'
 48    Cpybck = 'CopyBack'
 49    CBShld = 'Copyback Shielded'
 50    UBUnsp = 'UBad Unsupported'
 51    Rbld = 'Rebuild'
 52    Missing = 'Missing'
 53    JBOD = 'JBOD'
 54
 55    def __str__(self) -> str:
 56        return self.value
 57
 58    def is_good(self) -> bool:
 59        """Check if drive is good according to status"""
 60        good_states = [
 61            DriveState.DHS,
 62            DriveState.UGood,
 63            DriveState.GHS,
 64            # DriveState.Sntze, ??
 65            DriveState.Onln,
 66            DriveState.SED,
 67            # DriveState.UGUnsp, ??
 68            DriveState.UGShld,
 69            DriveState.HSPShld,
 70            DriveState.CFShld,
 71            DriveState.Cpybck,
 72            DriveState.CBShld,
 73            DriveState.Rbld,
 74            DriveState.JBOD
 75        ]
 76
 77        return self in good_states
 78
 79    def is_configured(self) -> bool:
 80        """Check if drive is configured according to status"""
 81        configured_states = [
 82            DriveState.DHS,
 83            DriveState.GHS,
 84            # DriveState.Sntze, ??
 85            DriveState.Onln,
 86            DriveState.SED,
 87            # DriveState.UGShld, ??
 88            DriveState.HSPShld,
 89            DriveState.CFShld,
 90            DriveState.Cpybck,
 91            DriveState.CBShld,
 92            DriveState.Rbld,
 93            DriveState.JBOD
 94        ]
 95
 96        return self in configured_states
 97
 98    def is_settable(self) -> bool:
 99        """Check if this status can be directly set. Not all statuses can be set directly."""
100        # online | offline | missing | good
101
102        settable_states = [
103            DriveState.Onln,
104            DriveState.Offln,
105            DriveState.Missing,
106            DriveState.UGood,
107            DriveState.JBOD
108        ]
109
110        return self in settable_states
111
112    def settable_str(self) -> str:
113        """Get string representation of settable status. Storcli uses different strings for set command than for show command."""
114        if self == DriveState.Onln:
115            return 'online'
116        elif self == DriveState.Offln:
117            return 'offline'
118        elif self == DriveState.Missing:
119            return 'missing'
120        elif self == DriveState.UGood:
121            return 'good'
122        elif self == DriveState.JBOD:
123            return 'jbod'
124        else:
125            raise ValueError('This status is not settable')
126
127    @staticmethod
128    def from_string(status: str) -> 'DriveState':
129        """Get DriveState from string"""
130        for drive_status in DriveState:
131            if drive_status.name.lower() == status.lower() or drive_status.value.lower() == status.lower():
132                return drive_status
133        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:
58    def is_good(self) -> bool:
59        """Check if drive is good according to status"""
60        good_states = [
61            DriveState.DHS,
62            DriveState.UGood,
63            DriveState.GHS,
64            # DriveState.Sntze, ??
65            DriveState.Onln,
66            DriveState.SED,
67            # DriveState.UGUnsp, ??
68            DriveState.UGShld,
69            DriveState.HSPShld,
70            DriveState.CFShld,
71            DriveState.Cpybck,
72            DriveState.CBShld,
73            DriveState.Rbld,
74            DriveState.JBOD
75        ]
76
77        return self in good_states

Check if drive is good according to status

def is_configured(self) -> bool:
79    def is_configured(self) -> bool:
80        """Check if drive is configured according to status"""
81        configured_states = [
82            DriveState.DHS,
83            DriveState.GHS,
84            # DriveState.Sntze, ??
85            DriveState.Onln,
86            DriveState.SED,
87            # DriveState.UGShld, ??
88            DriveState.HSPShld,
89            DriveState.CFShld,
90            DriveState.Cpybck,
91            DriveState.CBShld,
92            DriveState.Rbld,
93            DriveState.JBOD
94        ]
95
96        return self in configured_states

Check if drive is configured according to status

def is_settable(self) -> bool:
 98    def is_settable(self) -> bool:
 99        """Check if this status can be directly set. Not all statuses can be set directly."""
100        # online | offline | missing | good
101
102        settable_states = [
103            DriveState.Onln,
104            DriveState.Offln,
105            DriveState.Missing,
106            DriveState.UGood,
107            DriveState.JBOD
108        ]
109
110        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:
112    def settable_str(self) -> str:
113        """Get string representation of settable status. Storcli uses different strings for set command than for show command."""
114        if self == DriveState.Onln:
115            return 'online'
116        elif self == DriveState.Offln:
117            return 'offline'
118        elif self == DriveState.Missing:
119            return 'missing'
120        elif self == DriveState.UGood:
121            return 'good'
122        elif self == DriveState.JBOD:
123            return 'jbod'
124        else:
125            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:
127    @staticmethod
128    def from_string(status: str) -> 'DriveState':
129        """Get DriveState from string"""
130        for drive_status in DriveState:
131            if drive_status.name.lower() == status.lower() or drive_status.value.lower() == status.lower():
132                return drive_status
133        raise ValueError('Invalid drive status: {0}'.format(status))

Get DriveState from string

Inherited Members
enum.Enum
name
value
class Drive:
301class Drive(object):
302    """StorCLI Drive
303
304    Instance of this class represents drive in StorCLI hierarchy
305
306    Args:
307        ctl_id (str): controller id
308        encl_id (str): enclosure id
309        slot_id (str): slot id
310        binary (str): storcli binary or full path to the binary
311
312    Properties:
313        id (str): drive id
314        name (str): drive cmd name
315        facts (dict): raw drive facts
316        metrics (dict): drive metrics for monitoring
317        size (str): drive size
318        interface (str): SATA / SAS
319        medium (str): SSD / HDD
320        model (str): drive model informations
321        serial (str): drive serial number
322        wwn (str): drive wwn
323        firmware (str): drive firmware version
324        device_speed (str): drive speed
325        linke_speed (str): drive connection link speed
326        ctl_id (str): drive controller id
327        ctl (:obj:controller.Controller): drive controller
328        encl_id (str): drive enclosure
329        encl (:obj:enclosure.Enclosure): drive enclosure
330        phyerrorcounters (dict): drive error counters (also setter)
331        state (str): drive state (also setter)
332        spin (str): drive spin state (also setter)
333
334
335    Methods:
336        init_start (dict): starts the initialization process on a drive
337        init_stop (dict): stops an initialization process running on a drive
338        init_running (bool): check if initialization is running on a drive
339        erase_start (dict): securely erases non-SED drive
340        erase_stop (dict): stops an erase process running on a drive
341        erase_running (bool): check if erase is running on a drive
342        hotparedrive_create (dict): add drive to hotspares
343        hotparedrive_delete (dict): delete drive from hotspare
344
345    TODO:
346        Implement missing methods:
347            * start rebuild
348            * stop rebuild
349            * pause rebuild
350            * resume rebuild
351            * rebuild running
352    """
353
354    def __init__(self, ctl_id, encl_id, slot_id, binary='storcli64'):
355        """Constructor - create StorCLI Drive object
356
357        Args:
358            ctl_id (str): controller id
359            encl_id (str): enclosure id
360            slot_id (str): slot id
361            binary (str): storcli binary or full path to the binary
362        """
363        self._ctl_id = ctl_id
364        self._encl_id = encl_id
365        self._slot_id = slot_id
366        self._binary = binary
367        self._storcli = StorCLI(binary)
368        self._name = '/c{0}/e{1}/s{2}'.format(self._ctl_id,
369                                              self._encl_id, self._slot_id)
370
371        self._exist()
372
373    @staticmethod
374    def _response_properties(out):
375        return common.response_data(out)['Drive Information'][0]
376
377    def _response_attributes(self, out):
378        detailed_info = ('Drive /c{0}/e{1}/s{2}'
379                         ' - Detailed Information'.format(self._ctl_id, self._encl_id, self._slot_id))
380        attr = 'Drive /c{0}/e{1}/s{2} Device attributes'.format(
381            self._ctl_id, self._encl_id, self._slot_id)
382        return common.response_data(out)[detailed_info][attr]
383
384    def _run(self, args, **kwargs):
385        args = args[:]
386        args.insert(0, self._name)
387        return self._storcli.run(args, **kwargs)
388
389    def _exist(self):
390        try:
391            self._run(['show'])
392        except exc.StorCliCmdError:
393            raise exc.StorCliMissingError(
394                self.__class__.__name__, self._name) from None
395
396    @property
397    def id(self):
398        """(str): drive id
399        """
400        return self._slot_id
401
402    @property
403    def name(self):
404        """(str): drive cmd name
405        """
406        return self._name
407
408    @property
409    def facts(self):
410        """(dict): raw drive facts
411        """
412        args = [
413            'show',
414            'all'
415        ]
416        return common.response_data(self._run(args))
417
418    @property
419    def metrics(self):
420        """(dict): drive metrics
421        """
422        return DriveMetrics(self)
423
424    @property
425    def size(self):
426        """(str): drive size
427        """
428        args = [
429            'show'
430        ]
431        return self._response_properties(self._run(args))['Size']
432
433    @property
434    @common.upper
435    def interface(self):
436        """(str): SATA / SAS
437        """
438        args = [
439            'show'
440        ]
441        return self._response_properties(self._run(args))['Intf']
442
443    @property
444    @common.upper
445    def medium(self):
446        """(str): SSD / HDD
447        """
448        args = [
449            'show'
450        ]
451        return self._response_properties(self._run(args))['Med']
452
453    @property
454    @common.upper
455    @common.strip
456    def model(self):
457        """(str): drive model informations
458        """
459        args = [
460            'show'
461        ]
462        return self._response_properties(self._run(args))['Model']
463
464    @property
465    @common.upper
466    @common.strip
467    def serial(self):
468        """(str): drive serial number
469        """
470        args = [
471            'show',
472            'all'
473        ]
474        return self._response_attributes(self._run(args))['SN']
475
476    @property
477    @common.upper
478    def wwn(self):
479        """(str): drive wwn
480        """
481        args = [
482            'show',
483            'all'
484        ]
485        return self._response_attributes(self._run(args))['WWN']
486
487    @property
488    @common.upper
489    def firmware(self):
490        """(str): drive firmware version
491        """
492        args = [
493            'show',
494            'all'
495        ]
496        return self._response_attributes(self._run(args))['Firmware Revision']
497
498    @property
499    def device_speed(self):
500        """(str): drive speed
501        """
502        args = [
503            'show',
504            'all'
505        ]
506        return self._response_attributes(self._run(args))['Device Speed']
507
508    @property
509    def link_speed(self):
510        """(str): drive connection link speed
511        """
512        args = [
513            'show',
514            'all'
515        ]
516        return self._response_attributes(self._run(args))['Link Speed']
517
518    @property
519    def ctl_id(self):
520        """(str): drive controller id
521        """
522        return self._ctl_id
523
524    @property
525    def ctl(self):
526        """(:obj:controller.Controller): drive controller
527        """
528        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
529
530    @property
531    def encl_id(self):
532        """(str): dirve enclosure id
533        """
534        return self._encl_id
535
536    @property
537    def encl(self):
538        """(:obj:enclosure.Enclosure): drive enclosure
539        """
540        return enclosure.Enclosure(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary)
541
542    @property
543    def vd_id(self) -> Union[None, int]:
544        """(int): drive virtual drive id if any
545        """
546        args = [
547            'show']
548        dg = self._response_properties(self._run(args))['DG']
549
550        if isinstance(dg, int):
551            return dg
552        else:
553            return None
554
555    @property
556    def vd(self) -> Union[None, virtualdrive.VirtualDrive]:
557        """(:obj:virtualdrive.VirtualDrive): get the virtual drive if any
558        """
559        if self.vd_id is None:
560            return None
561        else:
562            return virtualdrive.VirtualDrive(self._ctl_id, self.vd_id, self._binary)
563
564    def init_start(self):
565        """Start initialization of a drive
566
567        Returns:
568            (dict): resposne cmd data
569        """
570        args = [
571            'start',
572            'initialization'
573        ]
574        return common.response_cmd(self._run(args))
575
576    def init_stop(self):
577        """Stop initialization on a drive
578
579        A stopped initialization process cannot be resumed.
580
581        Returns:
582            (dict): resposne cmd data
583        """
584        args = [
585            'stop',
586            'initialization'
587        ]
588        return common.response_cmd(self._run(args))
589
590    @property
591    def init_running(self):
592        """Check if initialization process is running on a drive
593
594        Returns:
595            (bool): true / false
596        """
597        args = [
598            'show',
599            'initialization'
600        ]
601
602        status = common.response_data(self._run(args))[0]['Status']
603        return bool(status == 'In progress')
604
605    def erase_start(self, mode='simple'):
606        """Securely erases non-SED drives with specified erase pattern
607
608        Args:
609            mode (str):
610                simple		-	Single pass, single pattern write
611                normal		-	Three pass, three pattern write
612                thorough	-	Nine pass, repeats the normal write 3 times
613                standard	-	Applicable only for DFF's
614                threepass	-	Three pass, pass1 random pattern write, pass2,3 write zero, verify
615                crypto 	-	Applicable only for ISE capable drives
616                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
617
618        Returns:
619            (dict): resposne cmd data
620        """
621        args = [
622            'start',
623            'erase',
624            '{0}'.format(mode)
625        ]
626        return common.response_cmd(self._run(args))
627
628    def erase_stop(self):
629        """Stops the erase operation of a drive
630
631        Returns:
632            (dict): resposne cmd data
633        """
634        args = [
635            'stop',
636            'erase'
637        ]
638        return common.response_cmd(self._run(args))
639
640    @property
641    def erase_running(self):
642        """Check if erase process is running on a drive
643
644        Returns:
645            (bool): true / false
646        """
647        args = [
648            'show',
649            'erase'
650        ]
651
652        status = common.response_data(self._run(args))[0]['Status']
653        return bool(status == 'In progress')
654
655    @property
656    def phyerrorcounters(self):
657        """Get/Reset the drive phyerrorcounters
658
659        Reset drive error counters with (str) 0
660        """
661        args = [
662            'show',
663            'phyerrorcounters'
664        ]
665        return common.response_data(self._run(args))[self._name]
666
667    @phyerrorcounters.setter
668    def phyerrorcounters_reset(self):
669        """
670        """
671        args = [
672            'reset',
673            'phyerrorcounters'
674        ]
675        return common.response_cmd(self._run(args))
676
677    @property
678    def state(self) -> DriveState:
679        """Get/Set drive state
680        """
681        args = [
682            'show'
683        ]
684
685        state = self._response_properties(self._run(args))['State']
686
687        return DriveState.from_string(state)
688
689    @state.setter
690    def state(self, value: Union[str, DriveState]):
691        """ Set drive state
692        """
693
694        return self.set_state(value, force=False)
695
696    def set_state(self, value: Union[str, DriveState], force: bool = False):
697        """ Set drive state
698        """
699        # if DriveState, get the string value
700        if isinstance(value, DriveState):
701            value = value.settable_str()
702
703        args = [
704            'set',
705            '{0}'.format(value)
706        ]
707
708        if force:
709            args.append('force')
710
711        return common.response_setter(self._run(args))
712
713    @property
714    def spin(self):
715        """Get/Set drive spin status
716
717        One of the following states can be set (str):
718            up - spins up and set to unconfigured good
719            down - spins down an unconfigured drive and prepares it for removal
720
721        Returns:
722            (str): up / down
723        """
724        args = [
725            'show'
726        ]
727
728        spin = self._response_properties(self._run(args))['Sp']
729        if spin == 'U':
730            return 'up'
731        return 'down'
732
733    @spin.setter
734    def spin(self, value):
735        """
736        """
737        if value == 'up':
738            spin = 'spinup'
739        elif value == 'down':
740            spin = 'spindown'
741        else:
742            spin = value
743
744        args = [
745            '{0}'.format(spin)
746        ]
747        return common.response_setter(self._run(args))
748
749    def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False):
750        """Creates a hotspare drive
751
752        Args:
753            dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...)
754            enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with.
755                                 If this option is specified, affinity is set; if it is not specified,
756                                 there is no affinity.NOTE Affinity cannot be removed once it is set
757                                 for a hotspare drive.
758            nonrevertible (bool): sets the drive as a nonrevertible hotspare
759
760        Returns:
761            (dict): resposne cmd data
762        """
763        args = [
764            'add',
765            'hotsparedrive'
766        ]
767
768        if dgs:
769            args.append("dgs={0}".format(dgs))
770        if enclaffinity:
771            args.append('enclaffinity')
772        if nonrevertible:
773            args.append('nonrevertible')
774        return common.response_cmd(self._run(args))
775
776    def hotparedrive_delete(self):
777        """Deletes drive from hotspares
778
779        Returns:
780            (dict): resposne cmd data
781        """
782        args = [
783            'delete',
784            'hotsparedrive'
785        ]
786        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')
354    def __init__(self, ctl_id, encl_id, slot_id, binary='storcli64'):
355        """Constructor - create StorCLI Drive object
356
357        Args:
358            ctl_id (str): controller id
359            encl_id (str): enclosure id
360            slot_id (str): slot id
361            binary (str): storcli binary or full path to the binary
362        """
363        self._ctl_id = ctl_id
364        self._encl_id = encl_id
365        self._slot_id = slot_id
366        self._binary = binary
367        self._storcli = StorCLI(binary)
368        self._name = '/c{0}/e{1}/s{2}'.format(self._ctl_id,
369                                              self._encl_id, self._slot_id)
370
371        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

(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

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):
564    def init_start(self):
565        """Start initialization of a drive
566
567        Returns:
568            (dict): resposne cmd data
569        """
570        args = [
571            'start',
572            'initialization'
573        ]
574        return common.response_cmd(self._run(args))

Start initialization of a drive

Returns: (dict): resposne cmd data

def init_stop(self):
576    def init_stop(self):
577        """Stop initialization on a drive
578
579        A stopped initialization process cannot be resumed.
580
581        Returns:
582            (dict): resposne cmd data
583        """
584        args = [
585            'stop',
586            'initialization'
587        ]
588        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'):
605    def erase_start(self, mode='simple'):
606        """Securely erases non-SED drives with specified erase pattern
607
608        Args:
609            mode (str):
610                simple		-	Single pass, single pattern write
611                normal		-	Three pass, three pattern write
612                thorough	-	Nine pass, repeats the normal write 3 times
613                standard	-	Applicable only for DFF's
614                threepass	-	Three pass, pass1 random pattern write, pass2,3 write zero, verify
615                crypto 	-	Applicable only for ISE capable drives
616                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
617
618        Returns:
619            (dict): resposne cmd data
620        """
621        args = [
622            'start',
623            'erase',
624            '{0}'.format(mode)
625        ]
626        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):
628    def erase_stop(self):
629        """Stops the erase operation of a drive
630
631        Returns:
632            (dict): resposne cmd data
633        """
634        args = [
635            'stop',
636            'erase'
637        ]
638        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

Set drive state

def set_state( self, value: Union[str, pystorcli2.DriveState], force: bool = False):
696    def set_state(self, value: Union[str, DriveState], force: bool = False):
697        """ Set drive state
698        """
699        # if DriveState, get the string value
700        if isinstance(value, DriveState):
701            value = value.settable_str()
702
703        args = [
704            'set',
705            '{0}'.format(value)
706        ]
707
708        if force:
709            args.append('force')
710
711        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):
749    def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False):
750        """Creates a hotspare drive
751
752        Args:
753            dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...)
754            enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with.
755                                 If this option is specified, affinity is set; if it is not specified,
756                                 there is no affinity.NOTE Affinity cannot be removed once it is set
757                                 for a hotspare drive.
758            nonrevertible (bool): sets the drive as a nonrevertible hotspare
759
760        Returns:
761            (dict): resposne cmd data
762        """
763        args = [
764            'add',
765            'hotsparedrive'
766        ]
767
768        if dgs:
769            args.append("dgs={0}".format(dgs))
770        if enclaffinity:
771            args.append('enclaffinity')
772        if nonrevertible:
773            args.append('nonrevertible')
774        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):
776    def hotparedrive_delete(self):
777        """Deletes drive from hotspares
778
779        Returns:
780            (dict): resposne cmd data
781        """
782        args = [
783            'delete',
784            'hotsparedrive'
785        ]
786        return common.response_cmd(self._run(args))

Deletes drive from hotspares

Returns: (dict): resposne cmd data

class Drives:
789class Drives(object):
790    """StorCLI drives
791
792    Instance of this class is iterable with :obj:Drive as item
793
794    Args:
795        ctl_id (str): controller id
796        encl_id (str): enclosure id
797        binary (str): storcli binary or full path to the binary
798
799    Properties:
800        ids (list of str): list of drives id
801        ctl_id (str): controller id where drives are located
802        encl_id (str): enclosure id where drives are located
803        ctl (:obj:controller.Controller): controller
804        encl (:obj:Enclosure): enclosure
805
806
807    Methods:
808        get_drive (:obj:Enclosure): return drive object by id
809        get_drive_range_ids (list of int): return list of drive ids in range
810        get_drive_range (:obj:Drives): return drives object in range
811    """
812
813    def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'):
814        """Constructor - create StorCLI Enclosures object
815
816        Args:
817            ctl_id (str): controller id
818            binary (str): storcli binary or full path to the binary
819        """
820        self._ctl_id: int = ctl_id
821        self._encl_id: int = encl_id
822        self._binary: str = binary
823        self._storcli: StorCLI = StorCLI(binary)
824
825    @property
826    def _drive_ids(self) -> List[int]:
827        args = [
828            '/c{0}/e{1}/sall'.format(self._ctl_id, self._encl_id),
829            'show'
830        ]
831
832        if not self.encl.has_drives:
833            return []
834
835        drives = common.response_data(self._storcli.run(args))[
836            'Drive Information']
837        return [int(drive['EID:Slt'].split(':')[1]) for drive in drives]
838
839    @property
840    def _drives(self):
841        for drive_id in self._drive_ids:
842            yield Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
843
844    def __iter__(self):
845        return self._drives
846
847    @property
848    def ids(self) -> List[int]:
849        """(list of str): list of enclosures id
850        """
851        return self._drive_ids
852
853    @property
854    def ctl_id(self) -> int:
855        """(str): enclosures controller id
856        """
857        return self._ctl_id
858
859    @property
860    def ctl(self):
861        """(:obj:controller.Controller): enclosures controller
862        """
863        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
864
865    @property
866    def encl_id(self) -> int:
867        """(str): enclosure id
868        """
869        return self._encl_id
870
871    @property
872    def encl(self):
873        """(:obj:Enclosure): enclosure
874        """
875        return enclosure.Enclosure(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary)
876
877    def get_drive(self, drive_id: int) -> Optional[Drive]:
878        """Get drive object by id
879
880        Args:
881            drive_id (str): drive id
882
883        Returns:
884            (None): no drive with id
885            (:obj:Drive): drive object
886        """
887        if drive_id in self._drive_ids:
888            return Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
889        else:
890            return None
891
892    def __getitem__(self, drive_id: int) -> Optional[Drive]:
893        return self.get_drive(drive_id)
894
895    def get_drive_range_ids(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None) -> List[int]:
896        """Get drive range list in the current enclosure
897
898        Args:
899            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
900            drive_id_end (Optional[int]): end of the range
901        """
902
903        if drive_id_end:
904            # check that drive_id_begin is integer, if not raise exception
905            if not isinstance(drive_id_begin, int):
906                raise ValueError('drive_id_begin must be an integer')
907
908            # otherwise convert to string
909            drive_id_begin = '{0}-{1}'.format(drive_id_begin, drive_id_end)
910
911        # if drive_id_begin is an integer, convert to string
912        if isinstance(drive_id_begin, int):
913            drive_id_begin = str(drive_id_begin)
914
915        # get the list of drives
916        drive_ids = []
917        for drive_id in drive_id_begin.split(','):
918            if '-' in drive_id:
919                range_begin = drive_id.split('-')[0]
920                range_end = drive_id.split('-')[1]
921                drive_ids.extend(
922                    range(int(range_begin), int(range_end) + 1))
923            else:
924                drive_ids.append(int(drive_id))
925
926        return drive_ids
927
928    def get_drive_range(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None):
929        """Get drive range in the current enclosure
930
931        Args:
932            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
933            drive_id_end (Optional[int]): end of the range
934        """
935        drive_ids = self.get_drive_range_ids(drive_id_begin, drive_id_end)
936
937        for drive_id in drive_ids:
938            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')
813    def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'):
814        """Constructor - create StorCLI Enclosures object
815
816        Args:
817            ctl_id (str): controller id
818            binary (str): storcli binary or full path to the binary
819        """
820        self._ctl_id: int = ctl_id
821        self._encl_id: int = encl_id
822        self._binary: str = binary
823        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]:
877    def get_drive(self, drive_id: int) -> Optional[Drive]:
878        """Get drive object by id
879
880        Args:
881            drive_id (str): drive id
882
883        Returns:
884            (None): no drive with id
885            (:obj:Drive): drive object
886        """
887        if drive_id in self._drive_ids:
888            return Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
889        else:
890            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]:
895    def get_drive_range_ids(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None) -> List[int]:
896        """Get drive range list in the current enclosure
897
898        Args:
899            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
900            drive_id_end (Optional[int]): end of the range
901        """
902
903        if drive_id_end:
904            # check that drive_id_begin is integer, if not raise exception
905            if not isinstance(drive_id_begin, int):
906                raise ValueError('drive_id_begin must be an integer')
907
908            # otherwise convert to string
909            drive_id_begin = '{0}-{1}'.format(drive_id_begin, drive_id_end)
910
911        # if drive_id_begin is an integer, convert to string
912        if isinstance(drive_id_begin, int):
913            drive_id_begin = str(drive_id_begin)
914
915        # get the list of drives
916        drive_ids = []
917        for drive_id in drive_id_begin.split(','):
918            if '-' in drive_id:
919                range_begin = drive_id.split('-')[0]
920                range_end = drive_id.split('-')[1]
921                drive_ids.extend(
922                    range(int(range_begin), int(range_end) + 1))
923            else:
924                drive_ids.append(int(drive_id))
925
926        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):
928    def get_drive_range(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None):
929        """Get drive range in the current enclosure
930
931        Args:
932            drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer
933            drive_id_end (Optional[int]): end of the range
934        """
935        drive_ids = self.get_drive_range_ids(drive_id_begin, drive_id_end)
936
937        for drive_id in drive_ids:
938            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:
118class VirtualDrive(object):
119    """StorCLI VirtualDrive
120
121    Instance of this class represents virtual drive (RAID) in StorCLI hierarchy
122
123    Args:
124        ctl_id (str): controller id
125        vd_id (str): virtual drive id
126        binary (str): storcli binary or full path to the binary
127
128    Properties:
129        id (str): virtual drive id
130        facts (dict): raw virtual drive facts
131        metrics (dict): virtual drive metrics
132        raid (str): vitual drive raid level
133        size (str): virtual drive size
134        state (str): virtual drive state
135        strip (str): virtual drive strip size
136        os_exposed (bool): virtual drive exposed to the OS
137        os_name (str): virtual drive device path (/dev/...)
138        ctl_id (str): virtual drive controller
139        ctl (:obj:controller.Controller): virtual drive controller
140        drives (list of :obj:drive.Drive): virtual drive drives
141        name (str): virtual drive name (also setter)
142        bootdrive (str): virtual drive bootdrive (also setter)
143        pdcache (str): current disk cache policy on a virtual drive (also setter)
144        wrcache (str): write cache policy on a virtual drive (also setter)
145        rdcache (str): read cache policy on a virtual drive (also setter)
146        iopolicy (str): I/O policy on a virtual drive (also setter)
147        autobgi (str):virtual drive auto background initialization setting (also setter)
148
149    Methods:
150        init_start (dict): starts the initialization process on a virtual drive
151        init_stop (dict): stops an initialization process running on a virtual drive
152        init_running (bool): check if initialization is running on a virtual drive
153        erase_start (dict): securely erases non-SED virtual drive
154        erase_stop (dict): stops an erase process running on a virtual drive
155        erase_running (bool): check if erase is running on a virtual drive
156        erase_progress (str): % progress of erase on a virtual drive
157        delete (dict): delete virtual drive
158        migrate_start (dict): starts the migration process on a virtual drive
159        migrate_stop (dict): stops an migration process running on a virtual drive
160        migrate_running (bool): check if migrate is running on a virtual drive
161
162    TODO:
163        Implement missing methods:
164            * start cc
165            * stop cc
166            * pause cc
167            * resume cc
168            * cc running
169    """
170
171    def __init__(self, ctl_id, vd_id, binary='storcli64'):
172        """Constructor - create StorCLI VirtualDrive object
173
174        Args:
175            ctl_id (str): controller id
176            vd_id (str): virtual drive id
177            binary (str): storcli binary or full path to the binary
178        """
179        self._ctl_id = ctl_id
180        self._vd_id = vd_id
181        self._binary = binary
182        self._storcli = StorCLI(binary)
183        self._name = '/c{0}/v{1}'.format(self._ctl_id, self._vd_id)
184
185        self._exist()
186
187    def _run(self, args, **kwargs):
188        args = args[:]
189        args.insert(0, self._name)
190        return self._storcli.run(args, **kwargs)
191
192    def _exist(self):
193        try:
194            self._run(['show'])
195        except exc.StorCliCmdError:
196            raise exc.StorCliMissingError(
197                self.__class__.__name__, self._name) from None
198
199    @staticmethod
200    def _response_properties(out):
201        return common.response_data(out)['Virtual Drives'][0]
202
203    def _response_properties_all(self, out):
204        return common.response_data(out)['VD{0} Properties'.format(self._vd_id)]
205
206    @staticmethod
207    def _resposne_operation_status(out):
208        return common.response_data(out)['VD Operation Status'][0]
209
210    @property
211    def id(self):
212        """(str): virtual drive id
213        """
214        return self._vd_id
215
216    @property
217    def facts(self):
218        """(dict): raw virtual drive facts
219        """
220        args = [
221            'show',
222            'all'
223        ]
224        return common.response_data(self._run(args))
225
226    @property
227    def metrics(self):
228        """(:obj:VirtualDriveMetrics): virtual drive metrics
229        """
230        return VirtualDriveMetrics(self)
231
232    @property
233    @common.lower
234    def raid(self):
235        """(str): virtual drive raid level
236        """
237        args = [
238            'show'
239        ]
240
241        return self._response_properties(self._run(args))['TYPE']
242
243    @property
244    def size(self):
245        """(str): virtual drive size
246        """
247        args = [
248            'show'
249        ]
250        return self._response_properties(self._run(args))['Size']
251
252    @property
253    @common.lower
254    def state(self):
255        """(str): virtual drive state (optimal | recovery | offline | degraded | degraded_partially)
256        """
257        args = [
258            'show'
259        ]
260        state = self._response_properties(self._run(args))['State']
261        if state == 'Optl':
262            return 'optimal'
263        elif state == 'Rec':
264            return 'recovery'
265        elif state == 'OfLn':
266            return 'offline'
267        elif state == 'Pdgd':
268            return 'degraded_partially'
269        return 'degraded'
270
271    @property
272    def strip(self):
273        """(str): virtual drive strip size
274        """
275        args = [
276            'show',
277            'all'
278        ]
279
280        size = self._response_properties_all(self._run(args))['Strip Size']
281        return size.split()[0]
282
283    @property
284    def os_exposed(self):
285        """(bool): virtual drive exposed to the OS
286        """
287        args = [
288            'show',
289            'all'
290        ]
291
292        exposed = self._response_properties_all(self._run(args))[
293            'Exposed to OS']
294        return bool(exposed == 'Yes')
295
296    @property
297    @common.lower
298    def os_name(self):
299        """(str): virtual drive device path (/dev/...)
300        """
301        args = [
302            'show',
303            'all'
304        ]
305        return self._response_properties_all(self._run(args))['OS Drive Name']
306
307    @property
308    def ctl_id(self):
309        """(str): virtual drive controller id
310        """
311        return self._ctl_id
312
313    @property
314    def ctl(self):
315        """(:obj:controller.Controller): virtual drive controller
316        """
317        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)
318
319    @property
320    def drives(self):
321        """(list of :obj:Drive): drives
322        """
323        args = [
324            'show',
325            'all'
326        ]
327
328        drives = []
329        pds = common.response_data(self._run(args))[
330            'PDs for VD {0}'.format(self._vd_id)]
331        for pd in pds:
332            drive_encl_id, drive_slot_id = pd['EID:Slt'].split(':')
333            drives.append(
334                drive.Drive(
335                    ctl_id=self._ctl_id,
336                    encl_id=drive_encl_id,
337                    slot_id=drive_slot_id,
338                    binary=self._binary
339                )
340            )
341        return drives
342
343    @property
344    def name(self):
345        """Get/Set virtual drive name
346
347        The name is restricted to 15 characters.
348
349        Returns:
350            (str): raid name
351        """
352        args = [
353            'show',
354        ]
355
356        properties = self._response_properties(self._run(args))
357        return properties['Name']
358
359    @name.setter
360    def name(self, value):
361        """
362        """
363        args = [
364            'set',
365            'name={0}'.format(value)
366        ]
367        return common.response_setter(self._run(args))
368
369    @property
370    def bootdrive(self):
371        """Get/Set virtual drive as Boot Drive
372
373        One of the following options can be set (str):
374            on - enable boot virtual drive
375            off - disable boot virtual dirve
376
377        Returns:
378            (str): on / off
379        """
380        args = [
381            '/c{0}'.format(self._ctl_id),
382            'show',
383            'bootdrive'
384        ]
385
386        for vd in common.response_property(self._storcli.run(args)):
387            if vd['Value'] == 'VD:{0}'.format(self._vd_id):
388                return 'on'
389        return 'off'
390
391    @bootdrive.setter
392    def bootdrive(self, value):
393        """
394        """
395        args = [
396            'set',
397            'bootdrive={0}'.format(value)
398        ]
399        return common.response_setter(self._run(args))
400
401    @property
402    def pdcache(self):
403        """Get/Set PD Cache Setting
404
405        One of the following options can be set (str):
406            on - enables PD Caching
407            off - disables PD Caching
408            default - default PD Caching
409
410        Returns:
411            (str): on / off
412        """
413        args = [
414            'show',
415            'all'
416        ]
417
418        properties = self._response_properties_all(self._run(args))
419        if properties['Disk Cache Policy'] == 'Enabled':
420            return 'on'
421        elif 'Default' in properties['Disk Cache Policy']:
422            return 'default'
423        return 'off'
424
425    @pdcache.setter
426    def pdcache(self, value):
427        """
428        """
429        args = [
430            'set',
431            'pdcache={0}'.format(value)
432        ]
433        return common.response_setter(self._run(args))
434
435    @property
436    def wrcache(self):
437        """Get/Set Write cache setting
438
439        One of the following options can be set (str):
440            wt - write Through
441            wb - write Back
442            awb - write Back even in case of bad BBU also
443
444        Returns:
445            (str): wt / wb / awb
446        """
447        args = [
448            'show',
449        ]
450
451        properties = self._response_properties(self._run(args))
452        if 'AWB' in properties['Cache']:
453            return 'awb'
454        elif 'WB' in properties['Cache']:
455            return 'wb'
456        return 'wt'
457
458    @wrcache.setter
459    def wrcache(self, value):
460        """
461        """
462        args = [
463            'set',
464            'wrcache={0}'.format(value)
465        ]
466        return common.response_setter(self._run(args))
467
468    @property
469    def rdcache(self):
470        """Get/Set Read cache setting
471
472        One of the following options can be set (str):
473            ra - Read Ahead
474            nora - No Read Ahead
475
476        Returns:
477            (str): ra / nora
478        """
479        args = [
480            'show',
481        ]
482
483        properties = self._response_properties(self._run(args))
484        if properties['Cache'][0:2] == 'NR':
485            return 'nora'
486        return 'ra'
487
488    @rdcache.setter
489    def rdcache(self, value):
490        """
491        """
492        args = [
493            'set',
494            'rdcache={0}'.format(value)
495        ]
496        return common.response_setter(self._run(args))
497
498    @property
499    def iopolicy(self):
500        """Get/Set iopolicy setting
501
502        One of the following options can be set (str):
503            cached - IOs are cached
504            direct - IOs are not cached
505
506        Returns:
507            (str): cached / direct
508        """
509        args = [
510            'show',
511        ]
512
513        properties = self._response_properties(self._run(args))
514        if properties['Cache'][-1] == 'D':
515            return 'direct'
516        return 'cached'
517
518    @iopolicy.setter
519    @common.lower
520    def iopolicy(self, value):
521        """
522        """
523        args = [
524            'set',
525            'iopolicy={0}'.format(value)
526        ]
527        return common.response_setter(self._run(args))
528
529    @property
530    @common.lower
531    def autobgi(self):
532        """Get/Set auto background initialization
533
534        One of the following options can be set (str):
535            on - enables autobgi
536            off - disables autobgi
537
538        Returns:
539            (str): on / off
540        """
541        args = [
542            'show',
543            'autobgi'
544        ]
545        return self._resposne_operation_status(self._run(args))['AutoBGI']
546
547    @autobgi.setter
548    def autobgi(self, value):
549        """
550        """
551        args = [
552            'set',
553            'autobgi={0}'.format(value)
554        ]
555        return common.response_setter(self._run(args))
556
557    def init_start(self, full=False, force=False):
558        """Starts the initialization of a virtual drive
559
560        Args:
561            full (bool, optional): if specified then it is the full init otherwise it is Fast init
562            force (bool, optional): must be set if there was before some user data
563
564        Returns:
565            (dict): resposne cmd data
566        """
567        args = [
568            'start',
569            'init'
570        ]
571
572        if full:
573            args.append('full')
574        if force:
575            args.append('force')
576        return common.response_cmd(self._run(args))
577
578    def init_stop(self):
579        """Stops the initialization of a virtual drive
580
581        A stopped initialization process cannot be resumed.
582
583        Returns:
584            (dict): resposne cmd data
585        """
586        args = [
587            'stop',
588            'init'
589        ]
590        return common.response_cmd(self._run(args))
591
592    @property
593    def init_running(self):
594        """Check if initialization is running on a virtual drive
595
596        Returns:
597            (bool): true / false
598        """
599        args = [
600            'show',
601            'init'
602        ]
603
604        status = self._resposne_operation_status(self._run(args))['Status']
605        return bool(status == 'In progress')
606
607    def erase_start(self, mode='simple'):
608        """Securely erases non-SED drives with specified erase pattern
609
610        Args:
611            mode (str, optional):
612                simple		-	Single pass, single pattern write
613                normal		-	Three pass, three pattern write
614                thorough	-	Nine pass, repeats the normal write 3 times
615                standard	-	Applicable only for DFF's
616                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
617
618        Returns:
619            (dict): resposne cmd data
620        """
621        args = [
622            'start',
623            'erase',
624            '{0}'.format(mode)
625        ]
626        return common.response_cmd(self._run(args))
627
628    def erase_stop(self):
629        """Stops the erase operation of a virtual drive
630
631        Returns:
632            (dict): resposne cmd data
633        """
634        args = [
635            'stop',
636            'erase'
637        ]
638        return common.response_cmd(self._run(args))
639
640    @property
641    def erase_running(self):
642        """Check if erase is running on a virtual drive
643
644        Returns:
645            (bool): true / false
646        """
647        args = [
648            'show',
649            'erase'
650        ]
651
652        status = self._resposne_operation_status(self._run(args))['Status']
653        return bool(status == 'In progress')
654
655    @property
656    def erase_progress(self):
657        """Show virtual drive erase progress in percentage
658
659        Returns:
660            (str): progress in percentage
661        """
662
663        args = [
664            'show',
665            'erase'
666        ]
667
668        progress = self._resposne_operation_status(self._run(args))[
669            'Progress%']
670        if progress == '-':
671            return "100"
672        return progress
673
674    def delete(self, force=False):
675        """Deletes a particular virtual drive
676
677        Args:
678            force (bool, optional): If you delete a virtual drive with a valid MBR
679                                    without erasing the data and then create a new
680                                    virtual drive using the same set of physical drives
681                                    and the same RAID level as the deleted virtual drive,
682                                    the old unerased MBR still exists at block0 of the
683                                    new virtual drive, which makes it a virtual drive with
684                                    valid user data. Therefore, you must provide the
685                                    force option to delete this newly created virtual drive.
686
687        Returns:
688            (dict): resposne cmd data
689        """
690        args = [
691            'del'
692        ]
693
694        if force:
695            args.append('force')
696        return common.response_cmd(self._run(args))
697
698    def migrate_start(self, option, drives, raid=None, force=False):
699        """Starts migartion on the virtual drive
700
701        Args:
702            option (str):
703                            add - adds the specified drives to the migrated raid
704                            remove - removes the specified drives from the migrated raid
705            drives (str): specifies the list drives which needs to be added
706                          or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y])
707            raid - raid level to which migration needs to be done (raid0, raid1, ...)
708            force - if specified, then migration will start even if any drive in the DG is secured
709
710        Returns:
711           (dict): resposne cmd data
712        """
713        if not raid:
714            raid = self.raid
715        args = [
716            'start',
717            'migrate',
718            'type={0}'.format(raid),
719            'option={0}'.format(option),
720            'drives={0}'.format(drives)
721        ]
722        if force:
723            args.append('force')
724        return common.response_cmd(self._run(args))
725
726    @property
727    def migrate_running(self):
728        """Check if migration is running on a virtual drive
729
730        Returns:
731            (bool): true / false
732        """
733        args = [
734            'show',
735            'migrate'
736        ]
737
738        status = self._resposne_operation_status(self._run(args))['Status']
739        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 (str): virtual drive state 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_stop (dict): stops an migration process running on a virtual drive migrate_running (bool): check if migrate is running on a virtual drive

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

VirtualDrive(ctl_id, vd_id, binary='storcli64')
171    def __init__(self, ctl_id, vd_id, binary='storcli64'):
172        """Constructor - create StorCLI VirtualDrive object
173
174        Args:
175            ctl_id (str): controller id
176            vd_id (str): virtual drive id
177            binary (str): storcli binary or full path to the binary
178        """
179        self._ctl_id = ctl_id
180        self._vd_id = vd_id
181        self._binary = binary
182        self._storcli = StorCLI(binary)
183        self._name = '/c{0}/v{1}'.format(self._ctl_id, self._vd_id)
184
185        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

(str): virtual drive raid level

size

(str): virtual drive size

state

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

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 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):
557    def init_start(self, full=False, force=False):
558        """Starts the initialization of a virtual drive
559
560        Args:
561            full (bool, optional): if specified then it is the full init otherwise it is Fast init
562            force (bool, optional): must be set if there was before some user data
563
564        Returns:
565            (dict): resposne cmd data
566        """
567        args = [
568            'start',
569            'init'
570        ]
571
572        if full:
573            args.append('full')
574        if force:
575            args.append('force')
576        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):
578    def init_stop(self):
579        """Stops the initialization of a virtual drive
580
581        A stopped initialization process cannot be resumed.
582
583        Returns:
584            (dict): resposne cmd data
585        """
586        args = [
587            'stop',
588            'init'
589        ]
590        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'):
607    def erase_start(self, mode='simple'):
608        """Securely erases non-SED drives with specified erase pattern
609
610        Args:
611            mode (str, optional):
612                simple		-	Single pass, single pattern write
613                normal		-	Three pass, three pattern write
614                thorough	-	Nine pass, repeats the normal write 3 times
615                standard	-	Applicable only for DFF's
616                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
617
618        Returns:
619            (dict): resposne cmd data
620        """
621        args = [
622            'start',
623            'erase',
624            '{0}'.format(mode)
625        ]
626        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):
628    def erase_stop(self):
629        """Stops the erase operation of a virtual drive
630
631        Returns:
632            (dict): resposne cmd data
633        """
634        args = [
635            'stop',
636            'erase'
637        ]
638        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):
674    def delete(self, force=False):
675        """Deletes a particular virtual drive
676
677        Args:
678            force (bool, optional): If you delete a virtual drive with a valid MBR
679                                    without erasing the data and then create a new
680                                    virtual drive using the same set of physical drives
681                                    and the same RAID level as the deleted virtual drive,
682                                    the old unerased MBR still exists at block0 of the
683                                    new virtual drive, which makes it a virtual drive with
684                                    valid user data. Therefore, you must provide the
685                                    force option to delete this newly created virtual drive.
686
687        Returns:
688            (dict): resposne cmd data
689        """
690        args = [
691            'del'
692        ]
693
694        if force:
695            args.append('force')
696        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):
698    def migrate_start(self, option, drives, raid=None, force=False):
699        """Starts migartion on the virtual drive
700
701        Args:
702            option (str):
703                            add - adds the specified drives to the migrated raid
704                            remove - removes the specified drives from the migrated raid
705            drives (str): specifies the list drives which needs to be added
706                          or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y])
707            raid - raid level to which migration needs to be done (raid0, raid1, ...)
708            force - if specified, then migration will start even if any drive in the DG is secured
709
710        Returns:
711           (dict): resposne cmd data
712        """
713        if not raid:
714            raid = self.raid
715        args = [
716            'start',
717            'migrate',
718            'type={0}'.format(raid),
719            'option={0}'.format(option),
720            'drives={0}'.format(drives)
721        ]
722        if force:
723            args.append('force')
724        return common.response_cmd(self._run(args))

Starts migartion 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

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