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']
27class StorCLI(object): 28 """StorCLI command line wrapper 29 30 Instance of this class is storcli command line wrapper 31 32 Args: 33 binary (str): storcli binary or full path to the binary 34 35 Properties: 36 cache_enable (boolean): enable disable resposne cache (also setter) 37 cache (dict): get / set raw cache content 38 39 Methods: 40 run (dict): output data from command line 41 check_response_status (): check ouput command line status from storcli 42 clear_cache (): purge cache 43 44 TODO: 45 * implement TTL for cache 46 47 """ 48 __singleton_instance = None 49 __cache_lock = threading.Lock() 50 __cache_enabled = False 51 __response_cache = {} 52 __cmdrunner = None 53 54 def __new__(cls, *args, **kwargs): 55 """Thread safe singleton 56 """ 57 global _SINGLETON_STORCLI_MODULE_LOCK 58 with _SINGLETON_STORCLI_MODULE_LOCK: 59 if _SINGLETON_STORCLI_MODULE_ENABLE: 60 if StorCLI.__singleton_instance is None: 61 StorCLI.__singleton_instance = super( 62 StorCLI, cls).__new__(cls) 63 return StorCLI.__singleton_instance 64 else: 65 return super(StorCLI, cls).__new__(cls) 66 67 def __init__(self, binary='storcli64', cmdrunner: cmdRunner.CMDRunner = None): 68 """Constructor - create StorCLI object wrapper 69 70 Args: 71 binary (str): storcli binary or full path to the binary 72 """ 73 74 if not _SINGLETON_STORCLI_MODULE_ENABLE or (_SINGLETON_STORCLI_MODULE_ENABLE and (not hasattr(self, '_StorCLI__cmdrunner') or self.__cmdrunner is None)): 75 # Do not override __cmdrunner if it is already set in singleton mode 76 if cmdrunner is None: 77 if self.__cmdrunner is None: 78 self.__cmdrunner = cmdRunner.CMDRunner() 79 else: 80 self.__cmdrunner = cmdrunner 81 82 if _SINGLETON_STORCLI_MODULE_ENABLE: 83 if not hasattr(self, '_storcli'): 84 # do not override _storcli in singleton if already exist 85 self._storcli = self.__cmdrunner.binaryCheck(binary) 86 87 if not _SINGLETON_STORCLI_MODULE_ENABLE: 88 # dont share singleton lock and binary 89 self._storcli = self.__cmdrunner.binaryCheck(binary) 90 self.__cache_lock = threading.Lock() 91 92 def set_cmdrunner(self, cmdrunner: cmdRunner.CMDRunner): 93 """ 94 Set command runner object. 95 This is only useful for testing. 96 """ 97 self.__cmdrunner = cmdrunner 98 99 @property 100 def cache_enable(self): 101 """Enable/Disable resposne cache (atomic) 102 103 Returns: 104 bool: true/false 105 """ 106 107 return self.__cache_enabled 108 109 @cache_enable.setter 110 def cache_enable(self, value): 111 with self.__cache_lock: 112 self.__cache_enabled = value 113 114 def clear_cache(self): 115 """Clear cache (atomic) 116 """ 117 with self.__cache_lock: 118 self.__response_cache = {} 119 120 @property 121 def cache(self): 122 """Get/Set raw cache 123 124 Args: 125 (dict): raw cache 126 127 Returns: 128 (dict): cache 129 """ 130 return self.__response_cache 131 132 @cache.setter 133 def cache(self, value): 134 with self.__cache_lock: 135 self.__response_cache = value 136 137 @staticmethod 138 def check_response_status(cmd, out): 139 """Check ouput command line status from storcli. 140 141 Args: 142 cmd (list of str): full command line 143 out (dict): output from command line 144 145 Raises: 146 StorCliCmdError 147 """ 148 cmd_status = common.response_cmd(out) 149 if cmd_status['Status'] == 'Failure': 150 if 'Detailed Status' in cmd_status: 151 raise exc.StorCliCmdError( 152 cmd, "{0}".format(cmd_status['Detailed Status'])) 153 else: 154 raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status)) 155 156 def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs): 157 """Execute storcli command line with arguments. 158 159 Run command line and check output for errors. 160 161 Args: 162 args (list of str): cmd line arguments (without binary) 163 stdout (fd): controll subprocess stdout fd 164 stderr (fd): controll subporcess stderr fd 165 **kwargs: arguments to subprocess run 166 167 Returns: 168 dict: output data from command line 169 170 Raises: 171 exc.StorCliCmdError 172 exc.StorCliRunTimeError 173 exc.StorCliRunTimeout 174 """ 175 cmd = [self._storcli] 176 cmd.extend(args) 177 # output in JSON format 178 cmd.append('J') 179 cmd_cache_key = ''.join(cmd) 180 181 if self.cache_enable: 182 if cmd_cache_key in self.__response_cache: 183 return self.__response_cache[cmd_cache_key] 184 185 with self.__cache_lock: 186 try: 187 ret = self.__cmdrunner.run( 188 args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs) 189 try: 190 ret_json = json.loads(ret.stdout) 191 self.check_response_status(cmd, ret_json) 192 ret.check_returncode() 193 if self.cache_enable: 194 self.__response_cache[cmd_cache_key] = ret_json 195 return ret_json 196 except json.JSONDecodeError as err: 197 # legacy handler (Ralequi: I don't know if this is still needed or what exactly it does) 198 output = re.search('(^.*)Storage.*Command.*$', 199 ret.stdout, re.MULTILINE | re.DOTALL) 200 if output: 201 raise exc.StorCliCmdError(cmd, output.group(1)) 202 203 # Check if we can still parse the output 204 parsed = {} 205 for line in ret.stdout.splitlines(): 206 if '=' in line: 207 key, value = line.split('=', 1) 208 parsed[key.strip()] = value.strip() 209 210 if 'Status' in parsed: 211 return parsed 212 else: 213 raise exc.StorCliCmdError(cmd, str(err)) 214 215 except subprocess.TimeoutExpired as err: 216 raise exc.StorCliRunTimeout(err) 217 except subprocess.SubprocessError as err: 218 raise exc.StorCliRunTimeError(err) 219 220 # Singleton stuff 221 @staticmethod 222 def __set_singleton(value): 223 global _SINGLETON_STORCLI_MODULE_ENABLE 224 global _SINGLETON_STORCLI_MODULE_LOCK 225 with _SINGLETON_STORCLI_MODULE_LOCK: 226 _SINGLETON_STORCLI_MODULE_ENABLE = value 227 228 @staticmethod 229 def enable_singleton(): 230 """Enable StorCLI to be singleton on module level 231 232 Use StorCLI singleton across all objects. All pystorcli 233 class instances use their own StorCLI object. With enabled cache 234 we can speedup for example metric lookups. 235 236 """ 237 StorCLI.__set_singleton(True) 238 239 @staticmethod 240 def disable_singleton(): 241 """Disable StoreCLI class as signleton 242 """ 243 StorCLI.__set_singleton(False) 244 245 @staticmethod 246 def is_singleton() -> bool: 247 """Check if singleton is enabled 248 """ 249 return _SINGLETON_STORCLI_MODULE_ENABLE 250 251 @property 252 def full_version(self) -> str: 253 """Get storcli version as storcli returns 254 """ 255 out = self.run(['show']) 256 version = common.response_cmd(out)['CLI Version'] 257 258 return version 259 260 @property 261 def version(self) -> str: 262 """Get storcli version in a cleaner way 263 """ 264 import re 265 266 # Remove duplicated 0s 267 first_clean = re.sub('0+', '0', self.full_version.split(' ')[0]) 268 269 # Remove leading 0s 270 second_clean = re.sub('^0+', '', first_clean) 271 272 return second_clean 273 274 @property 275 def controllers(self) -> 'pystorcli2.controller.Controllers': 276 """Get list of controllers 277 """ 278 from . import Controllers 279 280 return Controllers(binary=self._storcli)
StorCLI command line wrapper
Instance of this class is storcli command line wrapper
Args: binary (str): storcli binary or full path to the binary
Properties: cache_enable (boolean): enable disable resposne cache (also setter) cache (dict): get / set raw cache content
Methods: run (dict): output data from command line check_response_status (): check ouput command line status from storcli clear_cache (): purge cache
TODO: * implement TTL for cache
67 def __init__(self, binary='storcli64', cmdrunner: cmdRunner.CMDRunner = None): 68 """Constructor - create StorCLI object wrapper 69 70 Args: 71 binary (str): storcli binary or full path to the binary 72 """ 73 74 if not _SINGLETON_STORCLI_MODULE_ENABLE or (_SINGLETON_STORCLI_MODULE_ENABLE and (not hasattr(self, '_StorCLI__cmdrunner') or self.__cmdrunner is None)): 75 # Do not override __cmdrunner if it is already set in singleton mode 76 if cmdrunner is None: 77 if self.__cmdrunner is None: 78 self.__cmdrunner = cmdRunner.CMDRunner() 79 else: 80 self.__cmdrunner = cmdrunner 81 82 if _SINGLETON_STORCLI_MODULE_ENABLE: 83 if not hasattr(self, '_storcli'): 84 # do not override _storcli in singleton if already exist 85 self._storcli = self.__cmdrunner.binaryCheck(binary) 86 87 if not _SINGLETON_STORCLI_MODULE_ENABLE: 88 # dont share singleton lock and binary 89 self._storcli = self.__cmdrunner.binaryCheck(binary) 90 self.__cache_lock = threading.Lock()
Constructor - create StorCLI object wrapper
Args: binary (str): storcli binary or full path to the binary
92 def set_cmdrunner(self, cmdrunner: cmdRunner.CMDRunner): 93 """ 94 Set command runner object. 95 This is only useful for testing. 96 """ 97 self.__cmdrunner = cmdrunner
Set command runner object. This is only useful for testing.
114 def clear_cache(self): 115 """Clear cache (atomic) 116 """ 117 with self.__cache_lock: 118 self.__response_cache = {}
Clear cache (atomic)
137 @staticmethod 138 def check_response_status(cmd, out): 139 """Check ouput command line status from storcli. 140 141 Args: 142 cmd (list of str): full command line 143 out (dict): output from command line 144 145 Raises: 146 StorCliCmdError 147 """ 148 cmd_status = common.response_cmd(out) 149 if cmd_status['Status'] == 'Failure': 150 if 'Detailed Status' in cmd_status: 151 raise exc.StorCliCmdError( 152 cmd, "{0}".format(cmd_status['Detailed Status'])) 153 else: 154 raise exc.StorCliCmdError(cmd, "{0}".format(cmd_status))
Check ouput command line status from storcli.
Args: cmd (list of str): full command line out (dict): output from command line
Raises: StorCliCmdError
156 def run(self, args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs): 157 """Execute storcli command line with arguments. 158 159 Run command line and check output for errors. 160 161 Args: 162 args (list of str): cmd line arguments (without binary) 163 stdout (fd): controll subprocess stdout fd 164 stderr (fd): controll subporcess stderr fd 165 **kwargs: arguments to subprocess run 166 167 Returns: 168 dict: output data from command line 169 170 Raises: 171 exc.StorCliCmdError 172 exc.StorCliRunTimeError 173 exc.StorCliRunTimeout 174 """ 175 cmd = [self._storcli] 176 cmd.extend(args) 177 # output in JSON format 178 cmd.append('J') 179 cmd_cache_key = ''.join(cmd) 180 181 if self.cache_enable: 182 if cmd_cache_key in self.__response_cache: 183 return self.__response_cache[cmd_cache_key] 184 185 with self.__cache_lock: 186 try: 187 ret = self.__cmdrunner.run( 188 args=cmd, stdout=stdout, stderr=stderr, universal_newlines=True, **kwargs) 189 try: 190 ret_json = json.loads(ret.stdout) 191 self.check_response_status(cmd, ret_json) 192 ret.check_returncode() 193 if self.cache_enable: 194 self.__response_cache[cmd_cache_key] = ret_json 195 return ret_json 196 except json.JSONDecodeError as err: 197 # legacy handler (Ralequi: I don't know if this is still needed or what exactly it does) 198 output = re.search('(^.*)Storage.*Command.*$', 199 ret.stdout, re.MULTILINE | re.DOTALL) 200 if output: 201 raise exc.StorCliCmdError(cmd, output.group(1)) 202 203 # Check if we can still parse the output 204 parsed = {} 205 for line in ret.stdout.splitlines(): 206 if '=' in line: 207 key, value = line.split('=', 1) 208 parsed[key.strip()] = value.strip() 209 210 if 'Status' in parsed: 211 return parsed 212 else: 213 raise exc.StorCliCmdError(cmd, str(err)) 214 215 except subprocess.TimeoutExpired as err: 216 raise exc.StorCliRunTimeout(err) 217 except subprocess.SubprocessError as err: 218 raise exc.StorCliRunTimeError(err)
Execute storcli command line with arguments.
Run command line and check output for errors.
Args: args (list of str): cmd line arguments (without binary) stdout (fd): controll subprocess stdout fd stderr (fd): controll subporcess stderr fd **kwargs: arguments to subprocess run
Returns: dict: output data from command line
Raises: exc.StorCliCmdError exc.StorCliRunTimeError exc.StorCliRunTimeout
228 @staticmethod 229 def enable_singleton(): 230 """Enable StorCLI to be singleton on module level 231 232 Use StorCLI singleton across all objects. All pystorcli 233 class instances use their own StorCLI object. With enabled cache 234 we can speedup for example metric lookups. 235 236 """ 237 StorCLI.__set_singleton(True)
Enable StorCLI to be singleton on module level
Use StorCLI singleton across all objects. All pystorcli class instances use their own StorCLI object. With enabled cache we can speedup for example metric lookups.
239 @staticmethod 240 def disable_singleton(): 241 """Disable StoreCLI class as signleton 242 """ 243 StorCLI.__set_singleton(False)
Disable StoreCLI class as signleton
188class Controller(object): 189 """StorCLI Controller 190 191 Instance of this class represents controller in StorCLI hierarchy 192 193 Args: 194 ctl_id (str): controller id 195 binary (str): storcli binary or full path to the binary 196 197 Properties: 198 id (str): controller id 199 name (str): controller cmd name 200 facts (dict): raw controller facts 201 metrics (:obj:ControllerMetrics): controller metrics 202 vds (list of :obj:virtualdrive.VirtualDrives): controller virtual drives 203 encls (:obj:enclosure.Enclosures): controller enclosures 204 autorebuild (dict): current auto rebuild state (also setter) 205 foreignautoimport (dict): imports foreign configuration automatically at boot (also setter) 206 patrolread (dict): current patrol read settings (also setter) 207 208 Methods: 209 create_vd (:obj:VirtualDrive): create virtual drive 210 set_patrolread (dict): configures patrol read state and schedule 211 patrolread_start (dict): starts a patrol read on controller 212 patrolread_pause (dict): pauses patrol read on controller 213 patrolread_resume (dict): resumes patrol read on controller 214 patrolread_stop (dict): stops patrol read if running on controller 215 patrolread_running (bool): check if patrol read is running on controller 216 217 TODO: 218 Implement missing methods: 219 * patrol read progress 220 """ 221 222 def __init__(self, ctl_id, binary='storcli64'): 223 """Constructor - create StorCLI Controller object 224 225 Args: 226 ctl_id (str): controller id 227 binary (str): storcli binary or full path to the binary 228 """ 229 self._ctl_id = ctl_id 230 self._binary = binary 231 self._storcli = StorCLI(binary) 232 self._name = '/c{0}'.format(self._ctl_id) 233 234 self._exist() 235 236 def __str__(self): 237 return '{0}'.format(common.response_data(self._run(['show']))) 238 239 def _run(self, args, **kwargs): 240 args = args[:] 241 args.insert(0, self._name) 242 return self._storcli.run(args, **kwargs) 243 244 def _exist(self): 245 try: 246 self._run(['show']) 247 except exc.StorCliCmdError: 248 raise exc.StorCliMissingError( 249 self.__class__.__name__, self._name) from None 250 251 @property 252 def id(self): 253 """ (str): controller id 254 """ 255 return self._ctl_id 256 257 @property 258 def name(self): 259 """ (str): controller cmd name 260 """ 261 return self._name 262 263 @property 264 def facts(self): 265 """ (dict): raw controller facts 266 """ 267 args = [ 268 'show', 269 'all' 270 ] 271 return common.response_data(self._run(args)) 272 273 @property 274 def metrics(self): 275 """(:obj:ControllerMetrics): controller metrics 276 """ 277 return ControllerMetrics(ctl=self) 278 279 @property 280 def vds(self): 281 """(:obj:virtualdrive.VirtualDrives): controllers virtual drives 282 """ 283 return virtualdrive.VirtualDrives(ctl_id=self._ctl_id, binary=self._binary) 284 285 @property 286 def encls(self): 287 """(:obj:enclosure.Enclosures): controller enclosures 288 """ 289 return enclosure.Enclosures(ctl_id=self._ctl_id, binary=self._binary) 290 291 @property 292 def drives_ids(self) -> List[str]: 293 """(list of str): list of drives ids in format (e:s) 294 """ 295 drives = [] 296 for encl in self.encls: 297 for id in encl.drives.ids: 298 drives.append("{enc}:{id}".format(enc=encl.id, id=id)) 299 300 return drives 301 302 def create_vd(self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[virtualdrive.VirtualDrive]: 303 """Create virtual drive (raid) managed by current controller 304 305 Args: 306 name (str): virtual drive name 307 raid (str): virtual drive raid level (raid0, raid1, ...) 308 drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z) 309 strip (str, optional): virtual drive raid strip size 310 311 Returns: 312 (None): no virtual drive created with name 313 (:obj:virtualdrive.VirtualDrive) 314 """ 315 args = [ 316 'add', 317 'vd', 318 'r{0}'.format(raid), 319 'name={0}'.format(name), 320 'drives={0}'.format(drives), 321 'strip={0}'.format(strip) 322 ] 323 324 try: 325 if int(raid) >= 10 and PDperArray is None: 326 # Try to count the number of drives in the array 327 # The format of the drives argument is e:s|e:s-x|e:s-x,y;e:s-x,y,z 328 329 numDrives = common.count_drives(drives) 330 331 if numDrives % 2 != 0 and numDrives % 3 == 0: 332 # In some scenarios, such as 9 drives with raid 60, 3 is a good pd number but 4 is not 333 # Must check for similar scenarios 334 # BTW we don't clearly understand what PDperArray is for and what exactly it does under the hood. More investigation is needed 335 PDperArray = numDrives//3 336 else: 337 PDperArray = numDrives//2 338 339 except ValueError: 340 pass 341 342 finally: 343 if raid == '00' and PDperArray is None: 344 PDperArray = 1 345 346 if PDperArray is not None: 347 args.append('PDperArray={0}'.format(PDperArray)) 348 349 self._run(args) 350 for vd in self.vds: 351 if name == vd.name: 352 return vd 353 return None 354 355 @property 356 @common.lower 357 def autorebuild(self): 358 """Get/Set auto rebuild state 359 360 One of the following options can be set (str): 361 on - enables autorebuild 362 off - disables autorebuild 363 364 Returns: 365 (str): on / off 366 """ 367 args = [ 368 'show', 369 'autorebuild' 370 ] 371 372 prop = common.response_property(self._run(args))[0] 373 return prop['Value'] 374 375 @autorebuild.setter 376 def autorebuild(self, value): 377 """ 378 """ 379 args = [ 380 'set', 381 'autorebuild={0}'.format(value) 382 ] 383 return common.response_setter(self._run(args)) 384 385 @property 386 @common.lower 387 def foreignautoimport(self): 388 """Get/Set auto foreign import configuration 389 390 One of the following options can be set (str): 391 on - enables foreignautoimport 392 off - disables foreignautoimport 393 394 Returns: 395 (str): on / off 396 """ 397 args = [ 398 'show', 399 'foreignautoimport' 400 ] 401 prop = common.response_property(self._run(args))[0] 402 return prop['Value'] 403 404 @foreignautoimport.setter 405 def foreignautoimport(self, value): 406 """ 407 """ 408 args = [ 409 'set', 410 'foreignautoimport={0}'.format(value) 411 ] 412 return common.response_setter(self._run(args)) 413 414 @property 415 @common.lower 416 def patrolread(self): 417 """Get/Set patrol read 418 419 One of the following options can be set (str): 420 on - enables patrol read 421 off - disables patrol read 422 423 Returns: 424 (str): on / off 425 """ 426 args = [ 427 'show', 428 'patrolread' 429 ] 430 431 for pr in common.response_property(self._run(args)): 432 if pr['Ctrl_Prop'] == "PR Mode": 433 if pr['Value'] == 'Disable': 434 return 'off' 435 else: 436 return 'on' 437 return 'off' 438 439 440 @patrolread.setter 441 def patrolread(self, value): 442 """ 443 """ 444 return self.set_patrolread(value) 445 446 447 def set_patrolread(self, value, mode='manual'): 448 """Set patrol read 449 450 Args: 451 value (str): on / off to configure patrol read state 452 mode (str): auto | manual to configure patrol read schedule 453 """ 454 args = [ 455 'set', 456 'patrolread={0}'.format(value) 457 ] 458 459 if value == 'on': 460 args.append('mode={0}'.format(mode)) 461 462 return common.response_setter(self._run(args)) 463 464 def patrolread_start(self): 465 """Starts the patrol read operation of the controller 466 467 Returns: 468 (dict): response cmd data 469 """ 470 args = [ 471 'start', 472 'patrolread' 473 ] 474 return common.response_cmd(self._run(args)) 475 476 def patrolread_stop(self): 477 """Stops the patrol read operation of the controller 478 479 Returns: 480 (dict): response cmd data 481 """ 482 args = [ 483 'stop', 484 'patrolread' 485 ] 486 return common.response_cmd(self._run(args)) 487 488 def patrolread_pause(self): 489 """Pauses the patrol read operation of the controller 490 491 Returns: 492 (dict): response cmd data 493 """ 494 args = [ 495 'pause', 496 'patrolread' 497 ] 498 return common.response_cmd(self._run(args)) 499 500 def patrolread_resume(self): 501 """Resumes the patrol read operation of the controller 502 503 Returns: 504 (dict): response cmd data 505 """ 506 args = [ 507 'resume', 508 'patrolread' 509 ] 510 return common.response_cmd(self._run(args)) 511 512 @property 513 def patrolread_running(self): 514 """Check if patrol read is running on the controller 515 516 Returns: 517 (bool): true / false 518 """ 519 args = [ 520 'show', 521 'patrolread' 522 ] 523 524 status = '' 525 for pr in common.response_property(self._run(args)): 526 if pr['Ctrl_Prop'] == "PR Current State": 527 status = pr['Value'] 528 return bool('Active' in status)
StorCLI Controller
Instance of this class represents controller in StorCLI hierarchy
Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary
Properties: id (str): controller id name (str): controller cmd name facts (dict): raw controller facts metrics (:obj:ControllerMetrics): controller metrics vds (list of :obj:virtualdrive.VirtualDrives): controller virtual drives encls (:obj:enclosure.Enclosures): controller enclosures autorebuild (dict): current auto rebuild state (also setter) foreignautoimport (dict): imports foreign configuration automatically at boot (also setter) patrolread (dict): current patrol read settings (also setter)
Methods: create_vd (:obj:VirtualDrive): create virtual drive set_patrolread (dict): configures patrol read state and schedule patrolread_start (dict): starts a patrol read on controller patrolread_pause (dict): pauses patrol read on controller patrolread_resume (dict): resumes patrol read on controller patrolread_stop (dict): stops patrol read if running on controller patrolread_running (bool): check if patrol read is running on controller
TODO: Implement missing methods: * patrol read progress
222 def __init__(self, ctl_id, binary='storcli64'): 223 """Constructor - create StorCLI Controller object 224 225 Args: 226 ctl_id (str): controller id 227 binary (str): storcli binary or full path to the binary 228 """ 229 self._ctl_id = ctl_id 230 self._binary = binary 231 self._storcli = StorCLI(binary) 232 self._name = '/c{0}'.format(self._ctl_id) 233 234 self._exist()
Constructor - create StorCLI Controller object
Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary
302 def create_vd(self, name: str, raid: str, drives: str, strip: str = '64', PDperArray: Optional[int] = None) -> Optional[virtualdrive.VirtualDrive]: 303 """Create virtual drive (raid) managed by current controller 304 305 Args: 306 name (str): virtual drive name 307 raid (str): virtual drive raid level (raid0, raid1, ...) 308 drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z) 309 strip (str, optional): virtual drive raid strip size 310 311 Returns: 312 (None): no virtual drive created with name 313 (:obj:virtualdrive.VirtualDrive) 314 """ 315 args = [ 316 'add', 317 'vd', 318 'r{0}'.format(raid), 319 'name={0}'.format(name), 320 'drives={0}'.format(drives), 321 'strip={0}'.format(strip) 322 ] 323 324 try: 325 if int(raid) >= 10 and PDperArray is None: 326 # Try to count the number of drives in the array 327 # The format of the drives argument is e:s|e:s-x|e:s-x,y;e:s-x,y,z 328 329 numDrives = common.count_drives(drives) 330 331 if numDrives % 2 != 0 and numDrives % 3 == 0: 332 # In some scenarios, such as 9 drives with raid 60, 3 is a good pd number but 4 is not 333 # Must check for similar scenarios 334 # BTW we don't clearly understand what PDperArray is for and what exactly it does under the hood. More investigation is needed 335 PDperArray = numDrives//3 336 else: 337 PDperArray = numDrives//2 338 339 except ValueError: 340 pass 341 342 finally: 343 if raid == '00' and PDperArray is None: 344 PDperArray = 1 345 346 if PDperArray is not None: 347 args.append('PDperArray={0}'.format(PDperArray)) 348 349 self._run(args) 350 for vd in self.vds: 351 if name == vd.name: 352 return vd 353 return None
Create virtual drive (raid) managed by current controller
Args: name (str): virtual drive name raid (str): virtual drive raid level (raid0, raid1, ...) drives (str): storcli drives expression (e:s|e:s-x|e:s-x,y;e:s-x,y,z) strip (str, optional): virtual drive raid strip size
Returns: (None): no virtual drive created with name (:obj:virtualdrive.VirtualDrive)
447 def set_patrolread(self, value, mode='manual'): 448 """Set patrol read 449 450 Args: 451 value (str): on / off to configure patrol read state 452 mode (str): auto | manual to configure patrol read schedule 453 """ 454 args = [ 455 'set', 456 'patrolread={0}'.format(value) 457 ] 458 459 if value == 'on': 460 args.append('mode={0}'.format(mode)) 461 462 return common.response_setter(self._run(args))
Set patrol read
Args: value (str): on / off to configure patrol read state mode (str): auto | manual to configure patrol read schedule
464 def patrolread_start(self): 465 """Starts the patrol read operation of the controller 466 467 Returns: 468 (dict): response cmd data 469 """ 470 args = [ 471 'start', 472 'patrolread' 473 ] 474 return common.response_cmd(self._run(args))
Starts the patrol read operation of the controller
Returns: (dict): response cmd data
476 def patrolread_stop(self): 477 """Stops the patrol read operation of the controller 478 479 Returns: 480 (dict): response cmd data 481 """ 482 args = [ 483 'stop', 484 'patrolread' 485 ] 486 return common.response_cmd(self._run(args))
Stops the patrol read operation of the controller
Returns: (dict): response cmd data
488 def patrolread_pause(self): 489 """Pauses the patrol read operation of the controller 490 491 Returns: 492 (dict): response cmd data 493 """ 494 args = [ 495 'pause', 496 'patrolread' 497 ] 498 return common.response_cmd(self._run(args))
Pauses the patrol read operation of the controller
Returns: (dict): response cmd data
500 def patrolread_resume(self): 501 """Resumes the patrol read operation of the controller 502 503 Returns: 504 (dict): response cmd data 505 """ 506 args = [ 507 'resume', 508 'patrolread' 509 ] 510 return common.response_cmd(self._run(args))
Resumes the patrol read operation of the controller
Returns: (dict): response cmd data
531class Controllers(object): 532 """StorCLI Controllers 533 534 Instance of this class is iterable with :obj:Controller as item 535 536 Args: 537 binary (str): storcli binary or full path to the binary 538 539 Properties: 540 ids (list of str): list of controllers id 541 542 Methods: 543 get_clt (:obj:Controller): return controller object by id 544 """ 545 546 def __init__(self, binary='storcli64'): 547 """Constructor - create StorCLI Controllers object 548 549 Args: 550 binary (str): storcli binary or full path to the binary 551 """ 552 self._binary = binary 553 self._storcli = StorCLI(binary) 554 555 @ property 556 def _ctl_ids(self) -> List[str]: 557 out = self._storcli.run(['show']) 558 response = common.response_data(out) 559 560 if "Number of Controllers" in response and response["Number of Controllers"] == 0: 561 return [] 562 else: 563 return [ctl['Ctl'] for ctl in common.response_data_subkey(out, ['System Overview', 'IT System Overview'])] 564 565 @ property 566 def _ctls(self): 567 for ctl_id in self._ctl_ids: 568 yield Controller(ctl_id=ctl_id, binary=self._binary) 569 570 def __iter__(self): 571 return self._ctls 572 573 @ property 574 def ids(self): 575 """(list of str): controllers id 576 """ 577 return self._ctl_ids 578 579 def get_ctl(self, ctl_id: int) -> Optional[Controller]: 580 """Get controller object by id 581 582 Args: 583 ctl_id (str): controller id 584 585 Returns: 586 (None): no controller with id 587 (:obj:Controller): controller object 588 """ 589 for ctl in self: 590 if ctl.id == ctl_id: 591 return ctl 592 return None
StorCLI Controllers
Instance of this class is iterable with :obj:Controller as item
Args: binary (str): storcli binary or full path to the binary
Properties: ids (list of str): list of controllers id
Methods: get_clt (:obj:Controller): return controller object by id
546 def __init__(self, binary='storcli64'): 547 """Constructor - create StorCLI Controllers object 548 549 Args: 550 binary (str): storcli binary or full path to the binary 551 """ 552 self._binary = binary 553 self._storcli = StorCLI(binary)
Constructor - create StorCLI Controllers object
Args: binary (str): storcli binary or full path to the binary
579 def get_ctl(self, ctl_id: int) -> Optional[Controller]: 580 """Get controller object by id 581 582 Args: 583 ctl_id (str): controller id 584 585 Returns: 586 (None): no controller with id 587 (:obj:Controller): controller object 588 """ 589 for ctl in self: 590 if ctl.id == ctl_id: 591 return ctl 592 return None
Get controller object by id
Args: ctl_id (str): controller id
Returns: (None): no controller with id (:obj:Controller): controller object
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
22class DriveState(Enum): 23 """Drive status 24 """ 25 # From storcli 7.1704 26 # EID=Enclosure Device ID|Slt=Slot No|DID=Device ID|DG=DriveGroup 27 # DHS=Dedicated Hot Spare|UGood=Unconfigured Good|GHS=Global Hotspare 28 # UBad=Unconfigured Bad|Sntze=Sanitize|Onln=Online|Offln=Offline|Intf=Interface 29 # Med=Media Type|SED=Self Encryptive Drive|PI=Protection Info 30 # SeSz=Sector Size|Sp=Spun|U=Up|D=Down|T=Transition|F=Foreign 31 # UGUnsp=UGood Unsupported|UGShld=UGood shielded|HSPShld=Hotspare shielded 32 # CFShld=Configured shielded|Cpybck=CopyBack|CBShld=Copyback Shielded 33 # UBUnsp=UBad Unsupported|Rbld=Rebuild 34 35 DHS = 'Dedicated Hot Spare' 36 UGood = 'Unconfigured Good' 37 GHS = 'Global Hotspare' 38 UBad = 'Unconfigured Bad' 39 Sntze = 'Sanitize' 40 Onln = 'Online' 41 Offln = 'Offline' 42 Failed = 'Failed' 43 SED = 'Self Encryptive Drive' 44 UGUnsp = 'UGood Unsupported' 45 UGShld = 'UGood shielded' 46 HSPShld = 'Hotspare shielded' 47 CFShld = 'Configured shielded' 48 Cpybck = 'CopyBack' 49 CBShld = 'Copyback Shielded' 50 UBUnsp = 'UBad Unsupported' 51 Rbld = 'Rebuild' 52 Missing = 'Missing' 53 JBOD = 'JBOD' 54 55 def __str__(self) -> str: 56 return self.value 57 58 def is_good(self) -> bool: 59 """Check if drive is good according to status""" 60 good_states = [ 61 DriveState.DHS, 62 DriveState.UGood, 63 DriveState.GHS, 64 # DriveState.Sntze, ?? 65 DriveState.Onln, 66 DriveState.SED, 67 # DriveState.UGUnsp, ?? 68 DriveState.UGShld, 69 DriveState.HSPShld, 70 DriveState.CFShld, 71 DriveState.Cpybck, 72 DriveState.CBShld, 73 DriveState.Rbld, 74 DriveState.JBOD 75 ] 76 77 return self in good_states 78 79 def is_configured(self) -> bool: 80 """Check if drive is configured according to status""" 81 configured_states = [ 82 DriveState.DHS, 83 DriveState.GHS, 84 # DriveState.Sntze, ?? 85 DriveState.Onln, 86 DriveState.SED, 87 # DriveState.UGShld, ?? 88 DriveState.HSPShld, 89 DriveState.CFShld, 90 DriveState.Cpybck, 91 DriveState.CBShld, 92 DriveState.Rbld, 93 DriveState.JBOD 94 ] 95 96 return self in configured_states 97 98 def is_settable(self) -> bool: 99 """Check if this status can be directly set. Not all statuses can be set directly.""" 100 # online | offline | missing | good 101 102 settable_states = [ 103 DriveState.Onln, 104 DriveState.Offln, 105 DriveState.Missing, 106 DriveState.UGood, 107 DriveState.JBOD 108 ] 109 110 return self in settable_states 111 112 def settable_str(self) -> str: 113 """Get string representation of settable status. Storcli uses different strings for set command than for show command.""" 114 if self == DriveState.Onln: 115 return 'online' 116 elif self == DriveState.Offln: 117 return 'offline' 118 elif self == DriveState.Missing: 119 return 'missing' 120 elif self == DriveState.UGood: 121 return 'good' 122 elif self == DriveState.JBOD: 123 return 'jbod' 124 else: 125 raise ValueError('This status is not settable') 126 127 @staticmethod 128 def from_string(status: str) -> 'DriveState': 129 """Get DriveState from string""" 130 for drive_status in DriveState: 131 if drive_status.name.lower() == status.lower() or drive_status.value.lower() == status.lower(): 132 return drive_status 133 raise ValueError('Invalid drive status: {0}'.format(status))
Drive status
58 def is_good(self) -> bool: 59 """Check if drive is good according to status""" 60 good_states = [ 61 DriveState.DHS, 62 DriveState.UGood, 63 DriveState.GHS, 64 # DriveState.Sntze, ?? 65 DriveState.Onln, 66 DriveState.SED, 67 # DriveState.UGUnsp, ?? 68 DriveState.UGShld, 69 DriveState.HSPShld, 70 DriveState.CFShld, 71 DriveState.Cpybck, 72 DriveState.CBShld, 73 DriveState.Rbld, 74 DriveState.JBOD 75 ] 76 77 return self in good_states
Check if drive is good according to status
79 def is_configured(self) -> bool: 80 """Check if drive is configured according to status""" 81 configured_states = [ 82 DriveState.DHS, 83 DriveState.GHS, 84 # DriveState.Sntze, ?? 85 DriveState.Onln, 86 DriveState.SED, 87 # DriveState.UGShld, ?? 88 DriveState.HSPShld, 89 DriveState.CFShld, 90 DriveState.Cpybck, 91 DriveState.CBShld, 92 DriveState.Rbld, 93 DriveState.JBOD 94 ] 95 96 return self in configured_states
Check if drive is configured according to status
98 def is_settable(self) -> bool: 99 """Check if this status can be directly set. Not all statuses can be set directly.""" 100 # online | offline | missing | good 101 102 settable_states = [ 103 DriveState.Onln, 104 DriveState.Offln, 105 DriveState.Missing, 106 DriveState.UGood, 107 DriveState.JBOD 108 ] 109 110 return self in settable_states
Check if this status can be directly set. Not all statuses can be set directly.
112 def settable_str(self) -> str: 113 """Get string representation of settable status. Storcli uses different strings for set command than for show command.""" 114 if self == DriveState.Onln: 115 return 'online' 116 elif self == DriveState.Offln: 117 return 'offline' 118 elif self == DriveState.Missing: 119 return 'missing' 120 elif self == DriveState.UGood: 121 return 'good' 122 elif self == DriveState.JBOD: 123 return 'jbod' 124 else: 125 raise ValueError('This status is not settable')
Get string representation of settable status. Storcli uses different strings for set command than for show command.
127 @staticmethod 128 def from_string(status: str) -> 'DriveState': 129 """Get DriveState from string""" 130 for drive_status in DriveState: 131 if drive_status.name.lower() == status.lower() or drive_status.value.lower() == status.lower(): 132 return drive_status 133 raise ValueError('Invalid drive status: {0}'.format(status))
Get DriveState from string
Inherited Members
- enum.Enum
- name
- value
301class Drive(object): 302 """StorCLI Drive 303 304 Instance of this class represents drive in StorCLI hierarchy 305 306 Args: 307 ctl_id (str): controller id 308 encl_id (str): enclosure id 309 slot_id (str): slot id 310 binary (str): storcli binary or full path to the binary 311 312 Properties: 313 id (str): drive id 314 name (str): drive cmd name 315 facts (dict): raw drive facts 316 metrics (dict): drive metrics for monitoring 317 size (str): drive size 318 interface (str): SATA / SAS 319 medium (str): SSD / HDD 320 model (str): drive model informations 321 serial (str): drive serial number 322 wwn (str): drive wwn 323 firmware (str): drive firmware version 324 device_speed (str): drive speed 325 linke_speed (str): drive connection link speed 326 ctl_id (str): drive controller id 327 ctl (:obj:controller.Controller): drive controller 328 encl_id (str): drive enclosure 329 encl (:obj:enclosure.Enclosure): drive enclosure 330 phyerrorcounters (dict): drive error counters (also setter) 331 state (str): drive state (also setter) 332 spin (str): drive spin state (also setter) 333 334 335 Methods: 336 init_start (dict): starts the initialization process on a drive 337 init_stop (dict): stops an initialization process running on a drive 338 init_running (bool): check if initialization is running on a drive 339 erase_start (dict): securely erases non-SED drive 340 erase_stop (dict): stops an erase process running on a drive 341 erase_running (bool): check if erase is running on a drive 342 hotparedrive_create (dict): add drive to hotspares 343 hotparedrive_delete (dict): delete drive from hotspare 344 345 TODO: 346 Implement missing methods: 347 * start rebuild 348 * stop rebuild 349 * pause rebuild 350 * resume rebuild 351 * rebuild running 352 """ 353 354 def __init__(self, ctl_id, encl_id, slot_id, binary='storcli64'): 355 """Constructor - create StorCLI Drive object 356 357 Args: 358 ctl_id (str): controller id 359 encl_id (str): enclosure id 360 slot_id (str): slot id 361 binary (str): storcli binary or full path to the binary 362 """ 363 self._ctl_id = ctl_id 364 self._encl_id = encl_id 365 self._slot_id = slot_id 366 self._binary = binary 367 self._storcli = StorCLI(binary) 368 self._name = '/c{0}/e{1}/s{2}'.format(self._ctl_id, 369 self._encl_id, self._slot_id) 370 371 self._exist() 372 373 @staticmethod 374 def _response_properties(out): 375 return common.response_data(out)['Drive Information'][0] 376 377 def _response_attributes(self, out): 378 detailed_info = ('Drive /c{0}/e{1}/s{2}' 379 ' - Detailed Information'.format(self._ctl_id, self._encl_id, self._slot_id)) 380 attr = 'Drive /c{0}/e{1}/s{2} Device attributes'.format( 381 self._ctl_id, self._encl_id, self._slot_id) 382 return common.response_data(out)[detailed_info][attr] 383 384 def _run(self, args, **kwargs): 385 args = args[:] 386 args.insert(0, self._name) 387 return self._storcli.run(args, **kwargs) 388 389 def _exist(self): 390 try: 391 self._run(['show']) 392 except exc.StorCliCmdError: 393 raise exc.StorCliMissingError( 394 self.__class__.__name__, self._name) from None 395 396 @property 397 def id(self): 398 """(str): drive id 399 """ 400 return self._slot_id 401 402 @property 403 def name(self): 404 """(str): drive cmd name 405 """ 406 return self._name 407 408 @property 409 def facts(self): 410 """(dict): raw drive facts 411 """ 412 args = [ 413 'show', 414 'all' 415 ] 416 return common.response_data(self._run(args)) 417 418 @property 419 def metrics(self): 420 """(dict): drive metrics 421 """ 422 return DriveMetrics(self) 423 424 @property 425 def size(self): 426 """(str): drive size 427 """ 428 args = [ 429 'show' 430 ] 431 return self._response_properties(self._run(args))['Size'] 432 433 @property 434 @common.upper 435 def interface(self): 436 """(str): SATA / SAS 437 """ 438 args = [ 439 'show' 440 ] 441 return self._response_properties(self._run(args))['Intf'] 442 443 @property 444 @common.upper 445 def medium(self): 446 """(str): SSD / HDD 447 """ 448 args = [ 449 'show' 450 ] 451 return self._response_properties(self._run(args))['Med'] 452 453 @property 454 @common.upper 455 @common.strip 456 def model(self): 457 """(str): drive model informations 458 """ 459 args = [ 460 'show' 461 ] 462 return self._response_properties(self._run(args))['Model'] 463 464 @property 465 @common.upper 466 @common.strip 467 def serial(self): 468 """(str): drive serial number 469 """ 470 args = [ 471 'show', 472 'all' 473 ] 474 return self._response_attributes(self._run(args))['SN'] 475 476 @property 477 @common.upper 478 def wwn(self): 479 """(str): drive wwn 480 """ 481 args = [ 482 'show', 483 'all' 484 ] 485 return self._response_attributes(self._run(args))['WWN'] 486 487 @property 488 @common.upper 489 def firmware(self): 490 """(str): drive firmware version 491 """ 492 args = [ 493 'show', 494 'all' 495 ] 496 return self._response_attributes(self._run(args))['Firmware Revision'] 497 498 @property 499 def device_speed(self): 500 """(str): drive speed 501 """ 502 args = [ 503 'show', 504 'all' 505 ] 506 return self._response_attributes(self._run(args))['Device Speed'] 507 508 @property 509 def link_speed(self): 510 """(str): drive connection link speed 511 """ 512 args = [ 513 'show', 514 'all' 515 ] 516 return self._response_attributes(self._run(args))['Link Speed'] 517 518 @property 519 def ctl_id(self): 520 """(str): drive controller id 521 """ 522 return self._ctl_id 523 524 @property 525 def ctl(self): 526 """(:obj:controller.Controller): drive controller 527 """ 528 return controller.Controller(ctl_id=self._ctl_id, binary=self._binary) 529 530 @property 531 def encl_id(self): 532 """(str): dirve enclosure id 533 """ 534 return self._encl_id 535 536 @property 537 def encl(self): 538 """(:obj:enclosure.Enclosure): drive enclosure 539 """ 540 return enclosure.Enclosure(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary) 541 542 @property 543 def vd_id(self) -> Union[None, int]: 544 """(int): drive virtual drive id if any 545 """ 546 args = [ 547 'show'] 548 dg = self._response_properties(self._run(args))['DG'] 549 550 if isinstance(dg, int): 551 return dg 552 else: 553 return None 554 555 @property 556 def vd(self) -> Union[None, virtualdrive.VirtualDrive]: 557 """(:obj:virtualdrive.VirtualDrive): get the virtual drive if any 558 """ 559 if self.vd_id is None: 560 return None 561 else: 562 return virtualdrive.VirtualDrive(self._ctl_id, self.vd_id, self._binary) 563 564 def init_start(self): 565 """Start initialization of a drive 566 567 Returns: 568 (dict): resposne cmd data 569 """ 570 args = [ 571 'start', 572 'initialization' 573 ] 574 return common.response_cmd(self._run(args)) 575 576 def init_stop(self): 577 """Stop initialization on a drive 578 579 A stopped initialization process cannot be resumed. 580 581 Returns: 582 (dict): resposne cmd data 583 """ 584 args = [ 585 'stop', 586 'initialization' 587 ] 588 return common.response_cmd(self._run(args)) 589 590 @property 591 def init_running(self): 592 """Check if initialization process is running on a drive 593 594 Returns: 595 (bool): true / false 596 """ 597 args = [ 598 'show', 599 'initialization' 600 ] 601 602 status = common.response_data(self._run(args))[0]['Status'] 603 return bool(status == 'In progress') 604 605 def erase_start(self, mode='simple'): 606 """Securely erases non-SED drives with specified erase pattern 607 608 Args: 609 mode (str): 610 simple - Single pass, single pattern write 611 normal - Three pass, three pattern write 612 thorough - Nine pass, repeats the normal write 3 times 613 standard - Applicable only for DFF's 614 threepass - Three pass, pass1 random pattern write, pass2,3 write zero, verify 615 crypto - Applicable only for ISE capable drives 616 PatternA|PatternB - an 8-Bit binary pattern to overwrite the data. 617 618 Returns: 619 (dict): resposne cmd data 620 """ 621 args = [ 622 'start', 623 'erase', 624 '{0}'.format(mode) 625 ] 626 return common.response_cmd(self._run(args)) 627 628 def erase_stop(self): 629 """Stops the erase operation of a drive 630 631 Returns: 632 (dict): resposne cmd data 633 """ 634 args = [ 635 'stop', 636 'erase' 637 ] 638 return common.response_cmd(self._run(args)) 639 640 @property 641 def erase_running(self): 642 """Check if erase process is running on a drive 643 644 Returns: 645 (bool): true / false 646 """ 647 args = [ 648 'show', 649 'erase' 650 ] 651 652 status = common.response_data(self._run(args))[0]['Status'] 653 return bool(status == 'In progress') 654 655 @property 656 def phyerrorcounters(self): 657 """Get/Reset the drive phyerrorcounters 658 659 Reset drive error counters with (str) 0 660 """ 661 args = [ 662 'show', 663 'phyerrorcounters' 664 ] 665 return common.response_data(self._run(args))[self._name] 666 667 @phyerrorcounters.setter 668 def phyerrorcounters_reset(self): 669 """ 670 """ 671 args = [ 672 'reset', 673 'phyerrorcounters' 674 ] 675 return common.response_cmd(self._run(args)) 676 677 @property 678 def state(self) -> DriveState: 679 """Get/Set drive state 680 """ 681 args = [ 682 'show' 683 ] 684 685 state = self._response_properties(self._run(args))['State'] 686 687 return DriveState.from_string(state) 688 689 @state.setter 690 def state(self, value: Union[str, DriveState]): 691 """ Set drive state 692 """ 693 694 return self.set_state(value, force=False) 695 696 def set_state(self, value: Union[str, DriveState], force: bool = False): 697 """ Set drive state 698 """ 699 # if DriveState, get the string value 700 if isinstance(value, DriveState): 701 value = value.settable_str() 702 703 args = [ 704 'set', 705 '{0}'.format(value) 706 ] 707 708 if force: 709 args.append('force') 710 711 return common.response_setter(self._run(args)) 712 713 @property 714 def spin(self): 715 """Get/Set drive spin status 716 717 One of the following states can be set (str): 718 up - spins up and set to unconfigured good 719 down - spins down an unconfigured drive and prepares it for removal 720 721 Returns: 722 (str): up / down 723 """ 724 args = [ 725 'show' 726 ] 727 728 spin = self._response_properties(self._run(args))['Sp'] 729 if spin == 'U': 730 return 'up' 731 return 'down' 732 733 @spin.setter 734 def spin(self, value): 735 """ 736 """ 737 if value == 'up': 738 spin = 'spinup' 739 elif value == 'down': 740 spin = 'spindown' 741 else: 742 spin = value 743 744 args = [ 745 '{0}'.format(spin) 746 ] 747 return common.response_setter(self._run(args)) 748 749 def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False): 750 """Creates a hotspare drive 751 752 Args: 753 dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...) 754 enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with. 755 If this option is specified, affinity is set; if it is not specified, 756 there is no affinity.NOTE Affinity cannot be removed once it is set 757 for a hotspare drive. 758 nonrevertible (bool): sets the drive as a nonrevertible hotspare 759 760 Returns: 761 (dict): resposne cmd data 762 """ 763 args = [ 764 'add', 765 'hotsparedrive' 766 ] 767 768 if dgs: 769 args.append("dgs={0}".format(dgs)) 770 if enclaffinity: 771 args.append('enclaffinity') 772 if nonrevertible: 773 args.append('nonrevertible') 774 return common.response_cmd(self._run(args)) 775 776 def hotparedrive_delete(self): 777 """Deletes drive from hotspares 778 779 Returns: 780 (dict): resposne cmd data 781 """ 782 args = [ 783 'delete', 784 'hotsparedrive' 785 ] 786 return common.response_cmd(self._run(args))
StorCLI Drive
Instance of this class represents drive in StorCLI hierarchy
Args: ctl_id (str): controller id encl_id (str): enclosure id slot_id (str): slot id binary (str): storcli binary or full path to the binary
Properties: id (str): drive id name (str): drive cmd name facts (dict): raw drive facts metrics (dict): drive metrics for monitoring size (str): drive size interface (str): SATA / SAS medium (str): SSD / HDD model (str): drive model informations serial (str): drive serial number wwn (str): drive wwn firmware (str): drive firmware version device_speed (str): drive speed linke_speed (str): drive connection link speed ctl_id (str): drive controller id ctl (:obj:controller.Controller): drive controller encl_id (str): drive enclosure encl (:obj:enclosure.Enclosure): drive enclosure phyerrorcounters (dict): drive error counters (also setter) state (str): drive state (also setter) spin (str): drive spin state (also setter)
Methods: init_start (dict): starts the initialization process on a drive init_stop (dict): stops an initialization process running on a drive init_running (bool): check if initialization is running on a drive erase_start (dict): securely erases non-SED drive erase_stop (dict): stops an erase process running on a drive erase_running (bool): check if erase is running on a drive hotparedrive_create (dict): add drive to hotspares hotparedrive_delete (dict): delete drive from hotspare
TODO: Implement missing methods: * start rebuild * stop rebuild * pause rebuild * resume rebuild * rebuild running
354 def __init__(self, ctl_id, encl_id, slot_id, binary='storcli64'): 355 """Constructor - create StorCLI Drive object 356 357 Args: 358 ctl_id (str): controller id 359 encl_id (str): enclosure id 360 slot_id (str): slot id 361 binary (str): storcli binary or full path to the binary 362 """ 363 self._ctl_id = ctl_id 364 self._encl_id = encl_id 365 self._slot_id = slot_id 366 self._binary = binary 367 self._storcli = StorCLI(binary) 368 self._name = '/c{0}/e{1}/s{2}'.format(self._ctl_id, 369 self._encl_id, self._slot_id) 370 371 self._exist()
Constructor - create StorCLI Drive object
Args: ctl_id (str): controller id encl_id (str): enclosure id slot_id (str): slot id binary (str): storcli binary or full path to the binary
(:obj:virtualdrive.VirtualDrive): get the virtual drive if any
564 def init_start(self): 565 """Start initialization of a drive 566 567 Returns: 568 (dict): resposne cmd data 569 """ 570 args = [ 571 'start', 572 'initialization' 573 ] 574 return common.response_cmd(self._run(args))
Start initialization of a drive
Returns: (dict): resposne cmd data
576 def init_stop(self): 577 """Stop initialization on a drive 578 579 A stopped initialization process cannot be resumed. 580 581 Returns: 582 (dict): resposne cmd data 583 """ 584 args = [ 585 'stop', 586 'initialization' 587 ] 588 return common.response_cmd(self._run(args))
Stop initialization on a drive
A stopped initialization process cannot be resumed.
Returns: (dict): resposne cmd data
605 def erase_start(self, mode='simple'): 606 """Securely erases non-SED drives with specified erase pattern 607 608 Args: 609 mode (str): 610 simple - Single pass, single pattern write 611 normal - Three pass, three pattern write 612 thorough - Nine pass, repeats the normal write 3 times 613 standard - Applicable only for DFF's 614 threepass - Three pass, pass1 random pattern write, pass2,3 write zero, verify 615 crypto - Applicable only for ISE capable drives 616 PatternA|PatternB - an 8-Bit binary pattern to overwrite the data. 617 618 Returns: 619 (dict): resposne cmd data 620 """ 621 args = [ 622 'start', 623 'erase', 624 '{0}'.format(mode) 625 ] 626 return common.response_cmd(self._run(args))
Securely erases non-SED drives with specified erase pattern
Args: mode (str): simple - Single pass, single pattern write normal - Three pass, three pattern write thorough - Nine pass, repeats the normal write 3 times standard - Applicable only for DFF's threepass - Three pass, pass1 random pattern write, pass2,3 write zero, verify crypto - Applicable only for ISE capable drives PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
Returns: (dict): resposne cmd data
628 def erase_stop(self): 629 """Stops the erase operation of a drive 630 631 Returns: 632 (dict): resposne cmd data 633 """ 634 args = [ 635 'stop', 636 'erase' 637 ] 638 return common.response_cmd(self._run(args))
Stops the erase operation of a drive
Returns: (dict): resposne cmd data
696 def set_state(self, value: Union[str, DriveState], force: bool = False): 697 """ Set drive state 698 """ 699 # if DriveState, get the string value 700 if isinstance(value, DriveState): 701 value = value.settable_str() 702 703 args = [ 704 'set', 705 '{0}'.format(value) 706 ] 707 708 if force: 709 args.append('force') 710 711 return common.response_setter(self._run(args))
Set drive state
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
749 def hotparedrive_create(self, dgs=None, enclaffinity=False, nonrevertible=False): 750 """Creates a hotspare drive 751 752 Args: 753 dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...) 754 enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with. 755 If this option is specified, affinity is set; if it is not specified, 756 there is no affinity.NOTE Affinity cannot be removed once it is set 757 for a hotspare drive. 758 nonrevertible (bool): sets the drive as a nonrevertible hotspare 759 760 Returns: 761 (dict): resposne cmd data 762 """ 763 args = [ 764 'add', 765 'hotsparedrive' 766 ] 767 768 if dgs: 769 args.append("dgs={0}".format(dgs)) 770 if enclaffinity: 771 args.append('enclaffinity') 772 if nonrevertible: 773 args.append('nonrevertible') 774 return common.response_cmd(self._run(args))
Creates a hotspare drive
Args: dgs (str): specifies the drive group to which the hotspare drive is dedicated (N|0,1,2...) enclaffinity (bool): Specifies the enclosure to which the hotspare is associated with. If this option is specified, affinity is set; if it is not specified, there is no affinity.NOTE Affinity cannot be removed once it is set for a hotspare drive. nonrevertible (bool): sets the drive as a nonrevertible hotspare
Returns: (dict): resposne cmd data
776 def hotparedrive_delete(self): 777 """Deletes drive from hotspares 778 779 Returns: 780 (dict): resposne cmd data 781 """ 782 args = [ 783 'delete', 784 'hotsparedrive' 785 ] 786 return common.response_cmd(self._run(args))
Deletes drive from hotspares
Returns: (dict): resposne cmd data
789class Drives(object): 790 """StorCLI drives 791 792 Instance of this class is iterable with :obj:Drive as item 793 794 Args: 795 ctl_id (str): controller id 796 encl_id (str): enclosure id 797 binary (str): storcli binary or full path to the binary 798 799 Properties: 800 ids (list of str): list of drives id 801 ctl_id (str): controller id where drives are located 802 encl_id (str): enclosure id where drives are located 803 ctl (:obj:controller.Controller): controller 804 encl (:obj:Enclosure): enclosure 805 806 807 Methods: 808 get_drive (:obj:Enclosure): return drive object by id 809 get_drive_range_ids (list of int): return list of drive ids in range 810 get_drive_range (:obj:Drives): return drives object in range 811 """ 812 813 def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'): 814 """Constructor - create StorCLI Enclosures object 815 816 Args: 817 ctl_id (str): controller id 818 binary (str): storcli binary or full path to the binary 819 """ 820 self._ctl_id: int = ctl_id 821 self._encl_id: int = encl_id 822 self._binary: str = binary 823 self._storcli: StorCLI = StorCLI(binary) 824 825 @property 826 def _drive_ids(self) -> List[int]: 827 args = [ 828 '/c{0}/e{1}/sall'.format(self._ctl_id, self._encl_id), 829 'show' 830 ] 831 832 if not self.encl.has_drives: 833 return [] 834 835 drives = common.response_data(self._storcli.run(args))[ 836 'Drive Information'] 837 return [int(drive['EID:Slt'].split(':')[1]) for drive in drives] 838 839 @property 840 def _drives(self): 841 for drive_id in self._drive_ids: 842 yield Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary) 843 844 def __iter__(self): 845 return self._drives 846 847 @property 848 def ids(self) -> List[int]: 849 """(list of str): list of enclosures id 850 """ 851 return self._drive_ids 852 853 @property 854 def ctl_id(self) -> int: 855 """(str): enclosures controller id 856 """ 857 return self._ctl_id 858 859 @property 860 def ctl(self): 861 """(:obj:controller.Controller): enclosures controller 862 """ 863 return controller.Controller(ctl_id=self._ctl_id, binary=self._binary) 864 865 @property 866 def encl_id(self) -> int: 867 """(str): enclosure id 868 """ 869 return self._encl_id 870 871 @property 872 def encl(self): 873 """(:obj:Enclosure): enclosure 874 """ 875 return enclosure.Enclosure(ctl_id=self._ctl_id, encl_id=self._encl_id, binary=self._binary) 876 877 def get_drive(self, drive_id: int) -> Optional[Drive]: 878 """Get drive object by id 879 880 Args: 881 drive_id (str): drive id 882 883 Returns: 884 (None): no drive with id 885 (:obj:Drive): drive object 886 """ 887 if drive_id in self._drive_ids: 888 return Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary) 889 else: 890 return None 891 892 def __getitem__(self, drive_id: int) -> Optional[Drive]: 893 return self.get_drive(drive_id) 894 895 def get_drive_range_ids(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None) -> List[int]: 896 """Get drive range list in the current enclosure 897 898 Args: 899 drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer 900 drive_id_end (Optional[int]): end of the range 901 """ 902 903 if drive_id_end: 904 # check that drive_id_begin is integer, if not raise exception 905 if not isinstance(drive_id_begin, int): 906 raise ValueError('drive_id_begin must be an integer') 907 908 # otherwise convert to string 909 drive_id_begin = '{0}-{1}'.format(drive_id_begin, drive_id_end) 910 911 # if drive_id_begin is an integer, convert to string 912 if isinstance(drive_id_begin, int): 913 drive_id_begin = str(drive_id_begin) 914 915 # get the list of drives 916 drive_ids = [] 917 for drive_id in drive_id_begin.split(','): 918 if '-' in drive_id: 919 range_begin = drive_id.split('-')[0] 920 range_end = drive_id.split('-')[1] 921 drive_ids.extend( 922 range(int(range_begin), int(range_end) + 1)) 923 else: 924 drive_ids.append(int(drive_id)) 925 926 return drive_ids 927 928 def get_drive_range(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None): 929 """Get drive range in the current enclosure 930 931 Args: 932 drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer 933 drive_id_end (Optional[int]): end of the range 934 """ 935 drive_ids = self.get_drive_range_ids(drive_id_begin, drive_id_end) 936 937 for drive_id in drive_ids: 938 yield Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
StorCLI drives
Instance of this class is iterable with :obj:Drive as item
Args: ctl_id (str): controller id encl_id (str): enclosure id binary (str): storcli binary or full path to the binary
Properties: ids (list of str): list of drives id ctl_id (str): controller id where drives are located encl_id (str): enclosure id where drives are located ctl (:obj:controller.Controller): controller encl (:obj:Enclosure): enclosure
Methods: get_drive (:obj:Enclosure): return drive object by id get_drive_range_ids (list of int): return list of drive ids in range get_drive_range (:obj:Drives): return drives object in range
813 def __init__(self, ctl_id: int, encl_id: int, binary: str = 'storcli64'): 814 """Constructor - create StorCLI Enclosures object 815 816 Args: 817 ctl_id (str): controller id 818 binary (str): storcli binary or full path to the binary 819 """ 820 self._ctl_id: int = ctl_id 821 self._encl_id: int = encl_id 822 self._binary: str = binary 823 self._storcli: StorCLI = StorCLI(binary)
Constructor - create StorCLI Enclosures object
Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary
877 def get_drive(self, drive_id: int) -> Optional[Drive]: 878 """Get drive object by id 879 880 Args: 881 drive_id (str): drive id 882 883 Returns: 884 (None): no drive with id 885 (:obj:Drive): drive object 886 """ 887 if drive_id in self._drive_ids: 888 return Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary) 889 else: 890 return None
Get drive object by id
Args: drive_id (str): drive id
Returns: (None): no drive with id (:obj:Drive): drive object
895 def get_drive_range_ids(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None) -> List[int]: 896 """Get drive range list in the current enclosure 897 898 Args: 899 drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer 900 drive_id_end (Optional[int]): end of the range 901 """ 902 903 if drive_id_end: 904 # check that drive_id_begin is integer, if not raise exception 905 if not isinstance(drive_id_begin, int): 906 raise ValueError('drive_id_begin must be an integer') 907 908 # otherwise convert to string 909 drive_id_begin = '{0}-{1}'.format(drive_id_begin, drive_id_end) 910 911 # if drive_id_begin is an integer, convert to string 912 if isinstance(drive_id_begin, int): 913 drive_id_begin = str(drive_id_begin) 914 915 # get the list of drives 916 drive_ids = [] 917 for drive_id in drive_id_begin.split(','): 918 if '-' in drive_id: 919 range_begin = drive_id.split('-')[0] 920 range_end = drive_id.split('-')[1] 921 drive_ids.extend( 922 range(int(range_begin), int(range_end) + 1)) 923 else: 924 drive_ids.append(int(drive_id)) 925 926 return drive_ids
Get drive range list in the current enclosure
Args: drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer drive_id_end (Optional[int]): end of the range
928 def get_drive_range(self, drive_id_begin: Union[int, str], drive_id_end: Optional[int] = None): 929 """Get drive range in the current enclosure 930 931 Args: 932 drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer 933 drive_id_end (Optional[int]): end of the range 934 """ 935 drive_ids = self.get_drive_range_ids(drive_id_begin, drive_id_end) 936 937 for drive_id in drive_ids: 938 yield Drive(ctl_id=self._ctl_id, encl_id=self._encl_id, slot_id=drive_id, binary=self._binary)
Get drive range in the current enclosure
Args: drive_id_begin (Union[int,str]): A range in format '1-10' or '1-10,20-30' or just an integer drive_id_end (Optional[int]): end of the range
118class VirtualDrive(object): 119 """StorCLI VirtualDrive 120 121 Instance of this class represents virtual drive (RAID) in StorCLI hierarchy 122 123 Args: 124 ctl_id (str): controller id 125 vd_id (str): virtual drive id 126 binary (str): storcli binary or full path to the binary 127 128 Properties: 129 id (str): virtual drive id 130 facts (dict): raw virtual drive facts 131 metrics (dict): virtual drive metrics 132 raid (str): vitual drive raid level 133 size (str): virtual drive size 134 state (str): virtual drive state 135 strip (str): virtual drive strip size 136 os_exposed (bool): virtual drive exposed to the OS 137 os_name (str): virtual drive device path (/dev/...) 138 ctl_id (str): virtual drive controller 139 ctl (:obj:controller.Controller): virtual drive controller 140 drives (list of :obj:drive.Drive): virtual drive drives 141 name (str): virtual drive name (also setter) 142 bootdrive (str): virtual drive bootdrive (also setter) 143 pdcache (str): current disk cache policy on a virtual drive (also setter) 144 wrcache (str): write cache policy on a virtual drive (also setter) 145 rdcache (str): read cache policy on a virtual drive (also setter) 146 iopolicy (str): I/O policy on a virtual drive (also setter) 147 autobgi (str):virtual drive auto background initialization setting (also setter) 148 149 Methods: 150 init_start (dict): starts the initialization process on a virtual drive 151 init_stop (dict): stops an initialization process running on a virtual drive 152 init_running (bool): check if initialization is running on a virtual drive 153 erase_start (dict): securely erases non-SED virtual drive 154 erase_stop (dict): stops an erase process running on a virtual drive 155 erase_running (bool): check if erase is running on a virtual drive 156 erase_progress (str): % progress of erase on a virtual drive 157 delete (dict): delete virtual drive 158 migrate_start (dict): starts the migration process on a virtual drive 159 migrate_stop (dict): stops an migration process running on a virtual drive 160 migrate_running (bool): check if migrate is running on a virtual drive 161 162 TODO: 163 Implement missing methods: 164 * start cc 165 * stop cc 166 * pause cc 167 * resume cc 168 * cc running 169 """ 170 171 def __init__(self, ctl_id, vd_id, binary='storcli64'): 172 """Constructor - create StorCLI VirtualDrive object 173 174 Args: 175 ctl_id (str): controller id 176 vd_id (str): virtual drive id 177 binary (str): storcli binary or full path to the binary 178 """ 179 self._ctl_id = ctl_id 180 self._vd_id = vd_id 181 self._binary = binary 182 self._storcli = StorCLI(binary) 183 self._name = '/c{0}/v{1}'.format(self._ctl_id, self._vd_id) 184 185 self._exist() 186 187 def _run(self, args, **kwargs): 188 args = args[:] 189 args.insert(0, self._name) 190 return self._storcli.run(args, **kwargs) 191 192 def _exist(self): 193 try: 194 self._run(['show']) 195 except exc.StorCliCmdError: 196 raise exc.StorCliMissingError( 197 self.__class__.__name__, self._name) from None 198 199 @staticmethod 200 def _response_properties(out): 201 return common.response_data(out)['Virtual Drives'][0] 202 203 def _response_properties_all(self, out): 204 return common.response_data(out)['VD{0} Properties'.format(self._vd_id)] 205 206 @staticmethod 207 def _resposne_operation_status(out): 208 return common.response_data(out)['VD Operation Status'][0] 209 210 @property 211 def id(self): 212 """(str): virtual drive id 213 """ 214 return self._vd_id 215 216 @property 217 def facts(self): 218 """(dict): raw virtual drive facts 219 """ 220 args = [ 221 'show', 222 'all' 223 ] 224 return common.response_data(self._run(args)) 225 226 @property 227 def metrics(self): 228 """(:obj:VirtualDriveMetrics): virtual drive metrics 229 """ 230 return VirtualDriveMetrics(self) 231 232 @property 233 @common.lower 234 def raid(self): 235 """(str): virtual drive raid level 236 """ 237 args = [ 238 'show' 239 ] 240 241 return self._response_properties(self._run(args))['TYPE'] 242 243 @property 244 def size(self): 245 """(str): virtual drive size 246 """ 247 args = [ 248 'show' 249 ] 250 return self._response_properties(self._run(args))['Size'] 251 252 @property 253 @common.lower 254 def state(self): 255 """(str): virtual drive state (optimal | recovery | offline | degraded | degraded_partially) 256 """ 257 args = [ 258 'show' 259 ] 260 state = self._response_properties(self._run(args))['State'] 261 if state == 'Optl': 262 return 'optimal' 263 elif state == 'Rec': 264 return 'recovery' 265 elif state == 'OfLn': 266 return 'offline' 267 elif state == 'Pdgd': 268 return 'degraded_partially' 269 return 'degraded' 270 271 @property 272 def strip(self): 273 """(str): virtual drive strip size 274 """ 275 args = [ 276 'show', 277 'all' 278 ] 279 280 size = self._response_properties_all(self._run(args))['Strip Size'] 281 return size.split()[0] 282 283 @property 284 def os_exposed(self): 285 """(bool): virtual drive exposed to the OS 286 """ 287 args = [ 288 'show', 289 'all' 290 ] 291 292 exposed = self._response_properties_all(self._run(args))[ 293 'Exposed to OS'] 294 return bool(exposed == 'Yes') 295 296 @property 297 @common.lower 298 def os_name(self): 299 """(str): virtual drive device path (/dev/...) 300 """ 301 args = [ 302 'show', 303 'all' 304 ] 305 return self._response_properties_all(self._run(args))['OS Drive Name'] 306 307 @property 308 def ctl_id(self): 309 """(str): virtual drive controller id 310 """ 311 return self._ctl_id 312 313 @property 314 def ctl(self): 315 """(:obj:controller.Controller): virtual drive controller 316 """ 317 return controller.Controller(ctl_id=self._ctl_id, binary=self._binary) 318 319 @property 320 def drives(self): 321 """(list of :obj:Drive): drives 322 """ 323 args = [ 324 'show', 325 'all' 326 ] 327 328 drives = [] 329 pds = common.response_data(self._run(args))[ 330 'PDs for VD {0}'.format(self._vd_id)] 331 for pd in pds: 332 drive_encl_id, drive_slot_id = pd['EID:Slt'].split(':') 333 drives.append( 334 drive.Drive( 335 ctl_id=self._ctl_id, 336 encl_id=drive_encl_id, 337 slot_id=drive_slot_id, 338 binary=self._binary 339 ) 340 ) 341 return drives 342 343 @property 344 def name(self): 345 """Get/Set virtual drive name 346 347 The name is restricted to 15 characters. 348 349 Returns: 350 (str): raid name 351 """ 352 args = [ 353 'show', 354 ] 355 356 properties = self._response_properties(self._run(args)) 357 return properties['Name'] 358 359 @name.setter 360 def name(self, value): 361 """ 362 """ 363 args = [ 364 'set', 365 'name={0}'.format(value) 366 ] 367 return common.response_setter(self._run(args)) 368 369 @property 370 def bootdrive(self): 371 """Get/Set virtual drive as Boot Drive 372 373 One of the following options can be set (str): 374 on - enable boot virtual drive 375 off - disable boot virtual dirve 376 377 Returns: 378 (str): on / off 379 """ 380 args = [ 381 '/c{0}'.format(self._ctl_id), 382 'show', 383 'bootdrive' 384 ] 385 386 for vd in common.response_property(self._storcli.run(args)): 387 if vd['Value'] == 'VD:{0}'.format(self._vd_id): 388 return 'on' 389 return 'off' 390 391 @bootdrive.setter 392 def bootdrive(self, value): 393 """ 394 """ 395 args = [ 396 'set', 397 'bootdrive={0}'.format(value) 398 ] 399 return common.response_setter(self._run(args)) 400 401 @property 402 def pdcache(self): 403 """Get/Set PD Cache Setting 404 405 One of the following options can be set (str): 406 on - enables PD Caching 407 off - disables PD Caching 408 default - default PD Caching 409 410 Returns: 411 (str): on / off 412 """ 413 args = [ 414 'show', 415 'all' 416 ] 417 418 properties = self._response_properties_all(self._run(args)) 419 if properties['Disk Cache Policy'] == 'Enabled': 420 return 'on' 421 elif 'Default' in properties['Disk Cache Policy']: 422 return 'default' 423 return 'off' 424 425 @pdcache.setter 426 def pdcache(self, value): 427 """ 428 """ 429 args = [ 430 'set', 431 'pdcache={0}'.format(value) 432 ] 433 return common.response_setter(self._run(args)) 434 435 @property 436 def wrcache(self): 437 """Get/Set Write cache setting 438 439 One of the following options can be set (str): 440 wt - write Through 441 wb - write Back 442 awb - write Back even in case of bad BBU also 443 444 Returns: 445 (str): wt / wb / awb 446 """ 447 args = [ 448 'show', 449 ] 450 451 properties = self._response_properties(self._run(args)) 452 if 'AWB' in properties['Cache']: 453 return 'awb' 454 elif 'WB' in properties['Cache']: 455 return 'wb' 456 return 'wt' 457 458 @wrcache.setter 459 def wrcache(self, value): 460 """ 461 """ 462 args = [ 463 'set', 464 'wrcache={0}'.format(value) 465 ] 466 return common.response_setter(self._run(args)) 467 468 @property 469 def rdcache(self): 470 """Get/Set Read cache setting 471 472 One of the following options can be set (str): 473 ra - Read Ahead 474 nora - No Read Ahead 475 476 Returns: 477 (str): ra / nora 478 """ 479 args = [ 480 'show', 481 ] 482 483 properties = self._response_properties(self._run(args)) 484 if properties['Cache'][0:2] == 'NR': 485 return 'nora' 486 return 'ra' 487 488 @rdcache.setter 489 def rdcache(self, value): 490 """ 491 """ 492 args = [ 493 'set', 494 'rdcache={0}'.format(value) 495 ] 496 return common.response_setter(self._run(args)) 497 498 @property 499 def iopolicy(self): 500 """Get/Set iopolicy setting 501 502 One of the following options can be set (str): 503 cached - IOs are cached 504 direct - IOs are not cached 505 506 Returns: 507 (str): cached / direct 508 """ 509 args = [ 510 'show', 511 ] 512 513 properties = self._response_properties(self._run(args)) 514 if properties['Cache'][-1] == 'D': 515 return 'direct' 516 return 'cached' 517 518 @iopolicy.setter 519 @common.lower 520 def iopolicy(self, value): 521 """ 522 """ 523 args = [ 524 'set', 525 'iopolicy={0}'.format(value) 526 ] 527 return common.response_setter(self._run(args)) 528 529 @property 530 @common.lower 531 def autobgi(self): 532 """Get/Set auto background initialization 533 534 One of the following options can be set (str): 535 on - enables autobgi 536 off - disables autobgi 537 538 Returns: 539 (str): on / off 540 """ 541 args = [ 542 'show', 543 'autobgi' 544 ] 545 return self._resposne_operation_status(self._run(args))['AutoBGI'] 546 547 @autobgi.setter 548 def autobgi(self, value): 549 """ 550 """ 551 args = [ 552 'set', 553 'autobgi={0}'.format(value) 554 ] 555 return common.response_setter(self._run(args)) 556 557 def init_start(self, full=False, force=False): 558 """Starts the initialization of a virtual drive 559 560 Args: 561 full (bool, optional): if specified then it is the full init otherwise it is Fast init 562 force (bool, optional): must be set if there was before some user data 563 564 Returns: 565 (dict): resposne cmd data 566 """ 567 args = [ 568 'start', 569 'init' 570 ] 571 572 if full: 573 args.append('full') 574 if force: 575 args.append('force') 576 return common.response_cmd(self._run(args)) 577 578 def init_stop(self): 579 """Stops the initialization of a virtual drive 580 581 A stopped initialization process cannot be resumed. 582 583 Returns: 584 (dict): resposne cmd data 585 """ 586 args = [ 587 'stop', 588 'init' 589 ] 590 return common.response_cmd(self._run(args)) 591 592 @property 593 def init_running(self): 594 """Check if initialization is running on a virtual drive 595 596 Returns: 597 (bool): true / false 598 """ 599 args = [ 600 'show', 601 'init' 602 ] 603 604 status = self._resposne_operation_status(self._run(args))['Status'] 605 return bool(status == 'In progress') 606 607 def erase_start(self, mode='simple'): 608 """Securely erases non-SED drives with specified erase pattern 609 610 Args: 611 mode (str, optional): 612 simple - Single pass, single pattern write 613 normal - Three pass, three pattern write 614 thorough - Nine pass, repeats the normal write 3 times 615 standard - Applicable only for DFF's 616 PatternA|PatternB - an 8-Bit binary pattern to overwrite the data. 617 618 Returns: 619 (dict): resposne cmd data 620 """ 621 args = [ 622 'start', 623 'erase', 624 '{0}'.format(mode) 625 ] 626 return common.response_cmd(self._run(args)) 627 628 def erase_stop(self): 629 """Stops the erase operation of a virtual drive 630 631 Returns: 632 (dict): resposne cmd data 633 """ 634 args = [ 635 'stop', 636 'erase' 637 ] 638 return common.response_cmd(self._run(args)) 639 640 @property 641 def erase_running(self): 642 """Check if erase is running on a virtual drive 643 644 Returns: 645 (bool): true / false 646 """ 647 args = [ 648 'show', 649 'erase' 650 ] 651 652 status = self._resposne_operation_status(self._run(args))['Status'] 653 return bool(status == 'In progress') 654 655 @property 656 def erase_progress(self): 657 """Show virtual drive erase progress in percentage 658 659 Returns: 660 (str): progress in percentage 661 """ 662 663 args = [ 664 'show', 665 'erase' 666 ] 667 668 progress = self._resposne_operation_status(self._run(args))[ 669 'Progress%'] 670 if progress == '-': 671 return "100" 672 return progress 673 674 def delete(self, force=False): 675 """Deletes a particular virtual drive 676 677 Args: 678 force (bool, optional): If you delete a virtual drive with a valid MBR 679 without erasing the data and then create a new 680 virtual drive using the same set of physical drives 681 and the same RAID level as the deleted virtual drive, 682 the old unerased MBR still exists at block0 of the 683 new virtual drive, which makes it a virtual drive with 684 valid user data. Therefore, you must provide the 685 force option to delete this newly created virtual drive. 686 687 Returns: 688 (dict): resposne cmd data 689 """ 690 args = [ 691 'del' 692 ] 693 694 if force: 695 args.append('force') 696 return common.response_cmd(self._run(args)) 697 698 def migrate_start(self, option, drives, raid=None, force=False): 699 """Starts migartion on the virtual drive 700 701 Args: 702 option (str): 703 add - adds the specified drives to the migrated raid 704 remove - removes the specified drives from the migrated raid 705 drives (str): specifies the list drives which needs to be added 706 or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y]) 707 raid - raid level to which migration needs to be done (raid0, raid1, ...) 708 force - if specified, then migration will start even if any drive in the DG is secured 709 710 Returns: 711 (dict): resposne cmd data 712 """ 713 if not raid: 714 raid = self.raid 715 args = [ 716 'start', 717 'migrate', 718 'type={0}'.format(raid), 719 'option={0}'.format(option), 720 'drives={0}'.format(drives) 721 ] 722 if force: 723 args.append('force') 724 return common.response_cmd(self._run(args)) 725 726 @property 727 def migrate_running(self): 728 """Check if migration is running on a virtual drive 729 730 Returns: 731 (bool): true / false 732 """ 733 args = [ 734 'show', 735 'migrate' 736 ] 737 738 status = self._resposne_operation_status(self._run(args))['Status'] 739 return bool(status == 'In progress')
StorCLI VirtualDrive
Instance of this class represents virtual drive (RAID) in StorCLI hierarchy
Args: ctl_id (str): controller id vd_id (str): virtual drive id binary (str): storcli binary or full path to the binary
Properties: id (str): virtual drive id facts (dict): raw virtual drive facts metrics (dict): virtual drive metrics raid (str): vitual drive raid level size (str): virtual drive size state (str): virtual drive state strip (str): virtual drive strip size os_exposed (bool): virtual drive exposed to the OS os_name (str): virtual drive device path (/dev/...) ctl_id (str): virtual drive controller ctl (:obj:controller.Controller): virtual drive controller drives (list of :obj:drive.Drive): virtual drive drives name (str): virtual drive name (also setter) bootdrive (str): virtual drive bootdrive (also setter) pdcache (str): current disk cache policy on a virtual drive (also setter) wrcache (str): write cache policy on a virtual drive (also setter) rdcache (str): read cache policy on a virtual drive (also setter) iopolicy (str): I/O policy on a virtual drive (also setter) autobgi (str):virtual drive auto background initialization setting (also setter)
Methods: init_start (dict): starts the initialization process on a virtual drive init_stop (dict): stops an initialization process running on a virtual drive init_running (bool): check if initialization is running on a virtual drive erase_start (dict): securely erases non-SED virtual drive erase_stop (dict): stops an erase process running on a virtual drive erase_running (bool): check if erase is running on a virtual drive erase_progress (str): % progress of erase on a virtual drive delete (dict): delete virtual drive migrate_start (dict): starts the migration process on a virtual drive migrate_stop (dict): stops an migration process running on a virtual drive migrate_running (bool): check if migrate is running on a virtual drive
TODO: Implement missing methods: * start cc * stop cc * pause cc * resume cc * cc running
171 def __init__(self, ctl_id, vd_id, binary='storcli64'): 172 """Constructor - create StorCLI VirtualDrive object 173 174 Args: 175 ctl_id (str): controller id 176 vd_id (str): virtual drive id 177 binary (str): storcli binary or full path to the binary 178 """ 179 self._ctl_id = ctl_id 180 self._vd_id = vd_id 181 self._binary = binary 182 self._storcli = StorCLI(binary) 183 self._name = '/c{0}/v{1}'.format(self._ctl_id, self._vd_id) 184 185 self._exist()
Constructor - create StorCLI VirtualDrive object
Args: ctl_id (str): controller id vd_id (str): virtual drive id binary (str): storcli binary or full path to the binary
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
557 def init_start(self, full=False, force=False): 558 """Starts the initialization of a virtual drive 559 560 Args: 561 full (bool, optional): if specified then it is the full init otherwise it is Fast init 562 force (bool, optional): must be set if there was before some user data 563 564 Returns: 565 (dict): resposne cmd data 566 """ 567 args = [ 568 'start', 569 'init' 570 ] 571 572 if full: 573 args.append('full') 574 if force: 575 args.append('force') 576 return common.response_cmd(self._run(args))
Starts the initialization of a virtual drive
Args: full (bool, optional): if specified then it is the full init otherwise it is Fast init force (bool, optional): must be set if there was before some user data
Returns: (dict): resposne cmd data
578 def init_stop(self): 579 """Stops the initialization of a virtual drive 580 581 A stopped initialization process cannot be resumed. 582 583 Returns: 584 (dict): resposne cmd data 585 """ 586 args = [ 587 'stop', 588 'init' 589 ] 590 return common.response_cmd(self._run(args))
Stops the initialization of a virtual drive
A stopped initialization process cannot be resumed.
Returns: (dict): resposne cmd data
607 def erase_start(self, mode='simple'): 608 """Securely erases non-SED drives with specified erase pattern 609 610 Args: 611 mode (str, optional): 612 simple - Single pass, single pattern write 613 normal - Three pass, three pattern write 614 thorough - Nine pass, repeats the normal write 3 times 615 standard - Applicable only for DFF's 616 PatternA|PatternB - an 8-Bit binary pattern to overwrite the data. 617 618 Returns: 619 (dict): resposne cmd data 620 """ 621 args = [ 622 'start', 623 'erase', 624 '{0}'.format(mode) 625 ] 626 return common.response_cmd(self._run(args))
Securely erases non-SED drives with specified erase pattern
Args: mode (str, optional): simple - Single pass, single pattern write normal - Three pass, three pattern write thorough - Nine pass, repeats the normal write 3 times standard - Applicable only for DFF's PatternA|PatternB - an 8-Bit binary pattern to overwrite the data.
Returns: (dict): resposne cmd data
628 def erase_stop(self): 629 """Stops the erase operation of a virtual drive 630 631 Returns: 632 (dict): resposne cmd data 633 """ 634 args = [ 635 'stop', 636 'erase' 637 ] 638 return common.response_cmd(self._run(args))
Stops the erase operation of a virtual drive
Returns: (dict): resposne cmd data
Show virtual drive erase progress in percentage
Returns: (str): progress in percentage
674 def delete(self, force=False): 675 """Deletes a particular virtual drive 676 677 Args: 678 force (bool, optional): If you delete a virtual drive with a valid MBR 679 without erasing the data and then create a new 680 virtual drive using the same set of physical drives 681 and the same RAID level as the deleted virtual drive, 682 the old unerased MBR still exists at block0 of the 683 new virtual drive, which makes it a virtual drive with 684 valid user data. Therefore, you must provide the 685 force option to delete this newly created virtual drive. 686 687 Returns: 688 (dict): resposne cmd data 689 """ 690 args = [ 691 'del' 692 ] 693 694 if force: 695 args.append('force') 696 return common.response_cmd(self._run(args))
Deletes a particular virtual drive
Args: force (bool, optional): If you delete a virtual drive with a valid MBR without erasing the data and then create a new virtual drive using the same set of physical drives and the same RAID level as the deleted virtual drive, the old unerased MBR still exists at block0 of the new virtual drive, which makes it a virtual drive with valid user data. Therefore, you must provide the force option to delete this newly created virtual drive.
Returns: (dict): resposne cmd data
698 def migrate_start(self, option, drives, raid=None, force=False): 699 """Starts migartion on the virtual drive 700 701 Args: 702 option (str): 703 add - adds the specified drives to the migrated raid 704 remove - removes the specified drives from the migrated raid 705 drives (str): specifies the list drives which needs to be added 706 or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y]) 707 raid - raid level to which migration needs to be done (raid0, raid1, ...) 708 force - if specified, then migration will start even if any drive in the DG is secured 709 710 Returns: 711 (dict): resposne cmd data 712 """ 713 if not raid: 714 raid = self.raid 715 args = [ 716 'start', 717 'migrate', 718 'type={0}'.format(raid), 719 'option={0}'.format(option), 720 'drives={0}'.format(drives) 721 ] 722 if force: 723 args.append('force') 724 return common.response_cmd(self._run(args))
Starts migartion on the virtual drive
Args: option (str): add - adds the specified drives to the migrated raid remove - removes the specified drives from the migrated raid drives (str): specifies the list drives which needs to be added or removed in storcli format ([e:]s|[e:]s-x|[e:]s-x,y]) raid - raid level to which migration needs to be done (raid0, raid1, ...) force - if specified, then migration will start even if any drive in the DG is secured
Returns: (dict): resposne cmd data
742class VirtualDrives(object): 743 """StorCLI virtual drives 744 745 Instance of this class is iterable with :obj:VirtualDrive as item 746 747 Args: 748 ctl_id (str): controller id 749 binary (str): storcli binary or full path to the binary 750 751 Properties: 752 has_vds (bool): true if there are vds 753 ids (list of str): list of virtual drives id 754 ctl_id (str): virtual drives controller id 755 ctl (:obj:controller.Controller): virtual drives controller 756 757 758 Methods: 759 has_vd (bool): true if there are virtual drives 760 get_vd (:obj:VirtualDrive): get virtual drive object by id 761 get_named_vd (:obj:VirtualDrive): get virtual drive object by name 762 763 """ 764 765 def __init__(self, ctl_id, binary='storcli64'): 766 """Constructor - create StorCLI VirtualDrives object 767 768 Args: 769 ctl_id (str): controller id 770 binary (str): storcli binary or full path to the binary 771 """ 772 self._ctl_id = ctl_id 773 self._binary = binary 774 self._storecli = StorCLI(binary) 775 776 @property 777 def _vd_ids(self): 778 args = [ 779 '/c{0}'.format(self._ctl_id), 780 'show' 781 ] 782 data = common.response_data(self._storecli.run(args)) 783 if 'VD LIST' in data: 784 return [vd['DG/VD'].split('/')[1] for vd in data['VD LIST']] 785 return [] 786 787 @property 788 def _vds(self): 789 for vd_id in self._vd_ids: 790 yield VirtualDrive(ctl_id=self._ctl_id, vd_id=vd_id, binary=self._binary) 791 792 def __iter__(self): 793 return self._vds 794 795 @property 796 def ids(self): 797 """(list of str): list of virtual drives id 798 """ 799 return self._vd_ids 800 801 @property 802 def ctl_id(self): 803 """(str): virtual drives controller id 804 """ 805 return self._ctl_id 806 807 @property 808 def ctl(self): 809 """(:obj:controller.Controller): virtual drives controller 810 """ 811 return controller.Controller(ctl_id=self._ctl_id, binary=self._binary) 812 813 @property 814 def has_vds(self): 815 """(bool): true if there are virtual drives 816 """ 817 if self.ids: 818 return True 819 return False 820 821 def get_vd(self, vd_id): 822 """Get virtual drive object by id 823 824 Args: 825 vd_id (str): virtual drive id 826 827 Returns: 828 (None): no virtual drive with id 829 (:obj:VirtualDrive): virtual drive object 830 """ 831 for vd in self: 832 if vd.id == vd_id: 833 return vd 834 return None 835 836 def get_named_vd(self, vd_name): 837 """Get virtual drive object by name 838 839 Args: 840 vd_name (str): virtual drive name 841 842 Returns: 843 (None): no virtual drive with name 844 (:obj:VirtualDrive): virtual drive object 845 """ 846 for vd in self: 847 if vd.name == vd_name: 848 return vd 849 return None
StorCLI virtual drives
Instance of this class is iterable with :obj:VirtualDrive as item
Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary
Properties: has_vds (bool): true if there are vds ids (list of str): list of virtual drives id ctl_id (str): virtual drives controller id ctl (:obj:controller.Controller): virtual drives controller
Methods: has_vd (bool): true if there are virtual drives get_vd (:obj:VirtualDrive): get virtual drive object by id get_named_vd (:obj:VirtualDrive): get virtual drive object by name
765 def __init__(self, ctl_id, binary='storcli64'): 766 """Constructor - create StorCLI VirtualDrives object 767 768 Args: 769 ctl_id (str): controller id 770 binary (str): storcli binary or full path to the binary 771 """ 772 self._ctl_id = ctl_id 773 self._binary = binary 774 self._storecli = StorCLI(binary)
Constructor - create StorCLI VirtualDrives object
Args: ctl_id (str): controller id binary (str): storcli binary or full path to the binary
821 def get_vd(self, vd_id): 822 """Get virtual drive object by id 823 824 Args: 825 vd_id (str): virtual drive id 826 827 Returns: 828 (None): no virtual drive with id 829 (:obj:VirtualDrive): virtual drive object 830 """ 831 for vd in self: 832 if vd.id == vd_id: 833 return vd 834 return None
Get virtual drive object by id
Args: vd_id (str): virtual drive id
Returns: (None): no virtual drive with id (:obj:VirtualDrive): virtual drive object
836 def get_named_vd(self, vd_name): 837 """Get virtual drive object by name 838 839 Args: 840 vd_name (str): virtual drive name 841 842 Returns: 843 (None): no virtual drive with name 844 (:obj:VirtualDrive): virtual drive object 845 """ 846 for vd in self: 847 if vd.name == vd_name: 848 return vd 849 return None
Get virtual drive object by name
Args: vd_name (str): virtual drive name
Returns: (None): no virtual drive with name (:obj:VirtualDrive): virtual drive object