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
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
43
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
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
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
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
92
94 '''
95 Whether or not this Target has a certain feature.
96 '''
97 return self.fabric_module.has_feature(feature)
98
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
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
150
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
206
208 return self._parent_target
209
220
230
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
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
279
290
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
304
305
306
312
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'):
338
345
346 - def lun(self, lun, storage_object=None, alias=None):
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
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
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
450
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
533
546
558
560 return self._parent_tpg
561
564
576
577
578
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
643
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
655
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
699 return self._ip_address
700
703
705 return self._parent_tpg
706
708 try:
709 return bool(int(fread("%s/iser" % self.path)))
710 except IOError:
711 return False
712
721
722
723
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
760
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
769
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
802 return self._node_wwn
803
805 return self._parent_tpg
806
811
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
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
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
843
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
878 '''
879 Whether or not this NodeACL has a certain feature.
880 '''
881 return self.parent_tpg.has_feature(feature)
882
893
894 - def mapped_lun(self, mapped_lun, tpg_lun=None, write_protect=None):
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
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
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
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
986
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
1071
1084
1086 return self._mapped_lun
1087
1089 return self._parent_nodeacl
1090
1098
1103
1112
1116
1117
1118
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
1154
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
1177
1180 from doctest import testmod
1181 testmod()
1182
1183 if __name__ == "__main__":
1184 _test()
1185