Package rtslib :: Module utils
[hide private]
[frames] | no frames]

Source Code for Module rtslib.utils

  1  ''' 
  2  Provides various utility functions. 
  3   
  4  This file is part of RTSLib. 
  5  Copyright (c) 2011-2013 by Datera, Inc 
  6   
  7  Licensed under the Apache License, Version 2.0 (the "License"); you may 
  8  not use this file except in compliance with the License. You may obtain 
  9  a copy of the License at 
 10   
 11      http://www.apache.org/licenses/LICENSE-2.0 
 12   
 13  Unless required by applicable law or agreed to in writing, software 
 14  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 15  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 16  License for the specific language governing permissions and limitations 
 17  under the License. 
 18  ''' 
 19   
 20  import re 
 21  import os 
 22  import stat 
 23  import uuid 
 24  import socket 
 25  import subprocess 
 26  from contextlib import contextmanager 
27 28 -class RTSLibError(Exception):
29 ''' 30 Generic rtslib error. 31 ''' 32 pass
33 39
40 -class RTSLibNotInCFS(RTSLibError):
41 ''' 42 The underlying configfs object does not exist. Happens when 43 calling methods of an object that is instantiated but have 44 been deleted from congifs, or when trying to lookup an 45 object that does not exist. 46 ''' 47 pass
48
49 -def fwrite(path, string):
50 ''' 51 This function writes a string to a file, and takes care of 52 opening it and closing it. If the file does not exist, it 53 will be created. 54 55 >>> from rtslib.utils import * 56 >>> fwrite("/tmp/test", "hello") 57 >>> fread("/tmp/test") 58 'hello' 59 60 @param path: The file to write to. 61 @type path: string 62 @param string: The string to write to the file. 63 @type string: string 64 65 ''' 66 with open(path, 'w') as file_fd: 67 file_fd.write(str(string))
68
69 -def fread(path):
70 ''' 71 This function reads the contents of a file. 72 It takes care of opening and closing it. 73 74 >>> from rtslib.utils import * 75 >>> fwrite("/tmp/test", "hello") 76 >>> fread("/tmp/test") 77 'hello' 78 >>> fread("/tmp/notexistingfile") # doctest: +ELLIPSIS 79 Traceback (most recent call last): 80 ... 81 IOError: [Errno 2] No such file or directory: '/tmp/notexistingfile' 82 83 @param path: The path to the file to read from. 84 @type path: string 85 @return: A string containing the file's contents. 86 87 ''' 88 with open(path, 'r') as file_fd: 89 return file_fd.read().strip()
90
91 -def is_dev_in_use(path):
92 ''' 93 This function will check if the device or file referenced by path is 94 already mounted or used as a storage object backend. It works by trying to 95 open the path with O_EXCL flag, which will fail if someone else already 96 did. Note that the file is closed before the function returns, so this 97 does not guaranteed the device will still be available after the check. 98 @param path: path to the file of device to check 99 @type path: string 100 @return: A boolean, True is we cannot get exclusive descriptor on the path, 101 False if we can. 102 ''' 103 path = os.path.realpath(str(path)) 104 try: 105 file_fd = os.open(path, os.O_EXCL|os.O_NDELAY) 106 except OSError: 107 return True 108 else: 109 os.close(file_fd) 110 return False
111
112 -def get_blockdev_size(path):
113 ''' 114 Returns the size in logical blocks of a disk-type block device. 115 ''' 116 name = os.path.basename(os.path.realpath(path)) 117 118 # size is in 512-byte sectors, we want to return number of logical blocks 119 def get_size(path, is_partition=False): 120 sect_size = int(fread("%s/size" % path)) 121 if is_partition: 122 path = os.path.split(path)[0] 123 logical_block_size = int(fread("%s/queue/logical_block_size" % path)) 124 return sect_size / (logical_block_size / 512)
125 126 try: 127 return get_size("/sys/block/%s" % name) 128 except IOError: 129 # Maybe it's a partition? 130 m = re.search(r'^([a-z0-9_-]+)(\d+)$', name) 131 if m: 132 # If disk name ends with a digit, Linux sticks a 'p' between it and 133 # the partition number in the blockdev name. 134 disk = m.groups()[0] 135 if disk[-1] == 'p' and disk[-2].isdigit(): 136 disk = disk[:-1] 137 return get_size("/sys/block/%s/%s" % (disk, m.group()), True) 138 else: 139 raise 140 141 get_block_size = get_blockdev_size
142 143 -def get_blockdev_type(path):
144 ''' 145 This function returns a block device's type. 146 Example: 0 is TYPE_DISK 147 If no match is found, None is returned. 148 149 >>> from rtslib.utils import * 150 >>> get_blockdev_type("/dev/sda") 151 0 152 >>> get_blockdev_type("/dev/sr0") 153 5 154 >>> get_blockdev_type("/dev/scd0") 155 5 156 >>> get_blockdev_type("/dev/nodevicehere") is None 157 True 158 159 @param path: path to the block device 160 @type path: string 161 @return: An int for the block device type, or None if not a block device. 162 ''' 163 dev = os.path.realpath(path) 164 165 # is dev a block device? 166 try: 167 mode = os.stat(dev) 168 except OSError: 169 return None 170 171 if not stat.S_ISBLK(mode[stat.ST_MODE]): 172 return None 173 174 # assume disk if device/type is missing 175 disk_type = 0 176 with ignored(IOError): 177 disk_type = int(fread("/sys/block/%s/device/type" % os.path.basename(dev))) 178 179 return disk_type
180 181 get_block_type = get_blockdev_type
182 183 -def convert_scsi_path_to_hctl(path):
184 ''' 185 This function returns the SCSI ID in H:C:T:L form for the block 186 device being mapped to the udev path specified. 187 If no match is found, None is returned. 188 189 >>> import rtslib.utils as utils 190 >>> utils.convert_scsi_path_to_hctl('/dev/scd0') 191 (2, 0, 0, 0) 192 >>> utils.convert_scsi_path_to_hctl('/dev/sr0') 193 (2, 0, 0, 0) 194 >>> utils.convert_scsi_path_to_hctl('/dev/sda') 195 (3, 0, 0, 0) 196 >>> utils.convert_scsi_path_to_hctl('/dev/sda1') 197 >>> utils.convert_scsi_path_to_hctl('/dev/sdb') 198 (3, 0, 1, 0) 199 >>> utils.convert_scsi_path_to_hctl('/dev/sdc') 200 (3, 0, 2, 0) 201 202 @param path: The udev path to the SCSI block device. 203 @type path: string 204 @return: An (host, controller, target, lun) tuple of integer 205 values representing the SCSI ID of the device, or None if no 206 match is found. 207 ''' 208 devname = os.path.basename(os.path.realpath(path)) 209 try: 210 hctl = os.listdir("/sys/block/%s/device/scsi_device" 211 % devname)[0].split(':') 212 except: 213 return None 214 215 return [int(data) for data in hctl]
216
217 -def convert_scsi_hctl_to_path(host, controller, target, lun):
218 ''' 219 This function returns a udev path pointing to the block device being 220 mapped to the SCSI device that has the provided H:C:T:L. 221 222 >>> import rtslib.utils as utils 223 >>> utils.convert_scsi_hctl_to_path(0,0,0,0) 224 '' 225 >>> utils.convert_scsi_hctl_to_path(2,0,0,0) # doctest: +ELLIPSIS 226 '/dev/s...0' 227 >>> utils.convert_scsi_hctl_to_path(3,0,2,0) 228 '/dev/sdc' 229 230 @param host: The SCSI host id. 231 @type host: int 232 @param controller: The SCSI controller id. 233 @type controller: int 234 @param target: The SCSI target id. 235 @type target: int 236 @param lun: The SCSI Logical Unit Number. 237 @type lun: int 238 @return: A string for the canonical path to the device, or empty string. 239 ''' 240 try: 241 host = int(host) 242 controller = int(controller) 243 target = int(target) 244 lun = int(lun) 245 except ValueError: 246 raise RTSLibError( 247 "The host, controller, target and lun parameter must be integers.") 248 249 for devname in os.listdir("/sys/block"): 250 path = "/dev/%s" % devname 251 hctl = [host, controller, target, lun] 252 if convert_scsi_path_to_hctl(path) == hctl: 253 return os.path.realpath(path) 254 return ''
255
256 -def generate_wwn(wwn_type):
257 ''' 258 Generates a random WWN of the specified type: 259 - unit_serial: T10 WWN Unit Serial. 260 - iqn: iSCSI IQN 261 - naa: SAS NAA address 262 @param wwn_type: The WWN address type. 263 @type wwn_type: str 264 @returns: A string containing the WWN. 265 ''' 266 wwn_type = wwn_type.lower() 267 if wwn_type == 'free': 268 return str(uuid.uuid4()) 269 if wwn_type == 'unit_serial': 270 return str(uuid.uuid4()) 271 elif wwn_type == 'iqn': 272 localname = socket.gethostname().split(".")[0] 273 localarch = os.uname()[4].replace("_", "") 274 prefix = "iqn.2003-01.org.linux-iscsi.%s.%s" % (localname, localarch) 275 prefix = prefix.strip().lower() 276 serial = "sn.%s" % str(uuid.uuid4())[24:] 277 return "%s:%s" % (prefix, serial) 278 elif wwn_type == 'naa': 279 # see http://standards.ieee.org/develop/regauth/tut/fibre.pdf 280 # 5 = IEEE registered 281 # 001405 = OpenIB OUI (they let us use it I guess?) 282 # rest = random 283 return "naa.5001405" + uuid.uuid4().get_hex()[-9:] 284 elif wwn_type == 'eui': 285 return "eui.001405" + uuid.uuid4().get_hex()[-10:] 286 else: 287 raise ValueError("Unknown WWN type: %s." % wwn_type)
288
289 -def colonize(str):
290 ''' 291 helper function to add colons every 2 chars 292 ''' 293 return ":".join(str[i:i+2] for i in range(0, len(str), 2))
294
295 -def _cleanse_wwn(wwn_type, wwn):
296 ''' 297 Some wwns may have alternate text representations. Adjust to our 298 preferred representation. 299 ''' 300 wwn = str(wwn.strip()).lower() 301 302 if wwn_type in ('naa', 'eui', 'ib'): 303 if wwn.startswith("0x"): 304 wwn = wwn[2:] 305 wwn = wwn.replace("-", "") 306 wwn = wwn.replace(":", "") 307 308 if not (wwn.startswith("naa.") or wwn.startswith("eui.") or \ 309 wwn.startswith("ib.")): 310 wwn = wwn_type + "." + wwn 311 312 return wwn
313
314 -def normalize_wwn(wwn_types, wwn):
315 ''' 316 Take a WWN as given by the user and convert it to a standard text 317 representation. If possible_wwns is not None, verify that 318 the given WWN is on that list. 319 320 Returns (normalized_wwn, wwn_type), or exception if invalid wwn. 321 ''' 322 wwn_test = { 323 'free': lambda wwn: True, 324 'iqn': lambda wwn: \ 325 re.match("iqn\.[0-9]{4}-[0-1][0-9]\..*\..*", wwn) \ 326 and not re.search(' ', wwn) \ 327 and not re.search('_', wwn), 328 'naa': lambda wwn: re.match("naa\.[125][0-9a-fA-F]{15}$", wwn), 329 'eui': lambda wwn: re.match("eui\.[0-9a-f]{16}$", wwn), 330 'ib': lambda wwn: re.match("ib\.[0-9a-f]{32}$", wwn), 331 'unit_serial': lambda wwn: \ 332 re.match("[0-9A-Fa-f]{8}(-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$", wwn), 333 } 334 335 for wwn_type in wwn_types: 336 clean_wwn = _cleanse_wwn(wwn_type, wwn) 337 found_type = wwn_test[wwn_type](clean_wwn) 338 if found_type: 339 break 340 else: 341 raise RTSLibError("WWN not valid as: %s" % ", ".join(wwn_types)) 342 343 return (clean_wwn, wwn_type)
344
345 -def list_loaded_kernel_modules():
346 ''' 347 List all currently loaded kernel modules 348 ''' 349 return [line.split(" ")[0] for line in 350 fread("/proc/modules").split('\n') if line]
351
352 -def modprobe(module):
353 ''' 354 Load the specified kernel module if needed. 355 @param module: The name of the kernel module to be loaded. 356 @type module: str 357 ''' 358 if module in list_loaded_kernel_modules(): 359 return 360 361 try: 362 import kmod 363 kmod.Kmod().modprobe(module) 364 except ImportError: 365 process = subprocess.Popen(("modprobe", module), 366 stdout=subprocess.PIPE, 367 stderr=subprocess.PIPE) 368 (stdoutdata, stderrdata) = process.communicate() 369 if process.returncode != 0: 370 raise RTSLibError(stderrdata)
371
372 -def mount_configfs():
373 if not os.path.ismount("/sys/kernel/config"): 374 cmdline = "mount -t configfs none /sys/kernel/config" 375 process = subprocess.Popen(cmdline.split(), 376 stdout=subprocess.PIPE, 377 stderr=subprocess.PIPE) 378 (stdoutdata, stderrdata) = process.communicate() 379 if process.returncode != 0: 380 raise RTSLibError("Cannot mount configfs.")
381
382 -def dict_remove(d, items):
383 for item in items: 384 if item in d: 385 del d[item]
386
387 @contextmanager 388 -def ignored(*exceptions):
389 try: 390 yield 391 except exceptions: 392 pass
393
394 # 395 # These two functions are meant to be used with functools.partial and 396 # properties. 397 # 398 # 'ignore=True' will silently return None if the attribute is not present. 399 # This is good for attributes only present in some kernel versions. 400 # 401 # All curried arguments should be keyword args. 402 # 403 # These should only be used for attributes that follow the convention of 404 # "NULL" having a special sentinel value, such as auth attributes, and 405 # that return a string. 406 # 407 -def _get_auth_attr(self, attribute, ignore=False):
408 self._check_self() 409 path = "%s/%s" % (self.path, attribute) 410 try: 411 value = fread(path) 412 except: 413 if not ignore: 414 raise 415 return None 416 if value == "NULL": 417 return '' 418 else: 419 return value
420
421 # Auth params take the string "NULL" to unset the attribute 422 -def _set_auth_attr(self, value, attribute, ignore=False):
423 self._check_self() 424 path = "%s/%s" % (self.path, attribute) 425 value = value.strip() 426 if value == "NULL": 427 raise ValueError("'NULL' is not a permitted value") 428 if value == '': 429 value = "NULL" 430 try: 431 fwrite(path, "%s" % value) 432 except: 433 if not ignore: 434 raise
435
436 -def set_attributes(obj, attr_dict, err_func):
437 for name, value in attr_dict.iteritems(): 438 try: 439 obj.set_attribute(name, value) 440 except RTSLibError as e: 441 err_func(str(e))
442
443 -def set_parameters(obj, param_dict, err_func):
444 for name, value in param_dict.iteritems(): 445 try: 446 obj.set_parameter(name, value) 447 except RTSLibError as e: 448 err_func(str(e))
449
450 -def _test():
451 '''Run the doctests''' 452 import doctest 453 doctest.testmod()
454 455 if __name__ == "__main__": 456 _test() 457