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