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

StorCLI command line wrapper

Instance of this class is storcli command line wrapper

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

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

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

TODO: * implement TTL for cache

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

Constructor - create StorCLI object wrapper

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

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

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

cache_enable
 94    @property
 95    def cache_enable(self):
 96        """Enable/Disable resposne cache (atomic)
 97
 98        Returns:
 99            bool: true/false
100        """
101
102        return self.__cache_enabled

Enable/Disable resposne cache (atomic)

Returns: bool: true/false

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

Clear cache (atomic)

cache
115    @property
116    def cache(self):
117        """Get/Set raw cache
118
119        Args:
120            (dict): raw cache
121
122        Returns:
123            (dict): cache
124        """
125        return self.__response_cache

Get/Set raw cache

Args: (dict): raw cache

Returns: (dict): cache

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

Check ouput command line status from storcli.

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

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

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

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

Execute storcli command line with arguments.

Run command line and check output for errors.

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

Returns: dict: output data from command line

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

@staticmethod
def enable_singleton():
267    @staticmethod
268    def enable_singleton():
269        """Enable StorCLI to be singleton on module level
270
271        Use StorCLI singleton across all objects. All pystorcli
272        class instances use their own StorCLI object. With enabled cache
273        we can speedup for example metric lookups.
274
275        """
276        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():
278    @staticmethod
279    def disable_singleton():
280        """Disable StoreCLI class as signleton
281        """
282        StorCLI.__set_singleton(False)

Disable StoreCLI class as signleton

@staticmethod
def is_singleton() -> bool:
284    @staticmethod
285    def is_singleton() -> bool:
286        """Check if singleton is enabled
287        """
288        return _SINGLETON_STORCLI_MODULE_ENABLE

Check if singleton is enabled

full_version: str
290    @property
291    def full_version(self) -> str:
292        """Get storcli version as storcli returns
293        """
294        out = self.run(['show'])
295        version = common.response_cmd(out)['CLI Version']
296
297        return version

Get storcli version as storcli returns

version: str
299    @property
300    def version(self) -> str:
301        """Get storcli version in a cleaner way
302        """
303        import re
304
305        # Remove duplicated 0s
306        first_clean = re.sub('0+', '0', self.full_version.split(' ')[0])
307
308        # Remove leading 0s
309        second_clean = re.sub('^0+', '', first_clean)
310
311        return second_clean

Get storcli version in a cleaner way

controllers: Controllers
313    @property
314    def controllers(self) -> 'pystorcli2.controller.Controllers':
315        """Get list of controllers
316        """
317        from . import Controllers
318
319        return Controllers(binary=self._storcli)

Get list of controllers

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

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

TODO: Implement missing methods: * patrol read progress

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

Constructor - create StorCLI Controller object

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

id
94    @property
95    def id(self):
96        """ (str): controller id
97        """
98        return self._ctl_id

(str): controller id

name
100    @property
101    def name(self):
102        """ (str): controller cmd name
103        """
104        return self._name

(str): controller cmd name

facts
106    @property
107    def facts(self):
108        """ (dict): raw controller facts
109        """
110        args = [
111            'show',
112            'all'
113        ]
114        return common.response_data(self._run(args))

(dict): raw controller facts

metrics
116    @property
117    def metrics(self):
118        """(:obj:ControllerMetrics): controller metrics
119        """
120        return ControllerMetrics(ctl=self)

(:obj:ControllerMetrics): controller metrics

vds
122    @property
123    def vds(self):
124        """(:obj:virtualdrive.VirtualDrives): controllers virtual drives
125        """
126        return virtualdrive.VirtualDrives(ctl_id=self._ctl_id, binary=self._binary)

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

encls
128    @property
129    def encls(self):
130        """(:obj:enclosure.Enclosures): controller enclosures
131        """
132        return enclosure.Enclosures(ctl_id=self._ctl_id, binary=self._binary)

(:obj:enclosure.Enclosures): controller enclosures

drives_ids: List[str]
134    @property
135    def drives_ids(self) -> List[str]:
136        """(list of str): list of drives ids in format (e:s)
137        """
138        drives = []
139        for encl in self.encls:
140            for id in encl.drives.ids:
141                drives.append("{enc}:{id}".format(enc=encl.id, id=id))
142
143        return drives

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

func effective wrapper

foreignautoimport
 98    def wrapper(*args, **kwargs):
 99        """func effective wrapper
100        """
101        return func(*args, **kwargs).lower()

func effective wrapper

patrolread
 98    def wrapper(*args, **kwargs):
 99        """func effective wrapper
100        """
101        return func(*args, **kwargs).lower()

func effective wrapper

def set_patrolread(self, value, mode='manual'):
288    def set_patrolread(self, value, mode='manual'):
289        """Set patrol read
290
291        Args:
292            value (str): on / off to configure patrol read state
293            mode (str): auto | manual to configure patrol read schedule
294        """
295        args = [
296            'set',
297            'patrolread={0}'.format(value)
298        ]
299
300        if value == 'on':
301            args.append('mode={0}'.format(mode))
302
303        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):
305    def patrolread_start(self):
306        """Starts the patrol read operation of the controller
307
308        Returns:
309            (dict): response cmd data
310        """
311        args = [
312            'start',
313            'patrolread'
314        ]
315        return common.response_cmd(self._run(args))

Starts the patrol read operation of the controller

Returns: (dict): response cmd data

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

Stops the patrol read operation of the controller

Returns: (dict): response cmd data

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

Pauses the patrol read operation of the controller

Returns: (dict): response cmd data

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

Resumes the patrol read operation of the controller

Returns: (dict): response cmd data

patrolread_running
353    @property
354    def patrolread_running(self):
355        """Check if patrol read is running on the controller
356
357        Returns:
358            (bool): true / false
359        """
360        args = [
361            'show',
362            'patrolread'
363        ]
364
365        status = ''
366        for pr in common.response_property(self._run(args)):
367            if pr['Ctrl_Prop'] == "PR Current State":
368                status = pr['Value']
369        return bool('Active' in status)

Check if patrol read is running on the controller

Returns: (bool): true / false

cc
 98    def wrapper(*args, **kwargs):
 99        """func effective wrapper
100        """
101        return func(*args, **kwargs).lower()

func effective wrapper

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

Set consistency check

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

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

(bool): true if controller has foreign configurations

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

(bool): true if controller has healthy foreign configurations

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

Deletes foreign configurations

Returns: (dict): response cmd data

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

Imports foreign configurations

Returns: (dict): response cmd data

jbod
 98    def wrapper(*args, **kwargs):
 99        """func effective wrapper
100        """
101        return func(*args, **kwargs).lower()

func effective wrapper

class Controllers:
541class Controllers(object):
542    """StorCLI Controllers
543
544    Instance of this class is iterable with :obj:Controller as item
545
546    Args:
547        binary (str): storcli binary or full path to the binary
548
549    Properties:
550        ids (list of str): list of controllers id
551
552    Methods:
553        get_clt (:obj:Controller): return controller object by id
554    """
555
556    def __init__(self, binary='storcli64'):
557        """Constructor - create StorCLI Controllers object
558
559        Args:
560            binary (str): storcli binary or full path to the binary
561        """
562        self._binary = binary
563        self._storcli = StorCLI(binary)
564
565    @ property
566    def _ctl_ids(self) -> List[int]:
567        out = self._storcli.run(['show'], allow_error_codes=[
568            StorcliErrorCode.INCOMPLETE_FOREIGN_CONFIGURATION])
569        response = common.response_data(out)
570
571        if "Number of Controllers" in response and response["Number of Controllers"] == 0:
572            return []
573        else:
574            return [ctl['Ctl'] for ctl in common.response_data_subkey(out, ['System Overview', 'IT System Overview'])]
575
576    @ property
577    def _ctls(self):
578        for ctl_id in self._ctl_ids:
579            yield Controller(ctl_id=ctl_id, binary=self._binary)
580
581    def __iter__(self):
582        return self._ctls
583
584    @ property
585    def ids(self):
586        """(list of str): controllers id
587        """
588        return self._ctl_ids
589
590    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
591        """Get controller object by id
592
593        Args:
594            ctl_id (str): controller id
595
596        Returns:
597            (None): no controller with id
598            (:obj:Controller): controller object
599        """
600        for ctl in self:
601            if ctl.id == ctl_id:
602                return ctl
603        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')
556    def __init__(self, binary='storcli64'):
557        """Constructor - create StorCLI Controllers object
558
559        Args:
560            binary (str): storcli binary or full path to the binary
561        """
562        self._binary = binary
563        self._storcli = StorCLI(binary)

Constructor - create StorCLI Controllers object

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

ids
584    @ property
585    def ids(self):
586        """(list of str): controllers id
587        """
588        return self._ctl_ids

(list of str): controllers id

def get_ctl(self, ctl_id: int) -> Optional[Controller]:
590    def get_ctl(self, ctl_id: int) -> Optional[Controller]:
591        """Get controller object by id
592
593        Args:
594            ctl_id (str): controller id
595
596        Returns:
597            (None): no controller with id
598            (:obj:Controller): controller object
599        """
600        for ctl in self:
601            if ctl.id == ctl_id:
602                return ctl
603        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
68    @property
69    def id(self) -> int:
70        """(str): enclosure id
71        """
72        return self._encl_id

(str): enclosure id

name: str
74    @property
75    def name(self) -> str:
76        """(str): enclosure cmd name
77        """
78        return self._name

(str): enclosure cmd name

facts
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))

(dict): raw enclosure facts

ctl_id: int
90    @property
91    def ctl_id(self) -> int:
92        """(str): enclosure controller id
93        """
94        return self._ctl_id

(str): enclosure controller id

ctl
 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)

(:obj:controller.Controller): enclosure controller

has_drives: bool
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

(bool): true if enclosure has drives

drives: Drives
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)

(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]
184    @property
185    def ids(self) -> List[int]:
186        """(list of str): list of enclosures id
187        """
188        return self._encl_ids

(list of str): list of enclosures id

ctl_id: int
190    @property
191    def ctl_id(self) -> int:
192        """(str): enclosures controller id
193        """
194        return self._ctl_id

(str): enclosures controller id

ctl
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)

(:obj:controller.Controller): enclosures controller

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

Get enclosure object by id

Args: encl_id (str): enclosure id

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

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

Drive status

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

Check if drive is good according to status

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

Check if drive is configured according to status

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

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

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

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

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

Get DriveState from string

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

StorCLI Drive

Instance of this class represents drive in StorCLI hierarchy

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

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

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

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

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

Constructor - create StorCLI Drive object

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

id
120    @property
121    def id(self):
122        """(str): drive id
123        """
124        return self._slot_id

(str): drive id

name
126    @property
127    def name(self):
128        """(str): drive cmd name
129        """
130        return self._name

(str): drive cmd name

facts
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))

(dict): raw drive facts

metrics
142    @property
143    def metrics(self):
144        """(dict): drive metrics
145        """
146        return DriveMetrics(self)

(dict): drive metrics

size
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']

(str): drive size

interface
114    def wrapper(*args, **kwargs):
115        """func effective wrapper
116        """
117        return func(*args, **kwargs).upper()

func effective wrapper

medium
114    def wrapper(*args, **kwargs):
115        """func effective wrapper
116        """
117        return func(*args, **kwargs).upper()

func effective wrapper

model
114    def wrapper(*args, **kwargs):
115        """func effective wrapper
116        """
117        return func(*args, **kwargs).upper()

func effective wrapper

serial
114    def wrapper(*args, **kwargs):
115        """func effective wrapper
116        """
117        return func(*args, **kwargs).upper()

func effective wrapper

wwn
114    def wrapper(*args, **kwargs):
115        """func effective wrapper
116        """
117        return func(*args, **kwargs).upper()

func effective wrapper

firmware
114    def wrapper(*args, **kwargs):
115        """func effective wrapper
116        """
117        return func(*args, **kwargs).upper()

func effective wrapper

device_speed
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']

(str): drive speed

ctl_id
242    @property
243    def ctl_id(self):
244        """(str): drive controller id
245        """
246        return self._ctl_id

(str): drive controller id

ctl
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)

(:obj:controller.Controller): drive controller

encl_id
254    @property
255    def encl_id(self):
256        """(str): dirve enclosure id
257        """
258        return self._encl_id

(str): dirve enclosure id

encl
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)

(:obj:enclosure.Enclosure): drive enclosure

vd_id: Optional[int]
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

(int): drive virtual drive id if any

vd: Optional[VirtualDrive]
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)

(: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
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')

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
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')

Check if erase process is running on a drive

Returns: (bool): true / false

phyerrorcounters
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]

Get/Reset the drive phyerrorcounters

Reset drive error counters with (str) 0

phyerrorcounters_reset
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]

Get/Reset the drive phyerrorcounters

Reset drive error counters with (str) 0

state: DriveState
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)

Get/Set drive state

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

Set drive state

spin
439    @property
440    def spin(self):
441        """Get/Set drive spin status
442
443        One of the following states can be set (str):
444            up - spins up and set to unconfigured good
445            down - spins down an unconfigured drive and prepares it for removal
446
447        Returns:
448            (str): up / down
449        """
450        args = [
451            'show'
452        ]
453
454        spin = self._response_properties(self._run(args))['Sp']
455        if spin == 'U':
456            return 'up'
457        return 'down'

Get/Set drive spin status

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

Returns: (str): up / down

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

Creates a hotspare drive

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

Returns: (dict): resposne cmd data

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

Deletes drive from hotspares

Returns: (dict): resposne cmd data

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

StorCLI drives

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

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

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

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

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

Constructor - create StorCLI Enclosures object

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

ids: List[int]
573    @property
574    def ids(self) -> List[int]:
575        """(list of str): list of enclosures id
576        """
577        return self._drive_ids

(list of str): list of enclosures id

ctl_id: int
579    @property
580    def ctl_id(self) -> int:
581        """(str): enclosures controller id
582        """
583        return self._ctl_id

(str): enclosures controller id

ctl
585    @property
586    def ctl(self):
587        """(:obj:controller.Controller): enclosures controller
588        """
589        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)

(:obj:controller.Controller): enclosures controller

encl_id: int
591    @property
592    def encl_id(self) -> int:
593        """(str): enclosure id
594        """
595        return self._encl_id

(str): enclosure id

encl
597    @property
598    def encl(self):
599        """(:obj:Enclosure): enclosure
600        """
601        return enclosure.Enclosure(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary)

(:obj:Enclosure): enclosure

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

Get drive object by id

Args: drive_id (str): drive id

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

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

Get drive range list in the current enclosure

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

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

Get drive range in the current enclosure

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

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

StorCLI VirtualDrive

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

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

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

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

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

Constructor - create StorCLI VirtualDrive object

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

id
112    @property
113    def id(self):
114        """(str): virtual drive id
115        """
116        return self._vd_id

(str): virtual drive id

facts
118    @property
119    def facts(self):
120        """(dict): raw virtual drive facts
121        """
122        args = [
123            'show',
124            'all'
125        ]
126        return common.response_data(self._run(args))

(dict): raw virtual drive facts

metrics
128    @property
129    def metrics(self):
130        """(:obj:VirtualDriveMetrics): virtual drive metrics
131        """
132        return VirtualDriveMetrics(self)

(:obj:VirtualDriveMetrics): virtual drive metrics

raid
 98    def wrapper(*args, **kwargs):
 99        """func effective wrapper
100        """
101        return func(*args, **kwargs).lower()

func effective wrapper

size
145    @property
146    def size(self):
147        """(str): virtual drive size
148        """
149        args = [
150            'show'
151        ]
152        return self._response_properties(self._run(args))['Size']

(str): virtual drive size

state: pystorcli2.virtualdrive.state.VDState
154    @property
155    def state(self) -> VDState:
156        """(VDState): virtual drive state (optimal | recovery | offline | degraded | degraded_partially)
157        """
158        args = [
159            'show'
160        ]
161        state = self._response_properties(self._run(args))['State']
162
163        return VDState.from_string(state)

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

access: pystorcli2.virtualdrive.access.VDAccess
165    @property
166    def access(self) -> VDAccess:
167        """(VDAccess): virtual drive acess (RO,RW,...)
168        """
169        args = [
170            'show'
171        ]
172        access = self._response_properties(self._run(args))['Access']
173
174        return VDAccess.from_string(access)

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

strip
184    @property
185    def strip(self):
186        """(str): virtual drive strip size
187        """
188        args = [
189            'show',
190            'all'
191        ]
192
193        size = self._response_properties_all(self._run(args))['Strip Size']
194        return size.split()[0]

(str): virtual drive strip size

os_exposed
196    @property
197    def os_exposed(self):
198        """(bool): virtual drive exposed to the OS
199        """
200        args = [
201            'show',
202            'all'
203        ]
204
205        exposed = self._response_properties_all(self._run(args))[
206            'Exposed to OS']
207        return bool(exposed == 'Yes')

(bool): virtual drive exposed to the OS

os_name
 98    def wrapper(*args, **kwargs):
 99        """func effective wrapper
100        """
101        return func(*args, **kwargs).lower()

func effective wrapper

ctl_id
220    @property
221    def ctl_id(self):
222        """(str): virtual drive controller id
223        """
224        return self._ctl_id

(str): virtual drive controller id

ctl
226    @property
227    def ctl(self):
228        """(:obj:controller.Controller): virtual drive controller
229        """
230        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)

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

drives
232    @property
233    def drives(self):
234        """(list of :obj:Drive): drives
235        """
236        args = [
237            'show',
238            'all'
239        ]
240
241        drives = []
242        pds = common.response_data(self._run(args))[
243            'PDs for VD {0}'.format(self._vd_id)]
244        for pd in pds:
245            drive_encl_id, drive_slot_id = pd['EID:Slt'].split(':')
246            drives.append(
247                drive.Drive(
248                    ctl_id=self._ctl_id,
249                    encl_id=drive_encl_id,
250                    slot_id=drive_slot_id,
251                    binary=self._binary
252                )
253            )
254        return drives

(list of :obj:Drive): drives

name
256    @property
257    def name(self):
258        """Get/Set virtual drive name
259
260        The name is restricted to 15 characters.
261
262        Returns:
263            (str): raid name
264        """
265        args = [
266            'show',
267        ]
268
269        properties = self._response_properties(self._run(args))
270        return properties['Name']

Get/Set virtual drive name

The name is restricted to 15 characters.

Returns: (str): raid name

bootdrive
282    @property
283    def bootdrive(self):
284        """Get/Set virtual drive as Boot Drive
285
286        One of the following options can be set (str):
287            on - enable boot virtual drive
288            off - disable boot virtual dirve
289
290        Returns:
291            (str): on / off
292        """
293        args = [
294            '/c{0}'.format(self._ctl_id),
295            'show',
296            'bootdrive'
297        ]
298
299        for vd in common.response_property(self._storcli.run(args)):
300            if vd['Value'] == 'VD:{0}'.format(self._vd_id):
301                return 'on'
302        return 'off'

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
314    @property
315    def pdcache(self):
316        """Get/Set PD Cache Setting
317
318        One of the following options can be set (str):
319            on - enables PD Caching
320            off - disables PD Caching
321            default - default PD Caching
322
323        Returns:
324            (str): on / off
325        """
326        args = [
327            'show',
328            'all'
329        ]
330
331        properties = self._response_properties_all(self._run(args))
332        if properties['Disk Cache Policy'] == 'Enabled':
333            return 'on'
334        elif 'Default' in properties['Disk Cache Policy']:
335            return 'default'
336        return 'off'

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
348    @property
349    def wrcache(self):
350        """Get/Set Write cache setting
351
352        One of the following options can be set (str):
353            wt - write Through
354            wb - write Back
355            awb - write Back even in case of bad BBU also
356
357        Returns:
358            (str): wt / wb / awb
359        """
360        args = [
361            'show',
362        ]
363
364        properties = self._response_properties(self._run(args))
365        if 'AWB' in properties['Cache']:
366            return 'awb'
367        elif 'WB' in properties['Cache']:
368            return 'wb'
369        return 'wt'

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
381    @property
382    def rdcache(self):
383        """Get/Set Read cache setting
384
385        One of the following options can be set (str):
386            ra - Read Ahead
387            nora - No Read Ahead
388
389        Returns:
390            (str): ra / nora
391        """
392        args = [
393            'show',
394        ]
395
396        properties = self._response_properties(self._run(args))
397        if properties['Cache'][0:2] == 'NR':
398            return 'nora'
399        return 'ra'

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
411    @property
412    def iopolicy(self):
413        """Get/Set iopolicy setting
414
415        One of the following options can be set (str):
416            cached - IOs are cached
417            direct - IOs are not cached
418
419        Returns:
420            (str): cached / direct
421        """
422        args = [
423            'show',
424        ]
425
426        properties = self._response_properties(self._run(args))
427        if properties['Cache'][-1] == 'D':
428            return 'direct'
429        return 'cached'

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
 98    def wrapper(*args, **kwargs):
 99        """func effective wrapper
100        """
101        return func(*args, **kwargs).lower()

func effective wrapper

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

Starts the initialization of a virtual drive

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

Returns: (dict): resposne cmd data

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

Stops the initialization of a virtual drive

A stopped initialization process cannot be resumed.

Returns: (dict): resposne cmd data

init_running
504    @property
505    def init_running(self):
506        """Check if initialization is running on a virtual drive
507
508        Returns:
509            (bool): true / false
510        """
511        args = [
512            'show',
513            'init'
514        ]
515
516        status = self._response_operation_status(self._run(args))['Status']
517        return bool(status == 'In progress')

Check if initialization is running on a virtual drive

Returns: (bool): true / false

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

Securely erases non-SED drives with specified erase pattern

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

Returns: (dict): resposne cmd data

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

Stops the erase operation of a virtual drive

Returns: (dict): resposne cmd data

erase_running
552    @property
553    def erase_running(self):
554        """Check if erase is running on a virtual drive
555
556        Returns:
557            (bool): true / false
558        """
559        args = [
560            'show',
561            'erase'
562        ]
563
564        status = self._response_operation_status(self._run(args))['Status']
565        return bool(status == 'In progress')

Check if erase is running on a virtual drive

Returns: (bool): true / false

erase_progress
567    @property
568    def erase_progress(self):
569        """Show virtual drive erase progress in percentage
570
571        Returns:
572            (str): progress in percentage
573        """
574
575        args = [
576            'show',
577            'erase'
578        ]
579
580        progress = self._response_operation_status(self._run(args))[
581            'Progress%']
582        if progress == '-':
583            return "100"
584        return progress

Show virtual drive erase progress in percentage

Returns: (str): progress in percentage

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

Deletes a particular virtual drive

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

Returns: (dict): resposne cmd data

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

Starts migration on the virtual drive

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

Returns: (dict): resposne cmd data

migrate_running
638    @property
639    def migrate_running(self):
640        """Check if migration is running on a virtual drive
641
642        Returns:
643            (bool): true / false
644        """
645        args = [
646            'show',
647            'migrate'
648        ]
649
650        status = self._response_operation_status(self._run(args))['Status']
651        return bool(status == 'In progress')

Check if migration is running on a virtual drive

Returns: (bool): true / false

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

Starts a consistency check operation for a virtual drive

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

Returns: (dict): resposne cmd data

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

Stops the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

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

Pauses the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

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

Resumes the consistency check operation of a virtual drive

Returns: (dict): resposne cmd data

cc_running
706    @property
707    def cc_running(self):
708        """Check if consistency check is running on a virtual drive
709
710        Returns:
711            (bool): true / false
712        """
713        args = [
714            'show',
715            'cc'
716        ]
717
718        status = self._response_operation_status(self._run(args))['Status']
719        return bool(status == 'In progress')

Check if consistency check is running on a virtual drive

Returns: (bool): true / false

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

StorCLI virtual drives

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

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

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

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

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

Constructor - create StorCLI VirtualDrives object

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

ids
775    @property
776    def ids(self):
777        """(list of str): list of virtual drives id
778        """
779        return self._vd_ids

(list of str): list of virtual drives id

ctl_id
781    @property
782    def ctl_id(self):
783        """(str): virtual drives controller id
784        """
785        return self._ctl_id

(str): virtual drives controller id

ctl
787    @property
788    def ctl(self):
789        """(:obj:controller.Controller): virtual drives controller
790        """
791        return controller.Controller(ctl_id=self._ctl_id, binary=self._binary)

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

has_vds
793    @property
794    def has_vds(self):
795        """(bool): true if there are virtual drives
796        """
797        if self.ids:
798            return True
799        return False

(bool): true if there are virtual drives

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

Get virtual drive object by id

Args: vd_id (str): virtual drive id

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

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

Get virtual drive object by name

Args: vd_name (str): virtual drive name

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