As discussed on the list, this adds save and restore commands to the root. When saving, the object hierarchy generates a dict of its state via dump() methods, which is saved to a file. The restore command reads in a saved file and reconstitutes the objects based on it. Signed-off-by: Andy Grover <agrover@xxxxxxxxxx> --- rtslib/node.py | 9 +++++ rtslib/root.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++- rtslib/target.py | 51 +++++++++++++++++++++++++++++ rtslib/tcm.py | 39 ++++++++++++++++++++++ 4 files changed, 191 insertions(+), 2 deletions(-) diff --git a/rtslib/node.py b/rtslib/node.py index 142e330..3fc5383 100644 --- a/rtslib/node.py +++ b/rtslib/node.py @@ -231,6 +231,15 @@ class CFSNode(object): + "False if this object instanciation just looked " \ + "up the underlying configFS object.") + def dump(self): + d = {} + attrs = {} + for item in self.list_attributes(writable=True): + attrs[item] = int(self.get_attribute(item)) + if attrs: + d['attributes'] = attrs + return d + def _test(): import doctest doctest.testmod() diff --git a/rtslib/root.py b/rtslib/root.py index d405b5e..033659b 100644 --- a/rtslib/root.py +++ b/rtslib/root.py @@ -22,11 +22,18 @@ import os import glob from node import CFSNode -from target import Target, FabricModule +from target import Target, FabricModule, TPG, MappedLUN, LUN, NetworkPortal, NodeACL from tcm import FileIOBackstore, BlockBackstore from tcm import PSCSIBackstore, RDMCPBackstore from utils import RTSLibError, RTSLibBrokenLink, flatten_nested_list, modprobe +backstores = dict( + fileio=FileIOBackstore, + block=BlockBackstore, + pscsi=PSCSIBackstore, + rd_mcp=RDMCPBackstore, + ) + class RTSRoot(CFSNode): ''' This is an interface to the root of the configFS object tree. @@ -96,7 +103,6 @@ class RTSRoot(CFSNode): backstores.add( RDMCPBackstore(int(regex.group(3)), 'lookup')) return backstores - def _list_storage_objects(self): self._check_self() return set(flatten_nested_list([backstore.storage_objects @@ -133,6 +139,90 @@ class RTSRoot(CFSNode): # RTSRoot public stuff + def dump(self): + d = super(RTSRoot, self).dump() + # backstores:storage_object is *usually* 1:1. In any case, they're an + # implementation detail that the user doesn't need to care about. + # Return an array of storageobject info with the crucial plugin name + # added from backstore, instead of a list of sos for each bs. + d['storage_objects'] = [] + for bs in self.backstores: + for so in bs.storage_objects: + so_dump = so.dump() + so_dump['plugin'] = bs.plugin + d['storage_objects'].append(so_dump) + d['targets'] = [t.dump() for t in self.targets] + return d + + def restore(self, config, clear_existing=False): + + if clear_existing: + for so in self.storage_objects: + so.delete() + for t in self.targets: + t.delete() + + if not clear_existing and (self.backstores or self.targets): + raise RTSLibError("backstores or targets present, not restoring." + + " Set clear_existing=True?") + + def del_if_there(d, items): + for item in items: + if item in d: + del d[item] + + def set_attributes(obj, attr_dict): + for name, value in attr_dict.iteritems(): + try: + obj.set_attribute(name, value) + except RTSLibError: + # Setting some attributes may return an error, before kernel 3.3 + pass + + for index, so in enumerate(config['storage_objects']): + # We need to create a Backstore object for each StorageObject + bs_obj = backstores[so['plugin']](index) + + # Instantiate storageobject + kwargs = so.copy() + del_if_there(kwargs, ('exists', 'attributes', 'plugin')) + so_obj = bs_obj._storage_object_class(bs_obj, **kwargs) + set_attributes(so_obj, so['attributes']) + + for t in config['targets']: + fm = FabricModule(t['fabric']) + + # Instantiate target + t_obj = Target(fm, t.get('wwn')) + + for tpg in t['tpgs']: + tpg_obj = TPG(t_obj) + set_attributes(tpg_obj, tpg['attributes']) + + for lun in tpg['luns']: + bs_name, so_name = lun['storage_object'].split('/')[2:] + match_so = [x for x in self.storage_objects if so_name == x.name] + match_so = [x for x in match_so if bs_name == x.backstore.plugin] + if len(match_so) != 1: + raise RTSLibError("Can't find storage object %s" % + lun['storage_object']) + lun_obj = LUN(tpg_obj, lun.get('index'), storage_object=match_so[0]) + + for p in tpg['portals']: + NetworkPortal(tpg_obj, p['ip_address'], p['port']) + + for acl in tpg['node_acls']: + acl_obj = NodeACL(tpg_obj, acl['node_wwn']) + set_attributes(tpg_obj, tpg['attributes']) + for mlun in acl['mapped_luns']: + mlun_obj = MappedLUN(acl_obj, mlun['index'], + mlun['index'], mlun.get('write_protect')) + + del_if_there(acl, ('attributes', 'mapped_luns', 'node_wwn')) + for name, value in acl.iteritems(): + if value: + setattr(acl_obj, name, value) + backstores = property(_list_backstores, doc="Get the list of Backstore objects.") targets = property(_list_targets, diff --git a/rtslib/target.py b/rtslib/target.py index 6403306..584809e 100644 --- a/rtslib/target.py +++ b/rtslib/target.py @@ -536,6 +536,14 @@ class LUN(CFSNode): mapped_luns = property(_list_mapped_luns, doc="List all MappedLUN objects referencing this LUN.") + def dump(self): + d = super(LUN, self).dump() + d['storage_object'] = "/backstores/%s/%s" % \ + (self.storage_object.backstore.plugin, self.storage_object.name) + d['index'] = self.lun + return d + + class MappedLUN(CFSNode): ''' This is an interface to RTS Target Mapped LUNs. @@ -704,6 +712,13 @@ class MappedLUN(CFSNode): node_wwn = property(_get_node_wwn, doc="Get the wwn of the node for which the TPG LUN is mapped.") + def dump(self): + d = super(MappedLUN, self).dump() + d['write_protect'] = self.write_protect + d['index'] = self.mapped_lun + return d + + class NodeACL(CFSNode): ''' This is an interface to node ACLs in configFS. @@ -887,6 +902,18 @@ class NodeACL(CFSNode): mapped_luns = property(_list_mapped_luns, doc="Get the list of all MappedLUN objects in this NodeACL.") + def dump(self): + d = super(NodeACL, self).dump() + d['chap_userid'] = self.chap_userid + d['chap_password'] = self.chap_password + d['chap_mutual_userid'] = self.chap_mutual_userid + d['chap_mutual_password'] = self.chap_mutual_password + d['tcq_depth'] = int(self.tcq_depth) + d['node_wwn'] = self.node_wwn + d['mapped_luns'] = [lun.dump() for lun in self.mapped_luns] + return d + + class NetworkPortal(CFSNode): ''' This is an interface to NetworkPortals in configFS. A NetworkPortal is @@ -959,6 +986,13 @@ class NetworkPortal(CFSNode): ip_address = property(_get_ip_address, doc="Get the NetworkPortal's IP address as a string.") + def dump(self): + d = super(NetworkPortal, self).dump() + d['port'] = self.port + d['ip_address'] = self.ip_address + return d + + class TPG(CFSNode): ''' This is a an interface to Target Portal Groups in configFS. @@ -1219,6 +1253,15 @@ class TPG(CFSNode): nexus = property(_get_nexus, _set_nexus, doc="Get or set (once) the TPG's Nexus is used.") + def dump(self): + d = super(TPG, self).dump() + d['tag'] = self.tag + d['luns'] = [lun.dump() for lun in self.luns] + d['portals'] = [portal.dump() for portal in self.network_portals] + d['node_acls'] = [acl.dump() for acl in self.node_acls] + return d + + class Target(CFSNode): ''' This is an interface to Targets in configFS. @@ -1299,6 +1342,14 @@ class Target(CFSNode): tpgs = property(_list_tpgs, doc="Get the list of TPG for the Target.") + def dump(self): + d = super(Target, self).dump() + d['wwn'] = self.wwn + d['fabric'] = self.fabric_module.name + d['tpgs'] = [tpg.dump() for tpg in self.tpgs] + return d + + def _test(): testmod() diff --git a/rtslib/tcm.py b/rtslib/tcm.py index 0b48d67..3a19712 100644 --- a/rtslib/tcm.py +++ b/rtslib/tcm.py @@ -117,6 +117,14 @@ class Backstore(CFSNode): name = property(_get_name, doc="Get the backstore name.") + def dump(self): + d = super(Backstore, self).dump() + d['storage_objects'] = [so.dump() for so in self.storage_objects] + d['plugin'] = self.plugin + d['name'] = self.name + return d + + class PSCSIBackstore(Backstore): ''' This is an interface to pscsi backstore plugin objects in configFS. @@ -169,6 +177,11 @@ class PSCSIBackstore(Backstore): doc="Get the legacy mode flag. If True, the Vitualbackstore " + " index must match the StorageObjects real HBAs.") + def dump(self): + d = super(PSCSIBackstore, self).dump() + d['legacy_mode'] = self.legacy_mode + return d + class RDMCPBackstore(Backstore): ''' @@ -443,6 +456,13 @@ class StorageObject(CFSNode): attached_luns = property(_list_attached_luns, doc="Get the list of all LUN objects attached.") + def dump(self): + d = super(StorageObject, self).dump() + d['name'] = self.name + d['wwn'] = self.wwn + return d + + class PSCSIStorageObject(StorageObject): ''' An interface to configFS storage objects for pscsi backstore. @@ -719,6 +739,11 @@ class RDMCPStorageObject(StorageObject): size = property(_get_size, doc="Get the ramdisk size in bytes.") + def dump(self): + d = super(RDMCPStorageObject, self).dump() + d['size'] = self.size + return d + class FileIOStorageObject(StorageObject): ''' @@ -871,6 +896,14 @@ class FileIOStorageObject(StorageObject): size = property(_get_size, doc="Get the current FileIOStorage size in bytes") + def dump(self): + d = super(FileIOStorageObject, self).dump() + d['buffered_mode'] = self.buffered_mode + d['dev'] = self.udev_path + d['size'] = self.size + return d + + class BlockStorageObject(StorageObject): ''' An interface to configFS storage objects for block backstore. @@ -959,6 +992,12 @@ class BlockStorageObject(StorageObject): minor = property(_get_minor, doc="Get the block device minor number") + def dump(self): + d = super(BlockStorageObject, self).dump() + d['dev'] = self.udev_path + return d + + def _test(): import doctest doctest.testmod() -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe target-devel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html