def ui_command_restoreconfig(self, savefile=default_save_file, clear_existing=False): ''' Restores configuration from a file. ''' self.assert_root() savefile = os.path.expanduser(savefile) if not os.path.isfile(savefile): self.shell.log.info("Restore file %s not found" % savefile) return with open(savefile, "r") as f: try: errors = RTSRoot().restore(json.loads(f.read()), clear_existing) except ValueError: self.shell.log.error("Error parsing savefile: %s" % savefile) return if errors: self.shell.log.error( "Configuration restored, %d recoverable errors:" % len(errors)) for error in errors: self.shell.log.error(error) else: self.shell.log.info("Configuration restored from %s" % savefile) self.refresh()
def next_hba_index(self): self.shell.log.debug("%r" % [(backstore.plugin, backstore.index) for backstore in RTSRoot().backstores]) indexes = [ backstore.index for backstore in RTSRoot().backstores if backstore.plugin == self.name ] self.shell.log.debug("Existing %s backstore indexes: %r" % (self.name, indexes)) for index in range(1048576): if index not in indexes: backstore_index = index break if backstore_index is None: raise ExecutionError("Cannot find an available backstore index.") else: self.shell.log.debug("First available %s backstore index is %d." % (self.name, backstore_index)) return backstore_index
def refresh(self): ''' Refreshes the tree of target fabric modules. ''' self._children = set([]) UIBackstores(self) # only show fabrics present in the system for fm in RTSRoot().fabric_modules: if fm.wwns == None or list(fm.wwns) != []: UIFabricModule(fm, self)
def refresh(self): self._children = set([]) for backstore in RTSRoot().backstores: backstore_plugin = backstore.plugin if backstore_plugin == 'pscsi': UIPSCSIBackstoreLegacy(backstore, self) elif backstore_plugin == 'rd_mcp': UIRDMCPBackstoreLegacy(backstore, self) elif backstore_plugin == 'fileio': UIFileIOBackstoreLegacy(backstore, self) elif backstore_plugin == 'iblock': UIIBlockBackstoreLegacy(backstore, self)
def ui_command_clearconfig(self, confirm=None): ''' Removes entire configuration of backstores and targets ''' self.assert_root() confirm = self.ui_eval_param(confirm, 'bool', False) RTSRoot().clear_existing(confirm=confirm) self.shell.log.info("All configuration cleared") self.refresh()
def refresh(self): ''' Refreshes the tree of target fabric modules. ''' self._children = set([]) if self.shell.prefs['legacy_hba_view']: UIBackstoresLegacy(self) else: UIBackstores(self) for fabric_module in RTSRoot().fabric_modules: self.shell.log.debug("Using fabric module %s." % fabric_module.name) UIFabricModule(fabric_module, self)
def dedup_so_name(storage_object): ''' Useful for migration from ui_backstore_legacy to new style with 1:1 hba:so mapping. If name is a duplicate in a backstore, returns name_X where X is the HBA index. ''' names = [ so.name for so in RTSRoot().storage_objects if so.backstore.plugin == storage_object.backstore.plugin ] if names.count(storage_object.name) > 1: return "%s_%d" % (storage_object.name, storage_object.backstore.index) else: return storage_object.name
def ui_command_saveconfig(self, savefile=default_save_file): ''' Saves the current configuration to a file so that it can be restored on next boot. ''' self.assert_root() savefile = os.path.expanduser(savefile) with open(savefile + ".temp", "w+") as f: os.fchmod(f.fileno(), stat.S_IRUSR | stat.S_IWUSR) f.write(json.dumps(RTSRoot().dump(), sort_keys=True, indent=2)) f.write("\n") os.fsync(f.fileno()) # Only save backups if saving to default location if savefile == default_save_file: backup_dir = os.path.dirname(savefile) + "/backup" backup_name = "saveconfig-" + \ datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json" backupfile = backup_dir + "/" + backup_name with ignored(IOError): shutil.move(savefile, backupfile) # Kill excess backups backups = sorted( glob(os.path.dirname(savefile) + "/backup/*.json")) files_to_unlink = list(reversed(backups))[kept_backups:] for f in files_to_unlink: os.unlink(f) self.shell.log.info("Last %d configs saved in %s." % \ (kept_backups, backup_dir)) os.rename(savefile + ".temp", savefile) self.shell.log.info("Configuration saved to %s" % savefile)
def ui_command_sessions(self, action="list", sid=None): ''' Displays a detailed list of all open sessions. PARAMETERS ========== I{action} --------- The I{action} is one of: - B{list} gives a short session list - B{detail} gives a detailed list I{sid} ------ You can specify an I{sid} to only list this one, with or without details. SEE ALSO ======== status ''' indent_step = 4 base_steps = 0 action_list = ("list", "detail") root = RTSRoot() if action not in action_list: raise ExecutionError("action must be one of: %s" % ", ".join(action_list)) if sid is not None: try: int(sid) except ValueError: raise ExecutionError("sid must be a number, '%s' given" % sid) def indent_print(text, steps): console = self.shell.con console.display(console.indent(text, indent_step * steps), no_lf=True) def print_session(session): acl = session['parent_nodeacl'] indent_print("alias: %(alias)s\tsid: %(id)i type: " \ "%(type)s session-state: %(state)s" % session, base_steps) if action == 'detail': if self.as_root: if acl.authenticate_target: auth = " (authenticated)" else: auth = " (NOT AUTHENTICATED)" else: auth = "" indent_print("name: %s%s" % (acl.node_wwn, auth), base_steps + 1) for mlun in acl.mapped_luns: plugin = mlun.tpg_lun.storage_object.backstore.plugin name = mlun.tpg_lun.storage_object.name if mlun.write_protect: mode = "r" else: mode = "rw" indent_print( "mapped-lun: %d backstore: %s/%s mode: %s" % (mlun.mapped_lun, plugin, name, mode), base_steps + 1) for connection in session['connections']: indent_print("address: %(address)s (%(transport)s) cid: " \ "%(cid)i connection-state: %(cstate)s" % \ connection, base_steps + 1) if sid: printed_sessions = [ x for x in root.sessions if x['id'] == int(sid) ] else: printed_sessions = list(root.sessions) if len(printed_sessions): for session in printed_sessions: print_session(session) else: if sid is None: indent_print("(no open sessions)", base_steps) else: raise ExecutionError("no session found with sid %i" % int(sid))
def get_root(): global _rtsroot if _rtsroot is None: _rtsroot = RTSRoot() return _rtsroot
def refresh(self): self._children = set([]) for so in RTSRoot().storage_objects: if so.backstore.plugin == self.name: ui_so = UIStorageObject(so, self) ui_so.name = dedup_so_name(so)
def __init__(self, shell, as_root=False): UINode.__init__(self, '/', shell=shell) self.as_root = as_root self.rtsroot = RTSRoot()
class UIRoot(UINode): ''' The targetcli hierarchy root node. ''' def __init__(self, shell, as_root=False): UINode.__init__(self, '/', shell=shell) self.as_root = as_root self.rtsroot = RTSRoot() def refresh(self): ''' Refreshes the tree of target fabric modules. ''' self._children = set([]) UIBackstores(self) # only show fabrics present in the system for fm in self.rtsroot.fabric_modules: if fm.wwns == None or any(fm.wwns): UIFabricModule(fm, self) def ui_command_saveconfig(self, savefile=default_save_file): ''' Saves the current configuration to a file so that it can be restored on next boot. ''' self.assert_root() savefile = os.path.expanduser(savefile) # Only save backups if saving to default location if savefile == default_save_file: backup_dir = os.path.dirname(savefile) + "/backup" backup_name = "saveconfig-" + \ datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json" backupfile = backup_dir + "/" + backup_name with ignored(IOError): shutil.copy(savefile, backupfile) # Kill excess backups backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) files_to_unlink = list(reversed(backups))[kept_backups:] for f in files_to_unlink: os.unlink(f) self.shell.log.info("Last %d configs saved in %s." % \ (kept_backups, backup_dir)) self.rtsroot.save_to_file(savefile) self.shell.log.info("Configuration saved to %s" % savefile) def ui_command_restoreconfig(self, savefile=default_save_file, clear_existing=False): ''' Restores configuration from a file. ''' self.assert_root() savefile = os.path.expanduser(savefile) if not os.path.isfile(savefile): self.shell.log.info("Restore file %s not found" % savefile) return errors = self.rtsroot.restore_from_file(savefile, clear_existing) self.refresh() if errors: raise ExecutionError("Configuration restored, %d recoverable errors:\n%s" % \ (len(errors), "\n".join(errors))) self.shell.log.info("Configuration restored from %s" % savefile) def ui_command_clearconfig(self, confirm=None): ''' Removes entire configuration of backstores and targets ''' self.assert_root() confirm = self.ui_eval_param(confirm, 'bool', False) self.rtsroot.clear_existing(confirm=confirm) self.shell.log.info("All configuration cleared") self.refresh() def ui_command_version(self): ''' Displays the targetcli and support libraries versions. ''' from targetcli import __version__ as targetcli_version self.shell.log.info("targetcli version %s" % targetcli_version) def ui_command_sessions(self, action="list", sid=None): ''' Displays a detailed list of all open sessions. PARAMETERS ========== I{action} --------- The I{action} is one of: - B{list} gives a short session list - B{detail} gives a detailed list I{sid} ------ You can specify an I{sid} to only list this one, with or without details. SEE ALSO ======== status ''' indent_step = 4 base_steps = 0 action_list = ("list", "detail") if action not in action_list: raise ExecutionError("action must be one of: %s" % ", ".join(action_list)) if sid is not None: try: int(sid) except ValueError: raise ExecutionError("sid must be a number, '%s' given" % sid) def indent_print(text, steps): console = self.shell.con console.display(console.indent(text, indent_step * steps), no_lf=True) def print_session(session): acl = session['parent_nodeacl'] indent_print("alias: %(alias)s\tsid: %(id)i type: " \ "%(type)s session-state: %(state)s" % session, base_steps) if action == 'detail': if self.as_root: if acl.authenticate_target: auth = " (authenticated)" else: auth = " (NOT AUTHENTICATED)" else: auth = "" indent_print("name: %s%s" % (acl.node_wwn, auth), base_steps + 1) for mlun in acl.mapped_luns: plugin = mlun.tpg_lun.storage_object.plugin name = mlun.tpg_lun.storage_object.name if mlun.write_protect: mode = "r" else: mode = "rw" indent_print("mapped-lun: %d backstore: %s/%s mode: %s" % (mlun.mapped_lun, plugin, name, mode), base_steps + 1) for connection in session['connections']: indent_print("address: %(address)s (%(transport)s) cid: " \ "%(cid)i connection-state: %(cstate)s" % \ connection, base_steps + 1) if sid: printed_sessions = [x for x in self.rtsroot.sessions if x['id'] == int(sid)] else: printed_sessions = list(self.rtsroot.sessions) if len(printed_sessions): for session in printed_sessions: print_session(session) else: if sid is None: indent_print("(no open sessions)", base_steps) else: raise ExecutionError("no session found with sid %i" % int(sid))
def ui_command_create(self, backstore_plugin): ''' Creates a new backstore, using the chosen I{backstore_plugin}. More than one backstores using the same I{backstore_plugin} can co-exist. They will be identified by incremental index numbers, starting from 0. AVAILABLE BACKSTORE PLUGINS =========================== B{iblock} --------- This I{backstore_plugin} provides I{SPC-4}, along with I{ALUA} and I{Persistent Reservations} emulation on top of Linux BLOCK devices: B{any block device} that appears in /sys/block. B{pscsi} -------- Provides pass-through for Linux physical SCSI devices. It can be used with any storage object that does B{direct pass-through} of SCSI commands without SCSI emulation. This assumes an underlying SCSI device that appears with lsscsi in /proc/scsi/scsi, such as a SAS hard drive, such as any SCSI device. The Linux kernel code for device SCSI drivers resides in linux/drivers/scsi. SCSI-3 and higher is supported with this subsystem, but only for control CDBs capable by the device firmware. B{fileio} --------- This I{backstore_plugin} provides I{SPC-4}, along with I{ALUA} and I{Persistent Reservations} emulation on top of Linux VFS devices: B{any file on a mounted filesystem}. It may be backed by a file or an underlying real block device. FILEIO is using struct file to serve block I/O with various methods (synchronous or asynchronous) and (buffered or direct). B{rd_mcp} -------- This I{backstore_plugin} uses a ramdisk with a separate mapping using memory copy. Typically used for bandwidth testing. EXAMPLE ======= B{create iblock} ---------------- Creates a new backstore, using the B{iblock} I{backstore_plugin}. ''' self.assert_root() self.shell.log.debug("%r" % [(backstore.plugin, backstore.index) for backstore in RTSRoot().backstores]) indexes = [ backstore.index for backstore in RTSRoot().backstores if backstore.plugin == backstore_plugin ] self.shell.log.debug("Existing %s backstore indexes: %r" % (backstore_plugin, indexes)) for index in range(1048576): if index not in indexes: backstore_index = index break if backstore_index is None: self.shell.log.error("Cannot find an available backstore index.") return else: self.shell.log.info("First available %s backstore index is %d." % (backstore_plugin, backstore_index)) if backstore_plugin == 'pscsi': backstore = PSCSIBackstore(backstore_index, mode='create') return self.new_node(UIPSCSIBackstoreLegacy(backstore, self)) elif backstore_plugin == 'rd_mcp': backstore = RDMCPBackstore(backstore_index, mode='create') return self.new_node(UIRDMCPBackstoreLegacy(backstore, self)) elif backstore_plugin == 'fileio': backstore = FileIOBackstore(backstore_index, mode='create') return self.new_node(UIFileIOBackstoreLegacy(backstore, self)) elif backstore_plugin == 'iblock': backstore = IBlockBackstore(backstore_index, mode='create') return self.new_node(UIIBlockBackstoreLegacy(backstore, self)) else: self.shell.log.error("Invalid backstore plugin %s" % backstore_plugin) return self.shell.log.info("Created new backstore %s" % backstore.name)
def ui_command_create(self, name, file_or_dev, size=None, write_back=None, sparse=None): ''' Creates a FileIO storage object. If I{file_or_dev} is a path to a regular file to be used as backend, then the I{size} parameter is mandatory. Else, if I{file_or_dev} is a path to a block device, the size parameter B{must} be ommited. If present, I{size} is the size of the file to be used, I{file} the path to the file or I{dev} the path to a block device. The I{write_back} parameter is a boolean controlling write caching. It is enabled by default. The I{sparse} parameter is only applicable when creating a new backing file. It is a boolean stating if the created file should be created as a sparse file (the default), or fully initialized. SIZE SYNTAX =========== - If size is an int, it represents a number of bytes. - If size is a string, the following units can be used: - B{B} or no unit present for bytes - B{k}, B{K}, B{kB}, B{KB} for kB (kilobytes) - B{m}, B{M}, B{mB}, B{MB} for MB (megabytes) - B{g}, B{G}, B{gB}, B{GB} for GB (gigabytes) - B{t}, B{T}, B{tB}, B{TB} for TB (terabytes) ''' self.assert_root() sparse = self.ui_eval_param(sparse, 'bool', True) write_back = self.ui_eval_param(write_back, 'bool', True) self.shell.log.debug("Using params size=%s write_back=%s sparse=%s" % (size, write_back, sparse)) file_or_dev = os.path.expanduser(file_or_dev) # can't use is_dev_in_use() on files so just check against other # storage object paths if os.path.exists(file_or_dev): for so in RTSRoot().storage_objects: if so.udev_path and os.path.samefile(file_or_dev, so.udev_path): raise ExecutionError("storage object for %s already exists: %s" % \ (file_or_dev, so.name)) if get_block_type(file_or_dev) is not None: if size: self.shell.log.info("Block device, size parameter ignored") size = None self.shell.log.info("Note: block backstore preferred for best results") else: # use given file size only if backing file does not exist if os.path.isfile(file_or_dev): new_size = os.path.getsize(file_or_dev) if size: self.shell.log.info("%s exists, using its size (%s bytes) instead" % (file_or_dev, new_size)) size = new_size elif os.path.exists(file_or_dev): raise ExecutionError("Path %s exists but is not a file" % file_or_dev) else: # create file and extend to given file size if not size: raise ExecutionError("Attempting to create file for new" + " fileio backstore, need a size") size = human_to_bytes(size) self._create_file(file_or_dev, size, sparse) so = FileIOStorageObject(name, file_or_dev, size, write_back=write_back) ui_so = UIFileioStorageObject(so, self) self.setup_model_alias(so) self.shell.log.info("Created fileio %s with size %s" % (name, so.size)) return self.new_node(ui_so)
def refresh(self): self._children = set([]) for so in RTSRoot().storage_objects: if so.plugin == self.name: ui_so = self.so_cls(so, self) ui_so.name = so.name