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

Source Code for Module rtslib.target

   1  ''' 
   2  Implements the RTS generic Target fabric classes. 
   3   
   4  This file is part of RTSLib. 
   5  Copyright (c) 2011-2013 by Datera, Inc. 
   6  Copyright (c) 2011-2013 by Red Hat, Inc. 
   7   
   8  Licensed under the Apache License, Version 2.0 (the "License"); you may 
   9  not use this file except in compliance with the License. You may obtain 
  10  a copy of the License at 
  11   
  12      http://www.apache.org/licenses/LICENSE-2.0 
  13   
  14  Unless required by applicable law or agreed to in writing, software 
  15  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
  16  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
  17  License for the specific language governing permissions and limitations 
  18  under the License. 
  19  ''' 
  20   
  21  import re 
  22  import os 
  23  from glob import iglob as glob 
  24  import uuid 
  25   
  26  from node import CFSNode 
  27  from os.path import isdir 
  28  from utils import RTSLibError, RTSLibBrokenLink 
  29  from utils import fread, fwrite, normalize_wwn, generate_wwn 
  30  from utils import dict_remove, set_attributes, set_parameters, ignored 
  31  from utils import _get_auth_attr, _set_auth_attr 
  32  import tcm 
  33  from functools import partial 
34 35 -class Target(CFSNode):
36 ''' 37 This is an interface to Targets in configFS. 38 A Target is identified by its wwn. 39 To a Target is attached a list of TPG objects. 40 ''' 41 42 # Target private stuff 43
44 - def __repr__(self):
45 return "<Target %s/%s>" % (self.fabric_module.name, self.wwn)
46
47 - def __init__(self, fabric_module, wwn=None, mode='any'):
48 ''' 49 @param fabric_module: The target's fabric module. 50 @type fabric_module: FabricModule 51 @param wwn: The optional Target's wwn. 52 If no wwn is specified, one will be generated. 53 @type wwn: string 54 @param mode:An optionnal string containing the object creation mode: 55 - I{'any'} means the configFS object will be either looked up 56 or created. 57 - I{'lookup'} means the object MUST already exist configFS. 58 - I{'create'} means the object must NOT already exist in configFS. 59 @type mode:string 60 @return: A Target object. 61 ''' 62 63 super(Target, self).__init__() 64 self.fabric_module = fabric_module 65 66 fabric_module._check_self() 67 68 if wwn is not None: 69 # old versions used wrong NAA prefix, fixup 70 if wwn.startswith("naa.6"): 71 wwn = "naa.5" + wwn[5:] 72 self.wwn, self.wwn_type = fabric_module.to_normalized_wwn(wwn) 73 elif not fabric_module.wwns: 74 self.wwn = generate_wwn(fabric_module.wwn_types[0]) 75 self.wwn_type = fabric_module.wwn_types[0] 76 else: 77 raise RTSLibError("Fabric cannot generate WWN but it was not given") 78 79 # Checking is done, convert to format the fabric wants 80 fabric_wwn = fabric_module.to_fabric_wwn(self.wwn) 81 self._path = "%s/%s" % (self.fabric_module.path, fabric_wwn) 82 self._create_in_cfs_ine(mode)
83
84 - def _list_tpgs(self):
85 self._check_self() 86 for tpg_dir in glob("%s/tpgt*" % self.path): 87 tag = os.path.basename(tpg_dir).split('_')[1] 88 tag = int(tag) 89 yield TPG(self, tag, 'lookup')
90 91 # Target public stuff 92
93 - def has_feature(self, feature):
94 ''' 95 Whether or not this Target has a certain feature. 96 ''' 97 return self.fabric_module.has_feature(feature)
98
99 - def delete(self):
100 ''' 101 Recursively deletes a Target object. 102 This will delete all attached TPG objects and then the Target itself. 103 ''' 104 self._check_self() 105 for tpg in self.tpgs: 106 tpg.delete() 107 super(Target, self).delete()
108 109 tpgs = property(_list_tpgs, doc="Get the list of TPG for the Target.") 110 111 @classmethod
112 - def setup(cls, fm_obj, t, err_func):
113 ''' 114 Set up target objects based upon t dict, from saved config. 115 Guard against missing or bad dict items, but keep going. 116 Call 'err_func' for each error. 117 ''' 118 119 if 'wwn' not in t: 120 err_func("'wwn' not defined for Target") 121 return 122 123 try: 124 t_obj = Target(fm_obj, t['wwn']) 125 except RTSLibError as e: 126 err_func("Could not create Target object: %s" % e) 127 return 128 129 for tpg in t.get('tpgs', []): 130 TPG.setup(t_obj, tpg, err_func)
131
132 - def dump(self):
133 d = super(Target, self).dump() 134 d['wwn'] = self.wwn 135 d['fabric'] = self.fabric_module.name 136 d['tpgs'] = [tpg.dump() for tpg in self.tpgs] 137 return d
138
139 140 -class TPG(CFSNode):
141 ''' 142 This is a an interface to Target Portal Groups in configFS. 143 A TPG is identified by its parent Target object and its TPG Tag. 144 To a TPG object is attached a list of NetworkPortals. Targets without 145 the 'tpgts' feature cannot have more than a single TPG, so attempts 146 to create more will raise an exception. 147 ''' 148 149 # TPG private stuff 150
151 - def __repr__(self):
152 return "<TPG %d>" % self.tag
153
154 - def __init__(self, parent_target, tag=None, mode='any'):
155 ''' 156 @param parent_target: The parent Target object of the TPG. 157 @type parent_target: Target 158 @param tag: The TPG Tag (TPGT). 159 @type tag: int > 0 160 @param mode:An optionnal string containing the object creation mode: 161 - I{'any'} means the configFS object will be either looked up or 162 created. 163 - I{'lookup'} means the object MUST already exist configFS. 164 - I{'create'} means the object must NOT already exist in configFS. 165 @type mode:string 166 @return: A TPG object. 167 ''' 168 169 super(TPG, self).__init__() 170 171 if tag is None: 172 tags = [tpg.tag for tpg in parent_target.tpgs] 173 for index in xrange(1048576): 174 if index not in tags and index > 0: 175 tag = index 176 break 177 if tag is None: 178 raise RTSLibError("Cannot find an available TPG Tag.") 179 else: 180 tag = int(tag) 181 if not tag > 0: 182 raise RTSLibError("The TPG Tag must be >0.") 183 self._tag = tag 184 185 if isinstance(parent_target, Target): 186 self._parent_target = parent_target 187 else: 188 raise RTSLibError("Invalid parent Target.") 189 190 self._path = "%s/tpgt_%d" % (self.parent_target.path, self.tag) 191 192 target_path = self.parent_target.path 193 if not self.has_feature('tpgts') and not os.path.isdir(self._path): 194 for filename in os.listdir(target_path): 195 if filename.startswith("tpgt_") \ 196 and os.path.isdir("%s/%s" % (target_path, filename)) \ 197 and filename != "tpgt_%d" % self.tag: 198 raise RTSLibError("Target cannot have multiple TPGs.") 199 200 self._create_in_cfs_ine(mode) 201 if self.has_feature('nexus') and not self._get_nexus(): 202 self._set_nexus()
203
204 - def _get_tag(self):
205 return self._tag
206
207 - def _get_parent_target(self):
208 return self._parent_target
209
210 - def _list_network_portals(self):
211 self._check_self() 212 if not self.has_feature('nps'): 213 return 214 215 for network_portal_dir in os.listdir("%s/np" % self.path): 216 (ip_address, port) = \ 217 os.path.basename(network_portal_dir).rsplit(":", 1) 218 port = int(port) 219 yield NetworkPortal(self, ip_address, port, 'lookup')
220
221 - def _get_enable(self):
222 self._check_self() 223 path = "%s/enable" % self.path 224 # If the TPG does not have the enable attribute, then it is always 225 # enabled. 226 if os.path.isfile(path): 227 return bool(int(fread(path))) 228 else: 229 return True
230
231 - def _set_enable(self, boolean):
232 ''' 233 Enables or disables the TPG. If the TPG doesn't support the enable 234 attribute, do nothing. 235 ''' 236 self._check_self() 237 path = "%s/enable" % self.path 238 if os.path.isfile(path) and (boolean != self._get_enable()): 239 try: 240 fwrite(path, str(int(boolean))) 241 except IOError as e: 242 raise RTSLibError("Cannot change enable state: %s" % e)
243
244 - def _get_nexus(self):
245 ''' 246 Gets the nexus initiator WWN, or None if the TPG does not have one. 247 ''' 248 self._check_self() 249 if self.has_feature('nexus'): 250 try: 251 nexus_wwn = fread("%s/nexus" % self.path) 252 except IOError: 253 nexus_wwn = '' 254 return nexus_wwn 255 else: 256 return None
257
258 - def _set_nexus(self, nexus_wwn=None):
259 ''' 260 Sets the nexus initiator WWN. Raises an exception if the nexus is 261 already set or if the TPG does not use a nexus. 262 ''' 263 self._check_self() 264 265 if not self.has_feature('nexus'): 266 raise RTSLibError("The TPG does not use a nexus.") 267 if self._get_nexus(): 268 raise RTSLibError("The TPG's nexus initiator WWN is already set.") 269 270 fm = self.parent_target.fabric_module 271 272 if nexus_wwn: 273 nexus_wwn = fm.to_normalized_wwn(nexus_wwn)[0] 274 else: 275 # Nexus wwn type should match parent target 276 nexus_wwn = generate_wwn(self.parent_target.wwn_type) 277 278 fwrite("%s/nexus" % self.path, fm.to_fabric_wwn(nexus_wwn))
279
280 - def _list_node_acls(self):
281 self._check_self() 282 if not self.has_feature('acls'): 283 return 284 285 node_acl_dirs = [os.path.basename(path) 286 for path in os.listdir("%s/acls" % self.path)] 287 for node_acl_dir in node_acl_dirs: 288 fm = self.parent_target.fabric_module 289 yield NodeACL(self, fm.from_fabric_wwn(node_acl_dir), 'lookup')
290
291 - def _list_luns(self):
292 self._check_self() 293 lun_dirs = [os.path.basename(path) 294 for path in os.listdir("%s/lun" % self.path)] 295 for lun_dir in lun_dirs: 296 lun = lun_dir.split('_')[1] 297 lun = int(lun) 298 yield LUN(self, lun)
299
300 - def _control(self, command):
301 self._check_self() 302 path = "%s/control" % self.path 303 fwrite(path, "%s\n" % str(command))
304 305 # TPG public stuff 306
307 - def has_feature(self, feature):
308 ''' 309 Whether or not this TPG has a certain feature. 310 ''' 311 return self.parent_target.has_feature(feature)
312
313 - def delete(self):
314 ''' 315 Recursively deletes a TPG object. 316 This will delete all attached LUN, NetworkPortal and Node ACL objects 317 and then the TPG itself. Before starting the actual deletion process, 318 all sessions will be disconnected. 319 ''' 320 self._check_self() 321 322 self.enable = False 323 324 for acl in self.node_acls: 325 acl.delete() 326 for lun in self.luns: 327 lun.delete() 328 for portal in self.network_portals: 329 portal.delete() 330 super(TPG, self).delete()
331
332 - def node_acl(self, node_wwn, mode='any'):
333 ''' 334 Same as NodeACL() but without specifying the parent_tpg. 335 ''' 336 self._check_self() 337 return NodeACL(self, node_wwn=node_wwn, mode=mode)
338
339 - def network_portal(self, ip_address, port, mode='any'):
340 ''' 341 Same as NetworkPortal() but without specifying the parent_tpg. 342 ''' 343 self._check_self() 344 return NetworkPortal(self, ip_address=ip_address, port=port, mode=mode)
345
346 - def lun(self, lun, storage_object=None, alias=None):
347 ''' 348 Same as LUN() but without specifying the parent_tpg. 349 ''' 350 self._check_self() 351 return LUN(self, lun=lun, storage_object=storage_object, alias=alias)
352 353 tag = property(_get_tag, 354 doc="Get the TPG Tag as an int.") 355 parent_target = property(_get_parent_target, 356 doc="Get the parent Target object to which the " \ 357 + "TPG is attached.") 358 enable = property(_get_enable, _set_enable, 359 doc="Get or set a boolean value representing the " \ 360 + "enable status of the TPG. " \ 361 + "True means the TPG is enabled, False means it is " \ 362 + "disabled.") 363 network_portals = property(_list_network_portals, 364 doc="Get the list of NetworkPortal objects currently attached " \ 365 + "to the TPG.") 366 node_acls = property(_list_node_acls, 367 doc="Get the list of NodeACL objects currently " \ 368 + "attached to the TPG.") 369 luns = property(_list_luns, 370 doc="Get the list of LUN objects currently attached " \ 371 + "to the TPG.") 372 373 nexus = property(_get_nexus, _set_nexus, 374 doc="Get or set (once) the TPG's Nexus is used.") 375 376 chap_userid = property(partial(_get_auth_attr, attribute='auth/userid', ignore=True), 377 partial(_set_auth_attr, attribute='auth/userid', ignore=True), 378 doc="Set or get the initiator CHAP auth userid.") 379 chap_password = property(partial(_get_auth_attr, attribute='auth/password', ignore=True), 380 partial(_set_auth_attr, attribute='auth/password', ignore=True), 381 doc="Set or get the initiator CHAP auth password.") 382 chap_mutual_userid = property(partial(_get_auth_attr, attribute='auth/userid_mutual', ignore=True), 383 partial(_set_auth_attr, attribute='auth/userid_mutual', ignore=True), 384 doc="Set or get the initiator CHAP auth userid.") 385 chap_mutual_password = property(partial(_get_auth_attr, attribute='auth/password_mutual', ignore=True), 386 partial(_set_auth_attr, attribute='auth/password_mutual', ignore=True), 387 doc="Set or get the initiator CHAP auth password.") 388
389 - def _get_authenticate_target(self):
390 self._check_self() 391 path = "%s/auth/authenticate_target" % self.path 392 try: 393 return bool(int(fread(path))) 394 except: 395 return None
396 397 authenticate_target = property(_get_authenticate_target, 398 doc="Get the boolean authenticate target flag.") 399 400 @classmethod
401 - def setup(cls, t_obj, tpg, err_func):
402 tpg_obj = cls(t_obj, tag=tpg.get("tag", None)) 403 set_attributes(tpg_obj, tpg.get('attributes', {}), err_func) 404 set_parameters(tpg_obj, tpg.get('parameters', {}), err_func) 405 406 for lun in tpg.get('luns', []): 407 LUN.setup(tpg_obj, lun, err_func) 408 409 for p in tpg.get('portals', []): 410 NetworkPortal.setup(tpg_obj, p, err_func) 411 412 for acl in tpg.get('node_acls', []): 413 NodeACL.setup(tpg_obj, acl, err_func) 414 415 tpg_obj.enable = tpg.get('enable', True) 416 dict_remove(tpg, ('luns', 'portals', 'node_acls', 'tag', 417 'attributes', 'parameters', 'enable')) 418 for name, value in tpg.iteritems(): 419 if value: 420 try: 421 setattr(tpg_obj, name, value) 422 except: 423 err_func("Could not set tpg %s attribute '%s'" % 424 (tpg_obj.tag, name))
425
426 - def dump(self):
427 d = super(TPG, self).dump() 428 d['tag'] = self.tag 429 d['enable'] = self.enable 430 d['luns'] = [lun.dump() for lun in self.luns] 431 d['portals'] = [portal.dump() for portal in self.network_portals] 432 d['node_acls'] = [acl.dump() for acl in self.node_acls] 433 if self.has_feature("auth"): 434 for attr in ("userid", "password", "mutual_userid", "mutual_password"): 435 val = getattr(self, "chap_" + attr, None) 436 if val: 437 d["chap_" + attr] = val 438 return d
439
440 441 -class LUN(CFSNode):
442 ''' 443 This is an interface to RTS Target LUNs in configFS. 444 A LUN is identified by its parent TPG and LUN index. 445 ''' 446 447 MAX_LUN = 16383 448 449 # LUN private stuff 450
451 - def __repr__(self):
452 return "<LUN %d (%s/%s)>" % (self.lun, self.storage_object.plugin, 453 self.storage_object.name)
454
455 - def __init__(self, parent_tpg, lun=None, storage_object=None, alias=None):
456 ''' 457 A LUN object can be instanciated in two ways: 458 - B{Creation mode}: If I{storage_object} is specified, the 459 underlying configFS object will be created with that parameter. 460 No LUN with the same I{lun} index can pre-exist in the parent TPG 461 in that mode, or instanciation will fail. 462 - B{Lookup mode}: If I{storage_object} is not set, then the LUN 463 will be bound to the existing configFS LUN object of the parent 464 TPG having the specified I{lun} index. The underlying configFS 465 object must already exist in that mode. 466 467 @param parent_tpg: The parent TPG object. 468 @type parent_tpg: TPG 469 @param lun: The LUN index. 470 @type lun: 0-255 471 @param storage_object: The storage object to be exported as a LUN. 472 @type storage_object: StorageObject subclass 473 @param alias: An optional parameter to manually specify the LUN alias. 474 You probably do not need this. 475 @type alias: string 476 @return: A LUN object. 477 ''' 478 super(LUN, self).__init__() 479 480 if isinstance(parent_tpg, TPG): 481 self._parent_tpg = parent_tpg 482 else: 483 raise RTSLibError("Invalid parent TPG.") 484 485 if lun is None: 486 luns = [l.lun for l in self.parent_tpg.luns] 487 for index in xrange(self.MAX_LUN+1): 488 if index not in luns: 489 lun = index 490 break 491 if lun is None: 492 raise RTSLibError("All LUNs 0-%d in use" % self.MAX_LUN) 493 else: 494 lun = int(lun) 495 if lun < 0 or lun > self.MAX_LUN: 496 raise RTSLibError("LUN must be 0 to %d" % self.MAX_LUN) 497 498 self._lun = lun 499 500 self._path = "%s/lun/lun_%d" % (self.parent_tpg.path, self.lun) 501 502 if storage_object is None and alias is not None: 503 raise RTSLibError("The alias parameter has no meaning " \ 504 + "without the storage_object parameter.") 505 506 if storage_object is not None: 507 self._create_in_cfs_ine('create') 508 try: 509 self._configure(storage_object, alias) 510 except: 511 self.delete() 512 raise 513 else: 514 self._create_in_cfs_ine('lookup')
515
516 - def _configure(self, storage_object, alias):
517 self._check_self() 518 if alias is None: 519 alias = str(uuid.uuid4())[-10:] 520 else: 521 alias = str(alias).strip() 522 if '/' in alias: 523 raise RTSLibError("Invalid alias: %s", alias) 524 525 destination = "%s/%s" % (self.path, alias) 526 527 if storage_object.exists: 528 source = storage_object.path 529 else: 530 raise RTSLibError("storage_object does not exist in configFS.") 531 532 os.symlink(source, destination)
533
534 - def _get_alias(self):
535 self._check_self() 536 alias = None 537 for path in os.listdir(self.path): 538 if os.path.islink("%s/%s" % (self.path, path)): 539 alias = os.path.basename(path) 540 break 541 if alias is None: 542 raise RTSLibBrokenLink("Broken LUN in configFS, no " \ 543 + "storage object attached.") 544 else: 545 return alias
546
547 - def _get_storage_object(self):
548 self._check_self() 549 alias_path = None 550 for path in os.listdir(self.path): 551 if os.path.islink("%s/%s" % (self.path, path)): 552 alias_path = os.path.realpath("%s/%s" % (self.path, path)) 553 break 554 if alias_path is None: 555 raise RTSLibBrokenLink("Broken LUN in configFS, no " 556 + "storage object attached.") 557 return tcm.StorageObject.so_from_path(alias_path)
558
559 - def _get_parent_tpg(self):
560 return self._parent_tpg
561
562 - def _get_lun(self):
563 return self._lun
564
565 - def _list_mapped_luns(self):
566 self._check_self() 567 568 tpg = self.parent_tpg 569 if not tpg.has_feature('acls'): 570 return 571 572 for na in tpg.node_acls: 573 for mlun in na.mapped_luns: 574 if os.path.realpath("%s/%s" % (mlun.path, mlun._get_alias())) == self.path: 575 yield mlun
576 577 # LUN public stuff 578
579 - def delete(self):
580 ''' 581 If the underlying configFS object does not exist, this method does 582 nothing. If the underlying configFS object exists, this method attempts 583 to delete it along with all MappedLUN objects referencing that LUN. 584 ''' 585 self._check_self() 586 587 for mlun in self.mapped_luns: 588 mlun.delete() 589 590 try: 591 link = self.alias 592 except RTSLibBrokenLink: 593 pass 594 else: 595 if os.path.islink("%s/%s" % (self.path, link)): 596 os.unlink("%s/%s" % (self.path, link)) 597 598 super(LUN, self).delete()
599 600 parent_tpg = property(_get_parent_tpg, 601 doc="Get the parent TPG object.") 602 lun = property(_get_lun, 603 doc="Get the LUN index as an int.") 604 storage_object = property(_get_storage_object, 605 doc="Get the storage object attached to the LUN.") 606 alias = property(_get_alias, 607 doc="Get the LUN alias.") 608 mapped_luns = property(_list_mapped_luns, 609 doc="List all MappedLUN objects referencing this LUN.") 610 611 @classmethod
612 - def setup(cls, tpg_obj, lun, err_func):
613 if 'index' not in lun: 614 err_func("'index' missing from a LUN in TPG %d" % tpg_obj.tag) 615 return 616 617 try: 618 bs_name, so_name = lun['storage_object'].split('/')[2:] 619 except: 620 err_func("Malformed storage object field for LUN %d" % lun['index']) 621 return 622 623 for so in tcm.StorageObject.all(): 624 if so_name == so.name and bs_name == so.plugin: 625 match_so = so 626 break 627 else: 628 err_func("Could not find matching StorageObject for LUN %d" % lun['index']) 629 return 630 631 try: 632 cls(tpg_obj, lun['index'], storage_object=match_so) 633 except (RTSLibError, KeyError): 634 err_func("Creating TPG %d LUN index %d failed" % 635 (tpg_obj.tag, lun['index']))
636
637 - def dump(self):
638 d = super(LUN, self).dump() 639 d['storage_object'] = "/backstores/%s/%s" % \ 640 (self.storage_object.plugin, self.storage_object.name) 641 d['index'] = self.lun 642 return d
643
644 645 -class NetworkPortal(CFSNode):
646 ''' 647 This is an interface to NetworkPortals in configFS. A NetworkPortal is 648 identified by its IP and port, but here we also require the parent TPG, so 649 instance objects represent both the NetworkPortal and its association to a 650 TPG. This is necessary to get path information in order to create the 651 portal in the proper configFS hierarchy. 652 ''' 653 654 # NetworkPortal private stuff 655
656 - def __repr__(self):
657 return "<NetworkPortal %s port %s>" % (self.ip_address, self.port)
658
659 - def __init__(self, parent_tpg, ip_address, port=3260, mode='any'):
660 ''' 661 @param parent_tpg: The parent TPG object. 662 @type parent_tpg: TPG 663 @param ip_address: The ipv4/v6 IP address of the NetworkPortal. ipv6 664 addresses should be surrounded by '[]'. 665 @type ip_address: string 666 @param port: The optional (defaults to 3260) NetworkPortal TCP/IP port. 667 @type port: int 668 @param mode: An optionnal string containing the object creation mode: 669 - I{'any'} means the configFS object will be either looked up or 670 created. 671 - I{'lookup'} means the object MUST already exist configFS. 672 - I{'create'} means the object must NOT already exist in configFS. 673 @type mode:string 674 @return: A NetworkPortal object. 675 ''' 676 super(NetworkPortal, self).__init__() 677 678 self._ip_address = str(ip_address) 679 680 try: 681 self._port = int(port) 682 except ValueError: 683 raise RTSLibError("Invalid port.") 684 685 if isinstance(parent_tpg, TPG): 686 self._parent_tpg = parent_tpg 687 else: 688 raise RTSLibError("Invalid parent TPG.") 689 690 self._path = "%s/np/%s:%d" \ 691 % (self.parent_tpg.path, self.ip_address, self.port) 692 693 try: 694 self._create_in_cfs_ine(mode) 695 except OSError as msg: 696 raise RTSLibError(msg[1])
697
698 - def _get_ip_address(self):
699 return self._ip_address
700
701 - def _get_port(self):
702 return self._port
703
704 - def _get_parent_tpg(self):
705 return self._parent_tpg
706
707 - def _get_iser(self):
708 try: 709 return bool(int(fread("%s/iser" % self.path))) 710 except IOError: 711 return False
712
713 - def _set_iser(self, boolean):
714 path = "%s/iser" % self.path 715 try: 716 fwrite(path, str(int(boolean))) 717 except IOError: 718 # b/w compat: don't complain if iser entry is missing 719 if os.path.isfile(path): 720 raise RTSLibError("Cannot change iser")
721 722 # NetworkPortal public stuff 723
724 - def delete(self):
725 self.iser = False 726 super(NetworkPortal, self).delete()
727 728 parent_tpg = property(_get_parent_tpg, 729 doc="Get the parent TPG object.") 730 port = property(_get_port, 731 doc="Get the NetworkPortal's TCP port as an int.") 732 ip_address = property(_get_ip_address, 733 doc="Get the NetworkPortal's IP address as a string.") 734 iser = property(_get_iser, _set_iser, 735 doc="Get or set a boolean value representing if this " \ 736 + "NetworkPortal supports iSER.") 737 738 @classmethod
739 - def setup(cls, tpg_obj, p, err_func):
740 if 'ip_address' not in p: 741 err_func("'ip_address' field missing from a portal in TPG %d" % tpg_obj.tag) 742 return 743 if 'port' not in p: 744 err_func("'port' field missing from a portal in TPG %d" % tpg_obj.tag) 745 return 746 747 try: 748 np = cls(tpg_obj, p['ip_address'], p['port']) 749 np.iser = p.get('iser', False) 750 except (RTSLibError, KeyError) as e: 751 err_func("Creating NetworkPortal object %s:%s failed: %s" % 752 (p['ip_address'], p['port'], e))
753
754 - def dump(self):
755 d = super(NetworkPortal, self).dump() 756 d['port'] = self.port 757 d['ip_address'] = self.ip_address 758 d['iser'] = self.iser 759 return d
760
761 762 -class NodeACL(CFSNode):
763 ''' 764 This is an interface to node ACLs in configFS. 765 A NodeACL is identified by the initiator node wwn and parent TPG. 766 ''' 767 768 # NodeACL private stuff 769
770 - def __repr__(self):
771 return "<NodeACL %s>" % self.node_wwn
772
773 - def __init__(self, parent_tpg, node_wwn, mode='any'):
774 ''' 775 @param parent_tpg: The parent TPG object. 776 @type parent_tpg: TPG 777 @param node_wwn: The wwn of the initiator node for which the ACL is 778 created. 779 @type node_wwn: string 780 @param mode:An optionnal string containing the object creation mode: 781 - I{'any'} means the configFS object will be either looked up or 782 created. 783 - I{'lookup'} means the object MUST already exist configFS. 784 - I{'create'} means the object must NOT already exist in configFS. 785 @type mode:string 786 @return: A NodeACL object. 787 ''' 788 789 super(NodeACL, self).__init__() 790 791 if isinstance(parent_tpg, TPG): 792 self._parent_tpg = parent_tpg 793 else: 794 raise RTSLibError("Invalid parent TPG.") 795 796 fm = self.parent_tpg.parent_target.fabric_module 797 self._node_wwn, self.wwn_type = normalize_wwn(fm.wwn_types, node_wwn) 798 self._path = "%s/acls/%s" % (self.parent_tpg.path, fm.to_fabric_wwn(self.node_wwn)) 799 self._create_in_cfs_ine(mode)
800
801 - def _get_node_wwn(self):
802 return self._node_wwn
803
804 - def _get_parent_tpg(self):
805 return self._parent_tpg
806
807 - def _get_tcq_depth(self):
808 self._check_self() 809 path = "%s/cmdsn_depth" % self.path 810 return fread(path)
811
812 - def _set_tcq_depth(self, depth):
813 self._check_self() 814 path = "%s/cmdsn_depth" % self.path 815 try: 816 fwrite(path, "%s" % depth) 817 except IOError as msg: 818 msg = msg[1] 819 raise RTSLibError("Cannot set tcq_depth: %s" % str(msg))
820
821 - def _get_tag(self):
822 self._check_self() 823 try: 824 tag = fread("%s/tag" % self.path) 825 if tag: 826 return tag 827 return None 828 except IOError: 829 return None
830
831 - def _set_tag(self, tag_str):
832 with ignored(IOError): 833 if tag_str is None: 834 fwrite("%s/tag" % self.path, 'NULL') 835 else: 836 fwrite("%s/tag" % self.path, tag_str)
837
838 - def _list_mapped_luns(self):
839 self._check_self() 840 for mapped_lun_dir in glob("%s/lun_*" % self.path): 841 mapped_lun = int(os.path.basename(mapped_lun_dir).split("_")[1]) 842 yield MappedLUN(self, mapped_lun)
843
844 - def _get_session(self):
845 try: 846 lines = fread("%s/info" % self.path).splitlines() 847 except IOError: 848 return None 849 850 if lines[0].startswith("No active"): 851 return None 852 853 session = {} 854 855 for line in lines: 856 if line.startswith("InitiatorName:"): 857 session['parent_nodeacl'] = self 858 session['connections'] = [] 859 elif line.startswith("InitiatorAlias:"): 860 session['alias'] = line.split(":")[1].strip() 861 elif line.startswith("LIO Session ID:"): 862 session['id'] = int(line.split(":")[1].split()[0]) 863 session['type'] = line.split("SessionType:")[1].split()[0].strip() 864 elif "TARG_SESS_STATE_" in line: 865 session['state'] = line.split("_STATE_")[1].split()[0] 866 elif "TARG_CONN_STATE_" in line: 867 cid = int(line.split(":")[1].split()[0]) 868 cstate = line.split("_STATE_")[1].split()[0] 869 session['connections'].append(dict(cid=cid, cstate=cstate)) 870 elif "Address" in line: 871 session['connections'][-1]['address'] = line.split()[1] 872 session['connections'][-1]['transport'] = line.split()[2] 873 874 return session
875 876 # NodeACL public stuff
877 - def has_feature(self, feature):
878 ''' 879 Whether or not this NodeACL has a certain feature. 880 ''' 881 return self.parent_tpg.has_feature(feature)
882
883 - def delete(self):
884 ''' 885 Delete the NodeACL, including all MappedLUN objects. 886 If the underlying configFS object does not exist, this method does 887 nothing. 888 ''' 889 self._check_self() 890 for mapped_lun in self.mapped_luns: 891 mapped_lun.delete() 892 super(NodeACL, self).delete()
893
894 - def mapped_lun(self, mapped_lun, tpg_lun=None, write_protect=None):
895 ''' 896 Same as MappedLUN() but without the parent_nodeacl parameter. 897 ''' 898 self._check_self() 899 return MappedLUN(self, mapped_lun=mapped_lun, tpg_lun=tpg_lun, 900 write_protect=write_protect)
901 902 tcq_depth = property(_get_tcq_depth, _set_tcq_depth, 903 doc="Set or get the TCQ depth for the initiator " \ 904 + "sessions matching this NodeACL.") 905 tag = property(_get_tag, _set_tag, 906 doc="Set or get the NodeACL tag. If not supported, return None") 907 parent_tpg = property(_get_parent_tpg, 908 doc="Get the parent TPG object.") 909 node_wwn = property(_get_node_wwn, 910 doc="Get the node wwn.") 911 mapped_luns = property(_list_mapped_luns, 912 doc="Get the list of all MappedLUN objects in this NodeACL.") 913 session = property(_get_session, 914 doc="Gives a snapshot of the current session or C{None}") 915 916 chap_userid = property(partial(_get_auth_attr, attribute='auth/userid'), 917 partial(_set_auth_attr, attribute='auth/userid'), 918 doc="Set or get the initiator CHAP auth userid.") 919 chap_password = property(partial(_get_auth_attr, attribute='auth/password'), 920 partial(_set_auth_attr, attribute='auth/password',), 921 doc="Set or get the initiator CHAP auth password.") 922 chap_mutual_userid = property(partial(_get_auth_attr, attribute='auth/userid_mutual'), 923 partial(_set_auth_attr, attribute='auth/userid_mutual'), 924 doc="Set or get the initiator CHAP auth userid.") 925 chap_mutual_password = property(partial(_get_auth_attr, attribute='auth/password_mutual'), 926 partial(_set_auth_attr, attribute='auth/password_mutual'), 927 doc="Set or get the initiator CHAP auth password.") 928
929 - def _get_authenticate_target(self):
930 self._check_self() 931 path = "%s/auth/authenticate_target" % self.path 932 return bool(int(fread(path)))
933 934 authenticate_target = property(_get_authenticate_target, 935 doc="Get the boolean authenticate target flag.") 936 937 @classmethod
938 - def setup(cls, tpg_obj, acl, err_func):
939 if 'node_wwn' not in acl: 940 err_func("'node_wwn' missing in node_acl") 941 return 942 try: 943 acl_obj = cls(tpg_obj, acl['node_wwn']) 944 except RTSLibError as e: 945 err_func("Error when creating NodeACL for %s: %s" % (acl['node_wwn'], e)) 946 return 947 948 set_attributes(acl_obj, acl.get('attributes', {})) 949 950 for mlun in acl.get('mapped_luns', []): 951 MappedLUN.setup(tpg_obj, acl_obj, mlun, err_func) 952 953 dict_remove(acl, ('attributes', 'mapped_luns', 'node_wwn')) 954 for name, value in acl.iteritems(): 955 if value: 956 try: 957 setattr(acl_obj, name, value) 958 except: 959 err_func("Could not set nodeacl %s attribute '%s'" % 960 (acl['node_wwn'], name))
961
962 - def dump(self):
963 d = super(NodeACL, self).dump() 964 d['node_wwn'] = self.node_wwn 965 d['mapped_luns'] = [lun.dump() for lun in self.mapped_luns] 966 if self.tag: 967 d['tag'] = self.tag 968 if self.has_feature("auth"): 969 for attr in ("userid", "password", "mutual_userid", "mutual_password"): 970 val = getattr(self, "chap_" + attr, None) 971 if val: 972 d["chap_" + attr] = val 973 return d
974
975 976 -class MappedLUN(CFSNode):
977 ''' 978 This is an interface to RTS Target Mapped LUNs. 979 A MappedLUN is a mapping of a TPG LUN to a specific initiator node, and is 980 part of a NodeACL. It allows the initiator to actually access the TPG LUN 981 if ACLs are enabled for the TPG. The initial TPG LUN will then be seen by 982 the initiator node as the MappedLUN. 983 ''' 984 985 # MappedLUN private stuff 986
987 - def __repr__(self):
988 return "<MappedLUN %d -> %d>" % (self.mapped_lun, self.tpg_lun.lun)
989
990 - def __init__(self, parent_nodeacl, mapped_lun, 991 tpg_lun=None, write_protect=None):
992 ''' 993 A MappedLUN object can be instanciated in two ways: 994 - B{Creation mode}: If I{tpg_lun} is specified, the underlying 995 configFS object will be created with that parameter. No MappedLUN 996 with the same I{mapped_lun} index can pre-exist in the parent 997 NodeACL in that mode, or instanciation will fail. 998 - B{Lookup mode}: If I{tpg_lun} is not set, then the MappedLUN will 999 be bound to the existing configFS MappedLUN object of the parent 1000 NodeACL having the specified I{mapped_lun} index. The underlying 1001 configFS object must already exist in that mode. 1002 1003 @param mapped_lun: The mapped LUN index. 1004 @type mapped_lun: int 1005 @param tpg_lun: The TPG LUN index to map, or directly a LUN object that 1006 belong to the same TPG as the 1007 parent NodeACL. 1008 @type tpg_lun: int or LUN 1009 @param write_protect: The write-protect flag value, defaults to False 1010 (write-protection disabled). 1011 @type write_protect: bool 1012 ''' 1013 1014 super(MappedLUN, self).__init__() 1015 1016 if not isinstance(parent_nodeacl, NodeACL): 1017 raise RTSLibError("The parent_nodeacl parameter must be " \ 1018 + "a NodeACL object.") 1019 else: 1020 self._parent_nodeacl = parent_nodeacl 1021 if not parent_nodeacl.exists: 1022 raise RTSLibError("The parent_nodeacl does not exist.") 1023 1024 try: 1025 self._mapped_lun = int(mapped_lun) 1026 except ValueError: 1027 raise RTSLibError("The mapped_lun parameter must be an " \ 1028 + "integer value.") 1029 1030 self._path = "%s/lun_%d" % (self.parent_nodeacl.path, self.mapped_lun) 1031 1032 if tpg_lun is None and write_protect is not None: 1033 raise RTSLibError("The write_protect parameter has no " \ 1034 + "meaning without the tpg_lun parameter.") 1035 1036 if tpg_lun is not None: 1037 self._create_in_cfs_ine('create') 1038 try: 1039 self._configure(tpg_lun, write_protect) 1040 except: 1041 self.delete() 1042 raise 1043 else: 1044 self._create_in_cfs_ine('lookup')
1045
1046 - def _configure(self, tpg_lun, write_protect):
1047 self._check_self() 1048 if isinstance(tpg_lun, LUN): 1049 tpg_lun = tpg_lun.lun 1050 else: 1051 try: 1052 tpg_lun = int(tpg_lun) 1053 except ValueError: 1054 raise RTSLibError("The tpg_lun must be either an " 1055 + "integer or a LUN object.") 1056 # Check that the tpg_lun exists in the TPG 1057 for lun in self.parent_nodeacl.parent_tpg.luns: 1058 if lun.lun == tpg_lun: 1059 tpg_lun = lun 1060 break 1061 if not (isinstance(tpg_lun, LUN) and tpg_lun): 1062 raise RTSLibError("LUN %s does not exist in this TPG." 1063 % str(tpg_lun)) 1064 os.symlink(tpg_lun.path, "%s/%s" 1065 % (self.path, str(uuid.uuid4())[-10:])) 1066 1067 try: 1068 self.write_protect = int(write_protect) > 0 1069 except: 1070 self.write_protect = False
1071
1072 - def _get_alias(self):
1073 self._check_self() 1074 alias = None 1075 for path in os.listdir(self.path): 1076 if os.path.islink("%s/%s" % (self.path, path)): 1077 alias = os.path.basename(path) 1078 break 1079 if alias is None: 1080 raise RTSLibBrokenLink("Broken LUN in configFS, no " \ 1081 + "storage object attached.") 1082 else: 1083 return alias
1084
1085 - def _get_mapped_lun(self):
1086 return self._mapped_lun
1087
1088 - def _get_parent_nodeacl(self):
1089 return self._parent_nodeacl
1090
1091 - def _set_write_protect(self, write_protect):
1092 self._check_self() 1093 path = "%s/write_protect" % self.path 1094 if write_protect: 1095 fwrite(path, "1") 1096 else: 1097 fwrite(path, "0")
1098
1099 - def _get_write_protect(self):
1100 self._check_self() 1101 path = "%s/write_protect" % self.path 1102 return bool(int(fread(path)))
1103
1104 - def _get_tpg_lun(self):
1105 self._check_self() 1106 path = os.path.realpath("%s/%s" % (self.path, self._get_alias())) 1107 for lun in self.parent_nodeacl.parent_tpg.luns: 1108 if lun.path == path: 1109 return lun 1110 1111 raise RTSLibBrokenLink("Broken MappedLUN, no TPG LUN found !")
1112
1113 - def _get_node_wwn(self):
1114 self._check_self() 1115 return self.parent_nodeacl.node_wwn
1116 1117 # MappedLUN public stuff 1118
1119 - def delete(self):
1120 ''' 1121 Delete the MappedLUN. 1122 ''' 1123 self._check_self() 1124 try: 1125 lun_link = "%s/%s" % (self.path, self._get_alias()) 1126 except RTSLibBrokenLink: 1127 pass 1128 else: 1129 if os.path.islink(lun_link): 1130 os.unlink(lun_link) 1131 super(MappedLUN, self).delete()
1132 1133 mapped_lun = property(_get_mapped_lun, 1134 doc="Get the integer MappedLUN mapped_lun index.") 1135 parent_nodeacl = property(_get_parent_nodeacl, 1136 doc="Get the parent NodeACL object.") 1137 write_protect = property(_get_write_protect, _set_write_protect, 1138 doc="Get or set the boolean write protection.") 1139 tpg_lun = property(_get_tpg_lun, 1140 doc="Get the TPG LUN object the MappedLUN is pointing at.") 1141 node_wwn = property(_get_node_wwn, 1142 doc="Get the wwn of the node for which the TPG LUN is mapped.") 1143 1144 @classmethod
1145 - def setup(cls, tpg_obj, acl_obj, mlun, err_func):
1146 if 'tpg_lun' not in mlun: 1147 err_func("'tpg_lun' not in a mapped_lun") 1148 return 1149 if 'index' not in mlun: 1150 err_func("'index' not in a mapped_lun") 1151 return 1152 1153 # Mapped lun needs to correspond with already-created 1154 # TPG lun 1155 for lun in tpg_obj.luns: 1156 if lun.lun == mlun['tpg_lun']: 1157 tpg_lun_obj = lun 1158 break 1159 else: 1160 err_func("Could not find matching TPG LUN %d for MappedLUN %s" % 1161 (mlun['tpg_lun'], mlun['index'])) 1162 return 1163 1164 try: 1165 mlun_obj = cls(acl_obj, mlun['index'], 1166 tpg_lun_obj, mlun.get('write_protect')) 1167 mlun_obj.tag = mlun.get("tag", None) 1168 except (RTSLibError, KeyError): 1169 err_func("Creating MappedLUN object %d failed" % mlun['index'])
1170
1171 - def dump(self):
1172 d = super(MappedLUN, self).dump() 1173 d['write_protect'] = self.write_protect 1174 d['index'] = self.mapped_lun 1175 d['tpg_lun'] = self.tpg_lun.lun 1176 return d
1177
1178 1179 -def _test():
1180 from doctest import testmod 1181 testmod()
1182 1183 if __name__ == "__main__": 1184 _test() 1185