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