def discovery_wrapper(hostname=None, discovery_method=None, operation=None, op_params=(), discover=False): """ Returns the iscsiadm command, with discovery mode, corresponding to different specified arguments """ cmd = [ISCSIADM_BINARY, "-m", "discoverydb"] if hostname: cmd.extend(["-p", hostname]) if discovery_method: cmd.extend(["-t", discovery_method]) if operation: cmd.extend(["-o", operation]) if op_params: cmd.extend(["-n", "discovery.sendtargets.auth.%s" % op_params[0]]) cmd.extend(["-v", op_params[1]]) elif discover: cmd.extend(["-D", "-P", "1"]) return_code, output_success, output_failure = invoke(cmd, False) if return_code: try: error_string = output_failure.splitlines()[0].split(" ", 1)[-1] error_string = error_string.strip() # error_string extracts the text after "iscsiadm: " of the # first line of e.output error_code = get_error_code(return_code) raise TargetdError(error_code, error_string) except IndexError: raise TargetdError(DISCOVERY_RECORD_NOT_FOUND, "No discovery records found") return output_success
def fswrites(req, domain=None, operation=None, timeout=15): """Quiesce or unquiesce filesystem writes by a VM (domain). operation is "freeze" (quiesce) or "thaw" (unquiesce); timeout is optional, with a default of 15: to ensure that a VM is not left in a frozen state, if operation is "freeze" then a thaw will issue timeout seconds after running this program - a timeout of 0 means "no timeout". """ if not domain: raise TargetdError(GENERAL_TARGETD_ERROR, "Must specify a domain (VM).") if operation not in VIRSH_CMDS: raise TargetdError(GENERAL_TARGETD_ERROR, "Unsupported operation (%r)." % operation) cmd = VIRSH_CMDS[operation] % domain log.debug("fswrites: %s" % cmd) subp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = subp.communicate()[0].strip() if subp.returncode: raise RuntimeError("%r returned %r: %r" % (cmd, subp.returncode, out)) if operation == "freeze" and timeout != 0: thaw = VIRSH_CMDS["thaw"] % domain subprocess.Popen("sleep %s && %s" % (timeout, thaw), shell=True)
def create_filtered_volume(req, handler=None, name=None, filters=None, device=None, serial=None): filters = filters or [] handler = handler or "mp_filter_stack" if not name: raise TargetdError(GENERAL_TARGETD_ERROR, "No name specified.") err = _alnum(name, extras='_-') if err: raise TargetdError(GENERAL_TARGETD_ERROR, "%r: %s" % (name, err)) if not (device or serial): raise TargetdError(GENERAL_TARGETD_ERROR, "No device specified.") if serial and not device: device = _device_from_serial(serial) if not device or not os.path.exists(device): raise TargetdError(GENERAL_TARGETD_ERROR, "Device not found.") _validate_filters(filters) filterstack = '>'.join(filters) if filterstack: filterstack += '>' config_str = handler + '/>' + filterstack + device filtered_dev = rtslib.UserBackedStorageObject(name, config=config_str, size=_device_size(device)) log.debug("Created %s on %s" % (name, filterstack + device)) tcm_loop = rtslib.FabricModule("loopback") target = rtslib.Target(tcm_loop) tpg = rtslib.TPG(target, 1) lun = rtslib.LUN(tpg, 0, filtered_dev) log.debug("Created loopback target %s on %s" % (target.wwn, name)) for delay in range(1, 4): time.sleep(delay) dev = _device_from_wwn(filtered_dev.wwn) if dev: log.debug("Target %s is at %s" % (target.wwn, dev)) return dev
def validate_string(msg): """ Raise an error if the specified string is empty, longer than 255 chars or not ASCII encoded """ if len(msg) > 255: raise TargetdError(STRING_TOO_LONG, "String too long") elif msg == "": raise TargetdError(EMPTY_STRING, "Unauthorised empty string") try: msg.decode('ascii') except UnicodeDecodeError: raise TargetdError(NO_ASCSII_STRING, "Not a ascii-encoded unicode string")
def _invoke_retries(command, throw_exception): # TODO take out this loop, used to handle bug in btrfs # ERROR: Failed to lookup path for root 0 - No such file or directory for i in range(0, 5): result, out, err = invoke(command, False) if result == 0: return result, out, err elif result == 19: time.sleep(1) continue else: raise TargetdError(-303, "Unexpected exit code %d" % result) raise TargetdError(-303, "Unable to execute command after " "multiple retries %s" % (str(command)))
def node_wrapper(targetname=None, hostname=None, operation="", op_params=(), login_out=None): """ Returns the iscsiadm command, with node mode, corresponding to different specified arguments """ cmd = [ISCSIADM_BINARY, "-m", "node", "-P", "1"] if targetname: cmd.extend(["-T", targetname]) if hostname: cmd.extend(["-p", hostname]) if operation: cmd.extend(["-o", operation]) if op_params: cmd.extend(["-n", "node.session.auth.%s" % op_params[0]]) cmd.extend(["-v", op_params[1]]) elif login_out: if login_out == "login": cmd.append("--login") if login_out == "logout": cmd.append("--logout") error_code, output_success, output_failure = invoke(cmd, False) if error_code != 0: error_string = output_failure.splitlines()[0].split(" ", 1)[-1].strip() # error_string extracts the text after "iscsiadm: " of the # first line of e.output error_code = get_error_code(error_code) raise TargetdError(error_code, error_string) return output_success
def pool_check(pool_name): """ pool_name *cannot* be trusted, funcs taking a pool param must call this or to ensure passed-in pool name is one targetd has been configured to use. """ if pool_name not in pools: raise TargetdError(-110, "Invalid filesystem pool")
def fs_create(req, pool_name, name, size_bytes): pool_check(pool_name) full_path = os.path.join(pool_name, fs_path, name) if not os.path.exists(full_path): invoke([fs_cmd, 'subvolume', 'create', full_path]) else: raise TargetdError(-53, 'FS already exists')
def discover_portal(req, hostname, discovery_method="sendtargets", auth_method=None, username=None, password=None, username_in=None, password_in=None): """ Discover all targets for a given discovery portal using specified informations """ validate_string(hostname) if discovery_method not in DISCOVERY_METHODS: raise TargetdError( INVALID_VALUE_DISCOVERY, "Invalid value." " Possible values are : %s" % ", ".join(DISCOVERY_METHODS)) if auth_method in AUTH_METHODS: validate_string(username) validate_string(password) discovery_wrapper(hostname, discovery_method, "new") discovery_wrapper(hostname, discovery_method, "update", ("authmethod", "CHAP")) discovery_wrapper(hostname, discovery_method, "update", ("username", username)) discovery_wrapper(hostname, discovery_method, "update", ("password", password)) if auth_method == "mutual_chap": validate_string(username_in) validate_string(password_in) discovery_wrapper(hostname, discovery_method, "update", ("username_in", username_in)) discovery_wrapper(hostname, discovery_method, "update", ("password_in", password_in)) elif auth_method: raise TargetdError( INVALID_VALUE_AUTH, "Invalid value." " Possible values are : %s" % ", ".join(AUTH_METHODS)) output = discovery_wrapper(hostname, discovery_method, discover=True) return node_summary_parser(output)
def nfs_export_remove(req, host, path): found = False for e in Nfs.exports(): if e.host == host and e.path == path: Nfs.export_remove(e) found = True if not found: raise TargetdError(-400, "NFS export to remove not found %s:%s", (host, path))
def delete_filtered_volume(req, name=None): if not name: raise TargetdError(GENERAL_TARGETD_ERROR, "No name specified.") root = rtslib.RTSRoot() stores = [obj for obj in root.storage_objects if obj.name == name and obj.plugin == 'user'] if len(stores) != 1: raise TargetdError(GENERAL_TARGETD_ERROR, "%d backstores with name %r." % (len(stores), name)) targets = {} for target in root.targets: for tpg in target.tpgs: for lun in tpg.luns: if lun.storage_object.name == name: targets[target] = 1 for target in targets.keys(): log.debug("Deleting loopback target %s for %s." % (target.wwn, name)) target.delete() log.debug("Deleting %s." % name) stores[0].delete()
def fs_clone(req, fs_uuid, dest_fs_name, snapshot_id): fs = _get_fs_by_uuid(req, fs_uuid) if not fs: raise TargetdError(-104, "fs_uuid not found") if snapshot_id: snapshot = _get_ss_by_uuid(req, fs_uuid, snapshot_id) if not snapshot: raise TargetdError(-112, "snapshot not found") source = os.path.join(fs['pool'], ss_path, fs['name'], snapshot['name']) dest = os.path.join(fs['pool'], fs_path, dest_fs_name) else: source = os.path.join(fs['pool'], fs_path, fs['name']) dest = os.path.join(fs['pool'], fs_path, dest_fs_name) if os.path.exists(dest): raise TargetdError(-51, "Filesystem with that name exists") invoke([fs_cmd, 'subvolume', 'snapshot', source, dest])
def delete_discovery(req, hostname, discovery_method="sendtargets"): """ Delete discovery of targets at the specified hostname with the right discovery method """ validate_string(hostname) if discovery_method not in DISCOVERY_METHODS: raise TargetdError( INVALID_VALUE_DISCOVERY, "Invalid value." " Possible values are : %s" % ", ".join(DISCOVERY_METHODS)) output = discovery_wrapper(hostname, discovery_method, "delete")
def display_discovery(req, hostname, discovery_method="sendtargets"): """ Returns a dictionary of all data for the discovery portal at the specified hostname """ validate_string(hostname) if discovery_method not in DISCOVERY_METHODS: raise TargetdError( INVALID_VALUE_DISCOVERY, "Invalid value." " Possible values are : %s" % ", ".join(DISCOVERY_METHODS)) output = discovery_wrapper(hostname, discovery_method) return discovery_node_parser(output, "discovery")
def fs_snapshot(req, fs_uuid, dest_ss_name): fs = _get_fs_by_uuid(req, fs_uuid) if fs: source_path = os.path.join(fs['pool'], fs_path, fs['name']) dest_base = os.path.join(fs['pool'], ss_path, fs['name']) dest_path = os.path.join(dest_base, dest_ss_name) create_sub_volume(dest_base) if os.path.exists(dest_path): raise TargetdError(-53, "Snapshot already exists with that name") invoke([fs_cmd, 'subvolume', 'snapshot', '-r', source_path, dest_path])
def display_session(req, targetname=None, hostname=None): """ Returns a dictionary of all data for all active sessions If a session is specified, returns a dictionary of all data for the given session using specified informations """ output = session_wrapper() d = session_parser(output) if targetname and hostname: validate_string(targetname) validate_string(hostname) try: return {targetname: {hostname: d[targetname][hostname]}} except KeyError: raise TargetdError(NO_SESSION_INFO, "Could not get session info") elif targetname: validate_string(targetname) try: return {targetname: d[targetname]} except KeyError: raise TargetdError(NO_SESSION_INFO, "Could not get session info") else: return d
def nfs_export_add(req, host, path, export_path, options): if export_path is not None: raise TargetdError(-401, "separate export path not supported at " "this time") bit_opt = 0 key_opt = {} for o in options: if '=' in o: k, v = o.split('=') key_opt[k] = v else: bit_opt |= Export.bool_option[o] Nfs.export_add(host, path, bit_opt, key_opt)
def session_wrapper(session_id=None): """ Returns the iscsiadm command, with session mode, corresponding to different specified arguments """ cmd = [ISCSIADM_BINARY, "-m", "session", "-P", "1"] if session_id: cmd.extend(["-r", session_id]) error_code, output_success, output_failure = invoke(cmd, False) if error_code != 0: error_string = output_failure.splitlines()[0].split(" ", 1)[-1].strip() # error_string extracts the text after "iscsiadm: " of the # first line of e.output error_code = get_error_code(error_code) raise TargetdError(error_code, error_string) return output_success
def login_target(req, targetname, hostname, auth_method=None, username=None, password=None, username_in=None, password_in=None): """ Login to a given target using specified informations """ validate_string(targetname) validate_string(hostname) # allow for the possibility that we're already logged in devices = glob.glob("/dev/disk/by-path/*%s:*%s-lun-*" % (hostname, targetname)) if len(devices) == 1: return os.path.realpath(devices[0]) if auth_method in AUTH_METHODS: validate_string(username) validate_string(password) node_wrapper(targetname, hostname, "update", ("authmethod", "CHAP")) node_wrapper(targetname, hostname, "update", ("username", username)) node_wrapper(targetname, hostname, "update", ("password", password)) if auth_method == "mutual_chap": validate_string(username_in) validate_string(password_in) node_wrapper(targetname, hostname, "update", ("username_in", username_in)) node_wrapper(targetname, hostname, "update", ("password_in", password_in)) elif auth_method: raise TargetdError( INVALID_VALUE_AUTH, "Invalid value." " Possible values are : %s" % ", ".join(AUTH_METHODS)) node_wrapper(targetname, hostname, login_out="login") for delay in range(4): time.sleep(delay) devices = glob.glob("/dev/disk/by-path/*%s:*%s-lun-*" % (hostname, targetname)) if len(devices) == 1: return os.path.realpath(devices[0])
def _validate_filters(filters): for filt in filters: err = _alnum(filt, extras='_-') if err: raise TargetdError(GENERAL_TARGETD_ERROR, "%r: %s" % (filt, err))