def pool_check(pool_name): """ pool_name *cannot* be trusted, funcs taking a pool param must call this or vgopen() to ensure passed-in pool name is one targetd has been configured to use. """ if not has_pool(pool_name): raise TargetdError(TargetdError.INVALID_POOL, "Invalid pool (LVM)")
def create(req, pool, name, size): mod = pool_module(pool) # Check to ensure that we don't have a volume with this name already, # lvm/zfs will fail if we try to create a LV/dataset with a duplicate name if check_vol_exists(req, pool, name): raise TargetdError(TargetdError.NAME_CONFLICT, "Volume with that name exists") mod.create(req, pool, name, size)
def fs_snapshot(req, pool, name, dest_ss_name): _check_dataset_name(name) _check_dataset_name(dest_ss_name) zfs_pool = pools_fs[pool] info = snap_info(zfs_pool, name, dest_ss_name) if info is not None: raise TargetdError( TargetdError.EXISTS_FS_NAME, "Snapshot {0} already exists on pool {1} for {2}".format( dest_ss_name, pool, name)) code, out, err = _zfs_exec_command( ["snapshot", "{0}/{1}@{2}".format(zfs_pool, name, dest_ss_name)]) if code != 0: raise TargetdError(TargetdError.UNEXPECTED_EXIT_CODE, "Could not create snapshot")
def _zfs_find_cmd(): cmd = distutils.spawn.find_executable('zfs') \ or distutils.spawn.find_executable('zfs', '/sbin:/usr/sbin') if cmd is None or not cmd: raise TargetdError( TargetdError.INVALID, "zfs_block_pools is set but no zfs command was found") global zfs_cmd zfs_cmd = cmd
def destroy(req, pool, name): mod = pool_module(pool) if not check_vol_exists(req, pool, name): raise TargetdError(TargetdError.NOT_FOUND_VOLUME, "Volume %s not found in pool %s" % (name, pool)) with ignored(RTSLibNotInCFS): fm = FabricModule('iscsi') t = Target(fm, target_name, mode='lookup') tpg = TPG(t, 1, mode='lookup') so_name = get_so_name(pool, name) if so_name in (lun.storage_object.name for lun in tpg.luns): raise TargetdError(TargetdError.VOLUME_MASKED, "Volume '%s' cannot be " "removed while exported" % name) pool_module(pool).destroy(req, pool, name)
def pool_check(pool_name): """ pool_name *cannot* be trusted, funcs taking a pool param must call this or vgopen() to ensure passed-in pool name is one targetd has been configured to use. """ pool_to_check = get_vg_lv(pool_name)[0] if pool_to_check not in [get_vg_lv(x)[0] for x in pools]: raise TargetdError(TargetdError.INVALID_POOL, "Invalid pool")
def copy(req, pool, vol_orig, vol_new, timeout=10): """ Create a new volume that is a copy of an existing one. Since 0.6, requires thinp support. """ if any(v['name'] == vol_new for v in volumes(req, pool)): raise TargetdError(TargetdError.NAME_CONFLICT, "Volume with that name exists") vg_name, thin_pool = get_vg_lv(pool) if not thin_pool: raise RuntimeError("copy requires thin-provisioned volumes") try: bd.lvm.thsnapshotcreate(vg_name, vol_orig, vol_new, thin_pool) except bd.LVMError as err: raise TargetdError(TargetdError.UNEXPECTED_EXIT_CODE, "Failed to copy volume, " "nested error: {}".format(str(err).strip()))
def destroy(req, pool, name): _check_dataset_name(name) # -r will destroy snapshots and children but not dependant clones code, out, err = _zfs_exec_command(["destroy", "-r", pool + "/" + name]) if code != 0: if b'volume has dependent clones' in err: logging.error( "Volume %s on %s has dependent clones and cannot be destroyed. Stderr: %s" % (name, pool, err)) raise TargetdError( TargetdError.INVALID_ARGUMENT, "Volume %s on %s has dependent clones and cannot be destroyed." % (name, pool)) else: logging.error( "Could not destroy volume %s on pool %s. Code: %s, stderr %s" % (name, pool, code, err)) raise TargetdError( TargetdError.UNEXPECTED_EXIT_CODE, "Could not destroy volume %s on pool %s" % (name, pool))
def create(req, pool, name, size): _check_dataset_name(name) code, out, err = _zfs_exec_command( ["create", "-V", str(size), pool + "/" + name]) if code != 0: logging.error( "Could not create volume %s on pool %s. Code: %s, stderr %s" % (name, pool, code, err)) raise TargetdError( TargetdError.UNEXPECTED_EXIT_CODE, "Could not create volume %s on pool %s" % (name, pool))
def access_group_map_create(req, pool_name, vol_name, ag_name, h_lun_id=None): tpg = _get_iscsi_tpg() tpg.enable = True tpg.set_attribute("authentication", '0') set_portal_addresses(tpg) tpg_lun = _tpg_lun_of(tpg, pool_name, vol_name) # Pre-Check: # 1. Already mapped to requested access group, return None if any(tpg_lun.mapped_luns): tgt_map_list = access_group_map_list(req) for tgt_map in tgt_map_list: if tgt_map['ag_name'] == ag_name and \ tgt_map['pool_name'] == pool_name and \ tgt_map['vol_name'] == vol_name: # Already masked. return None node_acl_group = NodeACLGroup(tpg, ag_name) if not any(node_acl_group.wwns): # Non-existent access group means volume mapping status will not be # stored. This should be considered as an error instead of silently # returning. raise TargetdError(TargetdError.NOT_FOUND_ACCESS_GROUP, "Access group not found") if h_lun_id is None: # Find out next available host LUN ID # Assuming max host LUN ID is MAX_LUN free_h_lun_ids = set(range(MAX_LUN + 1)) - \ set([int(x.mapped_lun) for x in tpg_lun.mapped_luns]) if len(free_h_lun_ids) == 0: raise TargetdError(TargetdError.NO_FREE_HOST_LUN_ID, "All host LUN ID 0 ~ %d is in use" % MAX_LUN) else: h_lun_id = free_h_lun_ids.pop() node_acl_group.mapped_lun_group(h_lun_id, tpg_lun) RTSRoot().save_to_file()
def access_group_init_del(req, ag_name, init_id, init_type): if init_type != 'iscsi': raise TargetdError(TargetdError.NO_SUPPORT, "Only support iscsi") tpg = _get_iscsi_tpg() # Pre-check: # 1. Initiator is not in requested access group, return silently. if init_id not in list(NodeACLGroup(tpg, ag_name).wwns): return NodeACLGroup(tpg, ag_name).remove_acl(init_id) RTSRoot().save_to_file()
def check_pools_access(check_pools): for pool in check_pools: thinp = None error = "" vg_name, thin_pool = get_vg_lv(pool) if vg_name and thin_pool: # We have VG name and LV name, check for it! try: thinp = bd.lvm.lvinfo(vg_name, thin_pool) except bd.LVMError as lve: error = str(lve).strip() if thinp is None: raise TargetdError( TargetdError.NOT_FOUND_VOLUME_GROUP, "VG with thin LV {} not found, " "nested error: {}".format(pool, error)) else: try: bd.lvm.vginfo(vg_name) except bd.LVMError as vge: error = str(vge).strip() raise TargetdError( TargetdError.NOT_FOUND_VOLUME_GROUP, "VG pool {} not found, " "nested error: {}".format(vg_name, error)) # Allowed multi-pool configs: # two thinpools from a single vg: ok # two vgs: ok # vg and a thinpool from that vg: BAD # if thin_pool and vg_name in check_pools: raise TargetdError( TargetdError.INVALID, "VG pool and thin pool from same VG not supported") return
def destroy(req, pool, name): with ignored(RTSLibNotInCFS): fm = FabricModule('iscsi') t = Target(fm, target_name, mode='lookup') tpg = TPG(t, 1, mode='lookup') so_name = get_so_name(pool, name) if so_name in (lun.storage_object.name for lun in tpg.luns): raise TargetdError( TargetdError.VOLUME_MASKED, "Volume '%s' cannot be " "removed while exported" % name) pool_module(pool).destroy(req, pool, name)
def fs_snapshot_delete(req, pool, name, ss_name): _check_dataset_name(name) _check_dataset_name(ss_name) zfs_pool = pools_fs[pool] info = snap_info(zfs_pool, name, ss_name) if info is None: return code, out, err = _zfs_exec_command(["destroy", "-r", "{0}/{1}@{2}".format(zfs_pool, name, ss_name)]) if code != 0: raise TargetdError(TargetdError.UNEXPECTED_EXIT_CODE, "Could not destroy snapshot")
def access_group_create(req, ag_name, init_id, init_type): if init_type != 'iscsi': raise TargetdError(TargetdError.NO_SUPPORT, "Only support iscsi") name_check(ag_name) tpg = _get_iscsi_tpg() # Pre-check: # 1. Name conflict: requested name is in use # 2. Initiator conflict: request initiator is in use for node_acl_group in tpg.node_acl_groups: if node_acl_group.name == ag_name: raise TargetdError(TargetdError.NAME_CONFLICT, "Requested access group name is in use") if init_id in list(i.node_wwn for i in tpg.node_acls): raise TargetdError(TargetdError.EXISTS_INITIATOR, "Requested init_id is in use") node_acl_group = NodeACLGroup(tpg, ag_name) node_acl_group.add_acl(init_id) RTSRoot().save_to_file()
def destroy(req, pool, name): with ignored(RTSLibNotInCFS): fm = FabricModule('iscsi') t = Target(fm, target_name, mode='lookup') tpg = TPG(t, 1, mode='lookup') vg_name, lv_pool = get_vg_lv(pool) so_name = "%s:%s" % (vg_name, name) if so_name in (lun.storage_object.name for lun in tpg.luns): raise TargetdError( TargetdError.VOLUME_MASKED, "Volume '%s' cannot be " "removed while exported" % name) with vgopen(get_vg_lv(pool)[0]) as vg: vg.lvFromName(name).remove()
def create(req, pool, name, size): # Check to ensure that we don't have a volume with this name already, # lvm will fail if we try to create a LV with a duplicate name if any(v['name'] == name for v in volumes(req, pool)): raise TargetdError(TargetdError.NAME_CONFLICT, "Volume with that name exists") vg_name, lv_pool = get_vg_lv(pool) if lv_pool: # Fall back to non-thinp if needed try: bd.lvm.thlvcreate(vg_name, lv_pool, name, int(size)) except bd.LVMError: bd.lvm.lvcreate(vg_name, name, int(size), 'linear') else: bd.lvm.lvcreate(vg_name, name, int(size), 'linear')
def copy(req, pool, vol_orig, vol_new, timeout=10): """ Create a new volume that is a copy of an existing one. Since 0.6, requires thinp support. """ if any(v['name'] == vol_new for v in volumes(req, pool)): raise TargetdError(TargetdError.NAME_CONFLICT, "Volume with that name exists") vg_name, thin_pool = get_vg_lv(pool) if not thin_pool: raise RuntimeError("copy requires thin-provisioned volumes") try: bd.lvm.thsnapshotcreate(vg_name, vol_orig, vol_new, thin_pool) except AttributeError: raise NotImplementedError("liblvm lacks thin snap support")
def create(req, pool, name, size): # Check to ensure that we don't have a volume with this name already, # lvm will fail if we try to create a LV with a duplicate name if any(v['name'] == name for v in volumes(req, pool)): raise TargetdError(TargetdError.NAME_CONFLICT, "Volume with that name exists") vg_name, lv_pool = get_vg_lv(pool) with vgopen(vg_name) as vg: if lv_pool: # Fall back to non-thinp if needed try: vg.createLvThin(lv_pool, name, int(size)) except AttributeError: vg.createLvLinear(name, int(size)) else: vg.createLvLinear(name, int(size))
def initialize(config_dict): global pools pools = config_dict['block_pools'] global target_name target_name = config_dict['target_name'] # fail early if can't access any vg for pool in pools: vg_name, thin_pool = get_vg_lv(pool) test_vg = lvm.vgOpen(vg_name) test_vg.close() # Allowed multi-pool configs: # two thinpools from a single vg: ok # two vgs: ok # vg and a thinpool from that vg: BAD # if thin_pool and vg_name in pools: raise TargetdError( -1, "VG pool and thin pool from same VG not supported") return dict( vol_list=volumes, vol_create=create, vol_destroy=destroy, vol_copy=copy, export_list=export_list, export_create=export_create, export_destroy=export_destroy, initiator_set_auth=initiator_set_auth, initiator_list=initiator_list, access_group_list=access_group_list, access_group_create=access_group_create, access_group_destroy=access_group_destroy, access_group_init_add=access_group_init_add, access_group_init_del=access_group_init_del, access_group_map_list=access_group_map_list, access_group_map_create=access_group_map_create, access_group_map_destroy=access_group_map_destroy, )
def initialize(config_dict): global pools pools["lvm"] = list(config_dict['block_pools']) pools["zfs"] = list(config_dict['zfs_block_pools']) global target_name target_name = config_dict['target_name'] global addresses addresses = config_dict['portal_addresses'] if any(i in pools['zfs'] for i in pools['lvm']): raise TargetdError( TargetdError.INVALID, "Conflicting names in zfs_block_pools and block_pools in config.") # initialize and check both pools for modname, mod in pool_modules.items(): mod.initialize(pools[modname]) return dict( vol_list=volumes, vol_create=create, vol_destroy=destroy, vol_copy=copy, export_list=export_list, export_create=export_create, export_destroy=export_destroy, initiator_set_auth=initiator_set_auth, initiator_list=initiator_list, access_group_list=access_group_list, access_group_create=access_group_create, access_group_destroy=access_group_destroy, access_group_init_add=access_group_init_add, access_group_init_del=access_group_init_del, access_group_map_list=access_group_map_list, access_group_map_create=access_group_map_create, access_group_map_destroy=access_group_map_destroy, )
def export_destroy(req, pool, vol, initiator_wwn): mod = pool_module(pool) fm = FabricModule('iscsi') t = Target(fm, target_name) tpg = TPG(t, 1) na = NodeACL(tpg, initiator_wwn) pool_dev_name = mod.pool2dev_name(pool) for mlun in na.mapped_luns: # all SOs are Block so we can access udev_path safely if mod.has_udev_path(mlun.tpg_lun.storage_object.udev_path): mlun_vg, mlun_name = \ mod.split_udev_path(mlun.tpg_lun.storage_object.udev_path) if mlun_vg == pool_dev_name and mlun_name == vol: tpg_lun = mlun.tpg_lun mlun.delete() # be tidy and delete unused tpg lun mappings? if not any(tpg_lun.mapped_luns): so = tpg_lun.storage_object tpg_lun.delete() so.delete() break else: raise TargetdError(TargetdError.NOT_FOUND_VOLUME_EXPORT, "Volume '%s' not found in %s exports" % (vol, initiator_wwn)) # Clean up tree if branch has no leaf if not any(na.mapped_luns): na.delete() if not any(tpg.node_acls): tpg.delete() if not any(t.tpgs): t.delete() RTSRoot().save_to_file()
def export_destroy(req, pool, vol, initiator_wwn): pool_check(pool) fm = FabricModule('iscsi') t = Target(fm, target_name) tpg = TPG(t, 1) na = NodeACL(tpg, initiator_wwn) vg_name, thin_pool = get_vg_lv(pool) for mlun in na.mapped_luns: # all SOs are Block so we can access udev_path safely mlun_vg, mlun_name = \ mlun.tpg_lun.storage_object.udev_path.split("/")[2:] if mlun_vg == vg_name and mlun_name == vol: tpg_lun = mlun.tpg_lun mlun.delete() # be tidy and delete unused tpg lun mappings? if not any(tpg_lun.mapped_luns): so = tpg_lun.storage_object tpg_lun.delete() so.delete() break else: raise TargetdError( -151, "Volume '%s' not found in %s exports" % (vol, initiator_wwn)) # Clean up tree if branch has no leaf if not any(na.mapped_luns): na.delete() if not any(tpg.node_acls): tpg.delete() if not any(t.tpgs): t.delete() RTSRoot().save_to_file()
def copy(req, pool, vol_orig, vol_new, timeout=10): mod = pool_module(pool) if not check_vol_exists(req, pool, vol_orig): raise TargetdError(TargetdError.NOT_FOUND_VOLUME, "Volume %s not found in pool %s" % (vol_orig, pool)) pool_module(pool).copy(req, pool, vol_orig, vol_new, timeout)
def initialize(config_dict): global pools pools = config_dict['block_pools'] global target_name target_name = config_dict['target_name'] global addresses addresses = config_dict['portal_addresses'] # fail early if can't access any vg for pool in pools: thinp = None error = "" vg_name, thin_pool = get_vg_lv(pool) if vg_name and thin_pool: # We have VG name and LV name, check for it! try: thinp = bd.lvm.lvinfo(vg_name, thin_pool) except bd.LVMError as lve: error = str(lve).strip() if thinp is None: raise TargetdError( TargetdError.NOT_FOUND_VOLUME_GROUP, "VG with thin LV {} not found, " "nested error: {}".format(pool, error)) else: try: bd.lvm.vginfo(vg_name) except bd.LVMError as vge: error = str(vge).strip() raise TargetdError( TargetdError.NOT_FOUND_VOLUME_GROUP, "VG pool {} not found, " "nested error: {}".format(vg_name, error)) # Allowed multi-pool configs: # two thinpools from a single vg: ok # two vgs: ok # vg and a thinpool from that vg: BAD # if thin_pool and vg_name in pools: raise TargetdError( TargetdError.INVALID, "VG pool and thin pool from same VG not supported") return dict( vol_list=volumes, vol_create=create, vol_destroy=destroy, vol_copy=copy, export_list=export_list, export_create=export_create, export_destroy=export_destroy, initiator_set_auth=initiator_set_auth, initiator_list=initiator_list, access_group_list=access_group_list, access_group_create=access_group_create, access_group_destroy=access_group_destroy, access_group_init_add=access_group_init_add, access_group_init_del=access_group_init_del, access_group_map_list=access_group_map_list, access_group_map_create=access_group_map_create, access_group_map_destroy=access_group_map_destroy, )
def _check_dataset_name(name): if not ALLOWED_DATASET_NAMES.match(name): raise TargetdError( TargetdError.INVALID_ARGUMENT, "Invalid dataset name, can only contain alphanumeric characters," "underscores, dots and hyphens")
def copy(req, pool, vol_orig, vol_new, timeout=10): # TODO: should be easy to do with snapshots raise TargetdError(TargetdError.NO_SUPPORT, "Copy not yet impletmented on ZFS pools")
def so_name_module(so_name): for modname, mod in pool_modules.items(): if mod.has_so_name(so_name): return mod raise TargetdError(TargetdError.INVALID_POOL, "Pool not found by storage object (%s)" % so_name)
def udev_path_module(udev_path): for modname, mod in pool_modules.items(): if mod.has_udev_path(udev_path): return mod raise TargetdError(TargetdError.INVALID_POOL, "Pool not found by udev path (%s)" % udev_path)
def pool_module(pool_name): for modname, mod in pool_modules.items(): if mod.has_pool(pool_name): return mod raise TargetdError(TargetdError.INVALID_POOL, "Invalid pool (%s)" % pool_name)