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

StorCLI command line wrapper

Instance of this class is storcli command line wrapper

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

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

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

TODO: * implement TTL for cache

StorCLI( binary='storcli64', cmdrunner: Optional[pystorcli2.cmdRunner.CMDRunner] = None)
68    def __init__(self, binary='storcli64', cmdrunner: Optional[cmdRunner.CMDRunner] = None):
69        """Constructor - create StorCLI object wrapper
70
71        Args:
72            binary (str): storcli binary or full path to the binary
73        """
74
75        if cmdrunner is not None:
76            self._storcli = cmdrunner.binaryCheck(binary)
77        else:
78            self._storcli = self.__cmdrunner.binaryCheck(binary)
79
80        if cmdrunner is not None:
81            self.__cmdrunner = cmdrunner
82
83        if not _SINGLETON_STORCLI_MODULE_ENABLE:
84            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):
86    def set_cmdrunner(self, cmdrunner: cmdRunner.CMDRunner):
87        """
88        Set command runner object.
89        This is only useful for testing.
90        """
91        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):
108    def clear_cache(self):
109        """Clear cache (atomic)
110        """
111        with self.__cache_lock:
112            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):
131    @staticmethod
132    def check_response_status(cmd, out):
133        """Check ouput command line status from storcli.
134
135        Args:
136            cmd (list of str): full command line
137            out (dict): output from command line
138
139        Raises:
140            StorCliCmdError
141        """
142        cmd_status = common.response_cmd(out)
143        if cmd_status['Status'] == 'Failure':
144            if 'Detailed Status' in cmd_status:
145                raise exc.StorCliCmdError(
146                    cmd, "{0}".format(cmd_status['Detailed Status']))
147            else:
148                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):
150    def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
151        """Execute storcli command line with arguments.
152
153        Run command line and check output for errors.
154
155        Args:
156            args (list of str): cmd line arguments (without binary)
157            stdout (fd): controll subprocess stdout fd
158            stderr (fd): controll subporcess stderr fd
159            **kwargs: arguments to subprocess run
160
161        Returns:
162            dict: output data from command line
163
164        Raises:
165            exc.StorCliCmdError
166            exc.StorCliRunTimeError
167            exc.StorCliRunTimeout
168        """
169        cmd = [self._storcli]
170        cmd.extend(args)
171        # output in JSON format
172        cmd.append('J')
173        cmd_cache_key = ''.join(cmd)
174
175        if self.cache_enable:
176            if cmd_cache_key in self.__response_cache:
177                return self.__response_cache[cmd_cache_key]
178
179        with self.__cache_lock:
180            try:
181                ret = self.__cmdrunner.run(
182                    args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs)
183                try:
184                    ret_json = json.loads(ret.stdout)
185                    self.check_response_status(cmd, ret_json)
186                    ret.check_returncode()
187                    if self.cache_enable:
188                        self.__response_cache[cmd_cache_key] = ret_json
189                    return ret_json
190                except json.JSONDecodeError as err:
191                    # legacy handler (Ralequi: I don't know if this is still needed or what exactly it does)
192                    output = re.search('(^.*)Storage.*Command.*$',
193                                       ret.stdout, re.MULTILINE | re.DOTALL)
194                    if output:
195                        raise exc.StorCliCmdError(cmd, output.group(1))
196
197                    # Check if we can still parse the output
198                    parsed = {}
199                    for line in ret.stdout.splitlines():
200                        if '=' in line:
201                            key, value = line.split('=', 1)
202                            parsed[key.strip()] = value.strip()
203
204                    if 'Status' in parsed:
205                        return parsed
206                    else:
207                        raise exc.StorCliCmdError(cmd, str(err))
208
209            except subprocess.TimeoutExpired as err:
210                raise exc.StorCliRunTimeout(err)
211            except subprocess.SubprocessError as err:
212                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():
222    @staticmethod
223    def enable_singleton():
224        """Enable StorCLI to be singleton on module level
225
226        Use StorCLI singleton across all objects. All pystorcli
227        class instances use their own StorCLI object. With enabled cache
228        we can speedup for example metric lookups.
229
230        """
231        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():
233    @staticmethod
234    def disable_singleton():
235        """Disable StoreCLI class as signleton
236        """
237        StorCLI.__set_singleton(False)

Disable StoreCLI class as signleton

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

StorCLI Controller

Instance of this class represents controller in StorCLI hierarchy

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

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

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

TODO: Implement missing methods: * patrol read progress

Controller(ctl_id, binary='storcli64')
60    def __init__(self, ctl_id, binary='storcli64'):
61        """Constructor - create StorCLI Controller object
62
63        Args:
64            ctl_id (str): controller id
65            binary (str): storcli binary or full path to the binary
66        """
67        self._ctl_id = ctl_id
68        self._binary = binary
69        self._storcli = StorCLI(binary)
70        self._name = '/c{0}'.format(self._ctl_id)
71
72        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]:
140    def create_vd(self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[virtualdrive.VirtualDrive]:
141        """Create virtual drive (raid) managed by current controller
142
143        Args:
144            name (str): virtual drive name
145            raid (str): virtual drive raid level (raid0, raid1, ...)
146            drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z)
147            strip (str, optional): virtual drive raid strip size
148
149        Returns:
150            (None): no virtual drive created with name
151            (:obj:virtualdrive.VirtualDrive)
152        """
153        args = [
154            'add',
155            'vd',
156            'r{0}'.format(raid),
157            'name={0}'.format(name),
158            'drives={0}'.format(drives),
159            'strip={0}'.format(strip)
160        ]
161
162        try:
163            if int(raid) >= 10 and PDperArray is None:
164                # Try to count the number of drives in the array
165                # The format of the drives argument is e:s|e:s-x|e:s-x,y;e:s-x,y,z
166
167                numDrives = common.count_drives(drives)
168
169                if numDrives % 2 != 0 and numDrives % 3 == 0:
170                    # In some scenarios, such as 9 drives with raid 60, 3 is a good pd number but 4 is not
171                    # Must check for similar scenarios
172                    # BTW we don't clearly understand what PDperArray is for and what exactly it does under the hood. More investigation is needed
173                    PDperArray = numDrives//3
174                else:
175                    PDperArray = numDrives//2
176
177        except ValueError:
178            pass
179
180        finally:
181            if raid == '00' and PDperArray is None:
182                PDperArray = 1
183
184        if PDperArray is not None:
185            args.append('PDperArray={0}'.format(PDperArray))
186
187        self._run(args)
188        for vd in self.vds:
189            if name == vd.name:
190                return vd
191        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'):
283    def set_patrolread(self, value, mode='manual'):
284        """Set patrol read
285
286        Args:
287            value (str): on / off to configure patrol read state
288            mode (str): auto | manual to configure patrol read schedule
289        """
290        args = [
291            'set',
292            'patrolread={0}'.format(value)
293        ]
294
295        if value == 'on':
296            args.append('mode={0}'.format(mode))
297
298        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):
300    def patrolread_start(self):
301        """Starts the patrol read operation of the controller
302
303        Returns:
304            (dict): response cmd data
305        """
306        args = [
307            'start',
308            'patrolread'
309        ]
310        return common.response_cmd(self._run(args))

Starts the patrol read operation of the controller

Returns: (dict): response cmd data

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

Stops the patrol read operation of the controller

Returns: (dict): response cmd data

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

Pauses the patrol read operation of the controller

Returns: (dict): response cmd data

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

Resumes the patrol read operation of the controller

Returns: (dict): response cmd data

patrolread_running

Check if patrol read is running on the controller

Returns: (bool): true / false

cc

func effective wrapper

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

Set consistency check

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

class Controllers:
421class Controllers(object):
422    """StorCLI Controllers
423
424    Instance of this class is iterable with :obj:Controller as item
425
426    Args:
427        binary (str): storcli binary or full path to the binary
428
429    Properties:
430        ids (list of str): list of controllers id
431
432    Methods:
433        get_clt (:obj:Controller): return controller object by id
434    """
435
436    def __init__(self, binary='storcli64'):
437        """Constructor - create StorCLI Controllers object
438
439        Args:
440            binary (str): storcli binary or full path to the binary
441        """
442        self._binary = binary
443        self._storcli = StorCLI(binary)
444
445    @ property
446    def _ctl_ids(self) -> List[str]:
447        out = self._storcli.run(['show'])
448        response = common.response_data(out)
449
450        if "Number of Controllers" in response and response["Number of Controllers"] == 0:
451            return []
452        else:
453            return [ctl['Ctl'] for ctl in common.response_data_subkey(out, ['System Overview', 'IT System Overview'])]
454
455    @ property
456    def _ctls(self):
457        for ctl_id in self._ctl_ids:
458            yield Controller(ctl_id=ctl_id, binary=self._binary)
459
460    def __iter__(self):
461        return self._ctls
462
463    @ property
464    def ids(self):
465        """(list of str): controllers id
466        """
467        return self._ctl_ids
468
469    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
470        """Get controller object by id
471
472        Args:
473            ctl_id (str): controller id
474
475        Returns:
476            (None): no controller with id
477            (:obj:Controller): controller object
478        """
479        for ctl in self:
480            if ctl.id == ctl_id:
481                return ctl
482        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')
436    def __init__(self, binary='storcli64'):
437        """Constructor - create StorCLI Controllers object
438
439        Args:
440            binary (str): storcli binary or full path to the binary
441        """
442        self._binary = binary
443        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]:
469    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
470        """Get controller object by id
471
472        Args:
473            ctl_id (str): controller id
474
475        Returns:
476            (None): no controller with id
477            (:obj:Controller): controller object
478        """
479        for ctl in self:
480            if ctl.id == ctl_id:
481                return ctl
482        return None

Get controller object by id

Args: ctl_id (str): controller id

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

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

StorCLI enclosure

Instance of this class represents enclosure in StorCLI hierarchy

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

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

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

Constructor - create StorCLI Enclosure object

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

id: int

(str): enclosure id

name: str

(str): enclosure cmd name

facts

(dict): raw enclosure facts

ctl_id: int

(str): enclosure controller id

ctl

(:obj:controller.Controller): enclosure controller

has_drives: bool

(bool): true if enclosure has drives

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

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

StorCLI enclosures

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

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

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

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

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

Constructor - create StorCLI Enclosures object

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

ids: List[int]

(list of str): list of enclosures id

ctl_id: int

(str): enclosures controller id

ctl

(:obj:controller.Controller): enclosures controller

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

Get enclosure object by id

Args: encl_id (str): enclosure id

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

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

Drive status

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

Check if drive is good according to status

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

Check if drive is configured according to status

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

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

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

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

@staticmethod
def from_string(status: str) -> pystorcli2.DriveState:
118    @staticmethod
119    def from_string(status: str) -> 'DriveState':
120        """Get DriveState from string"""
121        for drive_status in DriveState:
122            if drive_status.name.lower() == status.lower() or drive_status.value.lower() == status.lower():
123                return drive_status
124        raise ValueError('Invalid drive status: {0}'.format(status))

Get DriveState from string

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

StorCLI Drive

Instance of this class represents drive in StorCLI hierarchy

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

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

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

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

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

Constructor - create StorCLI Drive object

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

id

(str): drive id

name

(str): drive cmd name

facts

(dict): raw drive facts

metrics

(dict): drive metrics

size

(str): drive size

interface

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

Start initialization of a drive

Returns: (dict): resposne cmd data

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

Stop initialization on a drive

A stopped initialization process cannot be resumed.

Returns: (dict): resposne cmd data

init_running

Check if initialization process is running on a drive

Returns: (bool): true / false

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

Securely erases non-SED drives with specified erase pattern

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

Returns: (dict): resposne cmd data

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

Stops the erase operation of a drive

Returns: (dict): resposne cmd data

erase_running

Check if erase process is running on a drive

Returns: (bool): true / false

phyerrorcounters

Get/Reset the drive phyerrorcounters

Reset drive error counters with (str) 0

phyerrorcounters_reset

Get/Reset the drive phyerrorcounters

Reset drive error counters with (str) 0

Set drive state

def set_state( self, value: Union[str, pystorcli2.DriveState], force: bool = False):
420    def set_state(self, value: Union[str, DriveState], force: bool = False):
421        """ Set drive state
422        """
423        # if DriveState, get the string value
424        if isinstance(value, DriveState):
425            value = value.settable_str()
426
427        args = [
428            'set',
429            '{0}'.format(value)
430        ]
431
432        if force:
433            args.append('force')
434
435        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):
473    def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False):
474        """Creates a hotspare drive
475
476        Args:
477            dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...)
478            enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with.
479                                 If this option is specified, affinity is set; if it is not specified,
480                                 there is no affinity.NOTE Affinity cannot be removed once it is set
481                                 for a hotspare drive.
482            nonrevertible (bool): sets the drive as a nonrevertible hotspare
483
484        Returns:
485            (dict): resposne cmd data
486        """
487        args = [
488            'add',
489            'hotsparedrive'
490        ]
491
492        if dgs:
493            args.append("dgs={0}".format(dgs))
494        if enclaffinity:
495            args.append('enclaffinity')
496        if nonrevertible:
497            args.append('nonrevertible')
498        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):
500    def hotparedrive_delete(self):
501        """Deletes drive from hotspares
502
503        Returns:
504            (dict): resposne cmd data
505        """
506        args = [
507            'delete',
508            'hotsparedrive'
509        ]
510        return common.response_cmd(self._run(args))

Deletes drive from hotspares

Returns: (dict): resposne cmd data

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

Get drive range in the current enclosure

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

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

StorCLI VirtualDrive

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

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

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

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

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

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

access: pystorcli2.virtualdrive.access.VDAccess

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

strip

(str): virtual drive strip size

os_exposed

(bool): virtual drive exposed to the OS

os_name

(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):
471    def init_start(self, full=False, force=False):
472        """Starts the initialization of a virtual drive
473
474        Args:
475            full (bool, optional): if specified then it is the full init otherwise it is Fast init
476            force (bool, optional): must be set if there was before some user data
477
478        Returns:
479            (dict): resposne cmd data
480        """
481        args = [
482            'start',
483            'init'
484        ]
485
486        if full:
487            args.append('full')
488        if force:
489            args.append('force')
490        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):
492    def init_stop(self):
493        """Stops the initialization of a virtual drive
494
495        A stopped initialization process cannot be resumed.
496
497        Returns:
498            (dict): resposne cmd data
499        """
500        args = [
501            'stop',
502            'init'
503        ]
504        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'):
521    def erase_start(self, mode='simple'):
522        """Securely erases non-SED drives with specified erase pattern
523
524        Args:
525            mode (str, optional):
526                simple		-	Single pass, single pattern write
527                normal		-	Three pass, three pattern write
528                thorough	-	Nine pass, repeats the normal write 3 times
529                standard	-	Applicable only for DFF's
530                PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
531
532        Returns:
533            (dict): resposne cmd data
534        """
535        args = [
536            'start',
537            'erase',
538            '{0}'.format(mode)
539        ]
540        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):
542    def erase_stop(self):
543        """Stops the erase operation of a virtual drive
544
545        Returns:
546            (dict): resposne cmd data
547        """
548        args = [
549            'stop',
550            'erase'
551        ]
552        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):
588    def delete(self, force=False):
589        """Deletes a particular virtual drive
590
591        Args:
592            force (bool, optional): If you delete a virtual drive with a valid MBR
593                                    without erasing the data and then create a new
594                                    virtual drive using the same set of physical drives
595                                    and the same RAID level as the deleted virtual drive,
596                                    the old unerased MBR still exists at block0 of the
597                                    new virtual drive, which makes it a virtual drive with
598                                    valid user data. Therefore, you must provide the
599                                    force option to delete this newly created virtual drive.
600
601        Returns:
602            (dict): resposne cmd data
603        """
604        args = [
605            'del'
606        ]
607
608        if force:
609            args.append('force')
610        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):
612    def migrate_start(self, option, drives, raid=None, force=False):
613        """Starts migration on the virtual drive
614
615        Args:
616            option (str):
617                            add - adds the specified drives to the migrated raid
618                            remove - removes the specified drives from the migrated raid
619            drives (str): specifies the list drives which needs to be added
620                          or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y])
621            raid - raid level to which migration needs to be done (raid0, raid1, ...)
622            force - if specified, then migration will start even if any drive in the DG is secured
623
624        Returns:
625           (dict): resposne cmd data
626        """
627        if not raid:
628            raid = self.raid
629        args = [
630            'start',
631            'migrate',
632            'type={0}'.format(raid),
633            'option={0}'.format(option),
634            'drives={0}'.format(drives)
635        ]
636        if force:
637            args.append('force')
638        return common.response_cmd(self._run(args))

Starts migration on the virtual drive

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

Returns: (dict): resposne cmd data

migrate_running

Check if migration is running on a virtual drive

Returns: (bool): true / false

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

Starts a consistency check operation for a virtual drive

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

Returns: (dict): resposne cmd data

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

Stops the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

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

Pauses the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

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

Resumes the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

cc_running

Check if consistency check is running on a virtual drive

Returns: (bool): true / false

cc_progress

Show virtual drive consistency check progress in percentage

Returns: (str): progress in percentage

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