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']
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
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
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.
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
109 def clear_cache(self): 110 """Clear cache (atomic) 111 """ 112 with self.__cache_lock: 113 self.__response_cache = {}
Clear cache (atomic)
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
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
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
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.
278 @staticmethod 279 def disable_singleton(): 280 """Disable StoreCLI class as signleton 281 """ 282 StorCLI.__set_singleton(False)
Disable StoreCLI class as signleton
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
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
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
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
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
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
116 @property 117 def metrics(self): 118 """(:obj:ControllerMetrics): controller metrics 119 """ 120 return ControllerMetrics(ctl=self)
(:obj:ControllerMetrics): controller metrics
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
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
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)
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)
98 def wrapper(*args, **kwargs): 99 """func effective wrapper 100 """ 101 return func(*args, **kwargs).lower()
func effective wrapper
98 def wrapper(*args, **kwargs): 99 """func effective wrapper 100 """ 101 return func(*args, **kwargs).lower()
func effective wrapper
98 def wrapper(*args, **kwargs): 99 """func effective wrapper 100 """ 101 return func(*args, **kwargs).lower()
func effective wrapper
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
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
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
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
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
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
98 def wrapper(*args, **kwargs): 99 """func effective wrapper 100 """ 101 return func(*args, **kwargs).lower()
func effective wrapper
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
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
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
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
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
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
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
584 @ property 585 def ids(self): 586 """(list of str): controllers id 587 """ 588 return self._ctl_ids
(list of str): controllers id
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
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
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
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
90 @property 91 def ctl_id(self) -> int: 92 """(str): enclosure controller id 93 """ 94 return self._ctl_id
(str): enclosure controller id
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
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
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
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
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
190 @property 191 def ctl_id(self) -> int: 192 """(str): enclosures controller id 193 """ 194 return self._ctl_id
(str): enclosures controller id
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
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
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
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
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
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.
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.
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
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
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
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
142 @property 143 def metrics(self): 144 """(dict): drive metrics 145 """ 146 return DriveMetrics(self)
(dict): drive metrics
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
114 def wrapper(*args, **kwargs): 115 """func effective wrapper 116 """ 117 return func(*args, **kwargs).upper()
func effective wrapper
114 def wrapper(*args, **kwargs): 115 """func effective wrapper 116 """ 117 return func(*args, **kwargs).upper()
func effective wrapper
114 def wrapper(*args, **kwargs): 115 """func effective wrapper 116 """ 117 return func(*args, **kwargs).upper()
func effective wrapper
114 def wrapper(*args, **kwargs): 115 """func effective wrapper 116 """ 117 return func(*args, **kwargs).upper()
func effective wrapper
114 def wrapper(*args, **kwargs): 115 """func effective wrapper 116 """ 117 return func(*args, **kwargs).upper()
func effective wrapper
114 def wrapper(*args, **kwargs): 115 """func effective wrapper 116 """ 117 return func(*args, **kwargs).upper()
func effective wrapper
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
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']
(str): drive connection link speed
242 @property 243 def ctl_id(self): 244 """(str): drive controller id 245 """ 246 return self._ctl_id
(str): drive controller id
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
254 @property 255 def encl_id(self): 256 """(str): dirve enclosure id 257 """ 258 return self._encl_id
(str): dirve enclosure id
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
579 @property 580 def ctl_id(self) -> int: 581 """(str): enclosures controller id 582 """ 583 return self._ctl_id
(str): enclosures controller id
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
591 @property 592 def encl_id(self) -> int: 593 """(str): enclosure id 594 """ 595 return self._encl_id
(str): enclosure id
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
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
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
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
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
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
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
128 @property 129 def metrics(self): 130 """(:obj:VirtualDriveMetrics): virtual drive metrics 131 """ 132 return VirtualDriveMetrics(self)
(:obj:VirtualDriveMetrics): virtual drive metrics
98 def wrapper(*args, **kwargs): 99 """func effective wrapper 100 """ 101 return func(*args, **kwargs).lower()
func effective wrapper
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
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)
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,...)
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
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
98 def wrapper(*args, **kwargs): 99 """func effective wrapper 100 """ 101 return func(*args, **kwargs).lower()
func effective wrapper
220 @property 221 def ctl_id(self): 222 """(str): virtual drive controller id 223 """ 224 return self._ctl_id
(str): virtual drive controller id
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
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
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
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
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
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
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
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
98 def wrapper(*args, **kwargs): 99 """func effective wrapper 100 """ 101 return func(*args, **kwargs).lower()
func effective wrapper
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
781 @property 782 def ctl_id(self): 783 """(str): virtual drives controller id 784 """ 785 return self._ctl_id
(str): virtual drives controller id
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
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
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
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