def map_luns(self, config): """ LIO will have blockstorage objects already defined by the igw_lun module, so this method, brings those objects into the gateways TPG """ lio_root = root.RTSRoot() # process each storage object added to the gateway, and map to the tpg for stg_object in lio_root.storage_objects: for tpg in self.tpg_list: if not self.lun_mapped(tpg, stg_object): # use the iblock number for the lun id - /sys/kernel/config/target/core/iblock_1/ansible4 # ^ lun_id = int(stg_object._path.split('/')[-2].split('_')[1]) try: mapped_lun = LUN(tpg, lun=lun_id, storage_object=stg_object) self.changes_made = True except RTSLibError as err: self.error = True self.error_msg = err break self.bind_alua_group_to_lun(config, mapped_lun)
def remove_dev_from_lio(self): lio_root = root.RTSRoot() # remove the device from all tpgs for t in lio_root.tpgs: for lun in t.luns: if lun.storage_object.name == self.backstore_object_name: try: lun.delete() except Exception as e: self.error = True self.error_msg = ("Delete from LIO/TPG failed - " "{}".format(e)) return else: break # continue to the next tpg so = self.lio_stg_object() if not so: self.error = True self.error_msg = ("Removal failed. Could not find LIO object.") return try: so.delete() except Exception as err: self.error = True self.error_msg = ("Delete from LIO/backstores failed - " "{}".format(err)) return
def define_client(self): """ Establish the links for this object to the corresponding ACL and TPG objects from LIO :return: """ r = lio_root.RTSRoot() # NB. this will check all tpg's for a matching iqn for client in r.node_acls: if client.node_wwn == self.iqn: self.acl = client self.tpg = client.parent_tpg logger.debug("(Client.define_client) - {} already defined".format(self.iqn)) return # at this point the client does not exist, so create it # NB. The solution supports only a single tpg definition, so simply grabbing the # first tpg is fine. If multiple tpgs are required this will need more work self.tpg = r.tpgs.next() try: self.acl = NodeACL(self.tpg, self.iqn) except RTSLibError as err: logger.error("(Client.define_client) FAILED to define {}".format(self.iqn)) logger.debug("(Client.define_client) failure msg {}".format(err)) self.error = True self.error_msg = err else: self.change_count += 1 logger.info("(Client.define_client) {} added successfully".format(self.iqn))
def _get_mapped_disks(self): ''' return a dict indexed by a pool/image name that points to the client that has this device mapped to it :return: dict ''' map = {} pools = {} lio_root = root.RTSRoot() # get a list of active sessions on this host indexed by the iqn connections = {} for con in lio_root.sessions: nodeacl = con['parent_nodeacl'] connections[nodeacl.node_wwn] = con['state'] for tpg in lio_root.tpgs: if tpg._get_enable(): # this tpg is enabled, so let's walk the luns # and process all luns mapped to this tpg for lun in tpg.luns: dm_id = os.path.basename(lun.storage_object.udev_path) dm_num = dm_id.split('-')[0] # dev_path = m_lun.storage_object.path # '/sys/kernel/config/target/core/iblock_0/ansible3' # iblock_name = dev_path.split('/')[-2] image_name = lun.storage_object.name if dm_num in pools: pool_name = pools[dm_num] else: pools[dm_num] = get_pool_name(pool_id=int(dm_num)) pool_name = pools[dm_num] key = "{}/{}".format(pool_name, image_name) # udev_path = m_lun.storage_object.udev_path # dev_id = get_devid(udev_path) client_iqns = [] for mapping in lun.mapped_luns: client_iqns.append(mapping.node_wwn) suffix = '' client_shortname = '' if len(client_iqns) == 1: client_shortname = client_iqns[0].split(':')[-1] suffix = '(CON)' if client_iqns[ 0] in connections else '' elif len(client_iqns) > 1: client_shortname = '- multi -' map[key] = client_shortname + suffix return map
def load_config(self): """ Grab the target, tpg and portal objects from LIO and store in this Gateway object """ try: lio_root = root.RTSRoot() self.target = [ tgt for tgt in lio_root.targets if tgt.wwn == self.iqn ][0] self.tpg_list = [] # there could/should be multiple tpg's for the target for tpg in self.target.tpgs: self.tpg_list.append(tpg) # self.portal = self.tpg.network_portals.next() except RTSLibError as err: self.error_msg = err self.error = True self.logger.info("(Gateway.load_config) successfully loaded existing " "target definition")
def logged_in(self): r = root.RTSRoot() for sess in r.sessions: if sess['parent_nodeacl'].node_wwn == self.client_iqn: return sess['state'] return ''
def map_luns(self, config): """ LIO will have objects already defined by the lun module, so this method, brings those objects into the gateways TPG """ lio_root = root.RTSRoot() # process each storage object added to the gateway, and map to the tpg for stg_object in lio_root.storage_objects: for tpg in self.tpg_list: self.logger.debug("processing tpg{}".format(tpg.tag)) if not self.lun_mapped(tpg, stg_object): self.logger.debug("{} needed mapping to " "tpg{}".format(stg_object.name, tpg.tag)) lun_id = int(stg_object.path.split('/')[-2].split('_')[1]) try: mapped_lun = LUN(tpg, lun=lun_id, storage_object=stg_object) self.changes_made = True except RTSLibError as err: self.logger.error("LUN mapping failed: {}".format(err)) self.error = True self.error_msg = err return self.bind_alua_group_to_lun(config, mapped_lun)
def get_tpgs(): """ determine the number of tpgs in the current LIO environment :return: count of the defined tpgs """ return len([tpg.tag for tpg in root.RTSRoot().tpgs])
def clear_config(self): """ Remove the target definition form LIO :return: None """ # check that there aren't any disks or clients in the configuration lio_root = root.RTSRoot() disk_count = len([disk for disk in lio_root.storage_objects]) clients = [] for tpg in self.tpg_list: tpg_clients = [node for node in tpg._list_node_acls()] clients += tpg_clients client_count = len(clients) if disk_count > 0 or client_count > 0: self.error = True self.error_msg = ("Clients({}) and disks({}) must be removed" "before the gateways".format(client_count, disk_count)) return self.logger.debug("Clients defined :{}".format(client_count)) self.logger.debug("Disks defined :{}".format(disk_count)) self.logger.info("Removing target configuration") try: self.delete() except RTSLibError as err: self.error = True self.error_msg = "Unable to delete target - {}".format(err)
def remove_dev_from_lio(self): lio_root = root.RTSRoot() # remove the device from all tpgs for t in lio_root.tpgs: for lun in t.luns: if lun.storage_object.name == self.config_key: try: lun.delete() except Exception as e: self.error = True self.error_msg = ("Delete from LIO/TPG failed - " "{}".format(e)) return else: break # continue to the next tpg for stg_object in lio_root.storage_objects: if stg_object.name == self.config_key: try: stg_object.delete() except Exception as e: self.error = True self.error_msg = ("Delete from LIO/backstores failed - " "{}".format(e)) return break
def drop_target(self, this_host): iqn = self.config.config['gateways'][this_host]['iqn'] lio_root = root.RTSRoot() for tgt in lio_root.targets: if tgt.wwn == iqn: tgt.delete() self.changed = True
def exists(self): """ This function determines whether this instances iqn is already defined to LIO :return: Boolean """ r = lio_root.RTSRoot() client_list = [client.node_wwn for client in r.node_acls] return self.iqn in client_list
def drop_target(self, this_host): if this_host in self.config.config['gateways']: lio_root = root.RTSRoot() for tgt in lio_root.targets: if tgt.wwn in self.config.config['targets'] \ and this_host in self.config.config['targets'][tgt.wwn]['portals']: tgt.delete() self.changed = True
def lun_in_lio(image): found_it = False rtsroot = root.RTSRoot() for stg_object in rtsroot.storage_objects: if stg_object.name == image: found_it = True break return stg_object if found_it else None
def drop_target(self, this_host): iqn = self.config.config['gateways'][this_host]['iqn'] lio_root = root.RTSRoot() for tgt in lio_root.targets: if tgt.wwn == iqn: tgt.delete() self.changed = True # remove the gateway from the config dict self.config.del_item('gateways', this_host)
def _get_mapped_disks(self): ''' return a dict indexed by a pool/image name that points to the client that has this device mapped to it. If the client mapped is currently connected the name used is the alias (dns) of the client from LIO session information - if not, we just use the last qualifier of the iqn :return: dict <pool>.<image> --> <client_name> | '- multi -' ''' map = {} pools = {} lio_root = root.RTSRoot() # get a list of active sessions on this host indexed by the iqn connections = {} for con in lio_root.sessions: nodeacl = con['parent_nodeacl'] connections[nodeacl.node_wwn] = { "state": con['state'], "alias": con['alias'].split('.')[0] } # seed the map dict with an entry for each storage object for so in lio_root.storage_objects: map[so.name] = '' # process each client for node in lio_root.node_acls: # for each client, look at it's luns for m_lun in node.mapped_luns: tpg_lun = m_lun._get_tpg_lun() disk_name = tpg_lun.storage_object.name if map[disk_name]: map[disk_name] = '- multi -' else: # if this node is connected, try and use it's alias if node.node_wwn in connections: suffix = "(CON)" alias_name = connections[node.node_wwn]["alias"] if alias_name: map[disk_name] = "{}(CON)".format(alias_name) else: map[disk_name] = "{}(CON)".format( node.node_wwn.split(':')[-1]) else: map[disk_name] = "{}".format( node.node_wwn.split(':')[-1]) return map
def lun_in_lio(self): found_it = False rtsroot = root.RTSRoot() for stg_object in rtsroot.storage_objects: # First match on name, but then check the pool incase the same name exists in multiple pools if stg_object.name == self.config_key: found_it = True break return stg_object if found_it else None
def map_luns(self, config): """ LIO will have objects already defined by the lun module, so this method, brings those objects into the gateways TPG """ lio_root = root.RTSRoot() target_config = config.config["targets"][self.iqn] target_stg_object = [ stg_object for stg_object in lio_root.storage_objects if stg_object.name in target_config['disks'] ] # a dict, key with tpg and storage object name tpg_stg_object_dict = {} for tpg in self.tpg_list: for l in tpg.luns: key = str(tpg.tag) + "-" + l.storage_object.name tpg_stg_object_dict[key] = l.storage_object.name # process each storage object added to the gateway, and map to the tpg for stg_object in target_stg_object: for tpg in self.tpg_list: self.logger.debug("processing tpg{}".format(tpg.tag)) if str(tpg.tag ) + "-" + stg_object.name not in tpg_stg_object_dict: self.logger.debug("{} needed mapping to " "tpg{}".format(stg_object.name, tpg.tag)) lun_id = int(stg_object.path.split('/')[-2].split('_')[1]) try: mapped_lun = LUN(tpg, lun=lun_id, storage_object=stg_object) self.changes_made = True except RTSLibError as err: self.logger.error("LUN mapping failed: {}".format(err)) self.error = True self.error_msg = err return try: self.bind_alua_group_to_lun(config, mapped_lun) except CephiSCSIInval as err: self.logger.error("Could not bind LUN to ALUA group: " "{}".format(err)) self.error = True self.error_msg = err return
def ui_command_create(self, target_iqn): """ Create an iSCSI target. This target is defined across all gateway nodes, providing the client with a single 'image' for iscsi discovery. Only ONE iSCSI target is supported, at this time. """ self.logger.debug("CMD: /iscsi create {}".format(target_iqn)) defined_targets = [tgt.name for tgt in self.children] if len(defined_targets) > 0: self.logger.error("Only ONE iscsi target image is supported") return # We need LIO to be empty, so check there aren't any targets defined local_lio = root.RTSRoot() current_target_names = [tgt.wwn for tgt in local_lio.targets] if current_target_names: self.logger.error("Local LIO instance already has LIO configured " "with a target - unable to continue") return # OK - this request is valid, but is the IQN usable? if not valid_iqn(target_iqn): self.logger.error("IQN name '{}' is not valid for " "iSCSI".format(target_iqn)) return # 'safe' to continue with the definition self.logger.debug("Create an iscsi target definition in the UI") local_api = ('{}://127.0.0.1:{}/api/' 'target/{}'.format(self.http_mode, settings.config.api_port, target_iqn)) api = APIRequest(local_api) api.put() if api.response.status_code == 200: self.logger.info('ok') # create the target entry in the UI tree Target(target_iqn, self) else: self.logger.error("Failed to create the target on the local node") raise GatewayAPIError("iSCSI target creation failed - " "{}".format(response_message(api.response, self.logger)))
def define_client(self): """ Establish the links for this object to the corresponding ACL and TPG objects from LIO :return: """ r = lio_root.RTSRoot() # NB. this will check all tpg's for a matching iqn for client in r.node_acls: if client.node_wwn == self.iqn: self.acl = client self.tpg = client.parent_tpg self.logger.debug("(Client.define_client) - {} already " "defined".format(self.iqn)) return # at this point the client does not exist, so create it # The configuration only has one active tpg, so pick that one for any # acl definitions for tpg in r.tpgs: if tpg.enable: self.tpg = tpg try: self.acl = NodeACL(self.tpg, self.iqn) # Try to detect network problems so we can kill connections # and cleanup before the initiator has begun recovery and # failed over. self.acl.set_attribute('dataout_timeout', '20') # default 3 # LIO default 30 self.acl.set_attribute( 'nopin_response_timeout', '{}'.format(settings.config.nopin_response_timeout)) # LIO default 15 self.acl.set_attribute('nopin_timeout', '{}'.format(settings.config.nopin_timeout)) except RTSLibError as err: self.logger.error("(Client.define_client) FAILED to define " "{}".format(self.iqn)) self.logger.debug("(Client.define_client) failure msg " "{}".format(err)) self.error = True self.error_msg = err else: self.logger.info("(Client.define_client) {} added " "successfully".format(self.iqn)) self.change_count += 1
def get_lio_devices(): """ LIO uses the kernel's configfs feature to store and manage configuration data, so use rtslib to get a list of the devices :return: dict of dicts describing the rbd devices mapped to LIO """ device_data = {} pools = {} lio_root = root.RTSRoot() # iterate over all luns - this will pick up the same lun mapped to # multiple tpgs - so we look out for that for lun in lio_root.luns: dm_id = os.path.basename(lun.storage_object.udev_path) dm_num = dm_id.split('-')[0] dev_path = lun.storage_object.path # '/sys/kernel/config/target/core/iblock_0/ansible3' iblock_name = dev_path.split('/')[-2] image_name = lun.storage_object.name if dm_num in pools: pool_name = pools[dm_num] else: pools[dm_num] = get_pool_name(pool_id=int(dm_num)) pool_name = pools[dm_num] if '.' in image_name: # assume new format names that have the pool name in already key = image_name else: key = "{}.{}".format(pool_name, image_name) if key not in device_data: rbd_name = 'rbd{}'.format(iblock_name.split('_')[1]) image_size = lun.storage_object.size wwn = lun.storage_object.wwn device_data[key] = {"size": image_size, "wwn": wwn, "rbd_name": rbd_name, "pool": pool_name, "image_name": image_name} return device_data
def define_client(self): """ Establish the links for this object to the corresponding ACL and TPG objects from LIO :return: """ r = lio_root.RTSRoot() # NB. this will check all tpg's for a matching iqn for client in r.node_acls: if client.node_wwn == self.iqn: self.acl = client self.tpg = client.parent_tpg try: self.update_acl_controls() except RTSLibError as err: self.logger.error( "(Client.define_client) FAILED to update " "{}".format(self.iqn)) self.error = True self.error_msg = err self.logger.debug("(Client.define_client) - {} already " "defined".format(self.iqn)) return # at this point the client does not exist, so create it # The configuration only has one active tpg, so pick that one for any # acl definitions for tpg in r.tpgs: if tpg.enable: self.tpg = tpg try: self.acl = NodeACL(self.tpg, self.iqn) self.update_acl_controls() except RTSLibError as err: self.logger.error("(Client.define_client) FAILED to define " "{}".format(self.iqn)) self.logger.debug("(Client.define_client) failure msg " "{}".format(err)) self.error = True self.error_msg = err else: self.logger.info("(Client.define_client) {} added " "successfully".format(self.iqn)) self.change_count += 1
def logged_in(self): r = root.RTSRoot() for sess in r.sessions: if sess['parent_nodeacl'].node_wwn == self.client_iqn: self.alias = sess.get('alias') state = sess.get('state').upper() ips = set() if state == 'LOGGED_IN': for conn in sess.get('connections'): ips.add(conn.get('address')) self.ip_address = ','.join(list(ips)) else: self.ip_address = '' return state return ''
def remove_dev_from_lio(self): lio_root = root.RTSRoot() # remove the device from all tpgs for t in lio_root.tpgs: for lun in t.luns: if lun.storage_object.name == self.config_key: try: lun.delete() except RTSLibError as e: self.error = True self.error_msg = "Delete from LIO/TPG failed - {}".format( e) return else: break # continue to the next tpg for stg_object in lio_root.storage_objects: if stg_object.name == self.config_key: alua_dir = os.path.join(stg_object.path, "alua") # remove the alua directories (future versions will handle this # natively within rtslib_fb for dirname in next(os.walk(alua_dir))[1]: if dirname != "default_tg_pt_gp": try: alua_tpg = ALUATargetPortGroup(stg_object, dirname) alua_tpg.delete() except (RTSLibError, RTSLibNotInCFS) as err: self.error = True self.error_msg = "Delete of ALUA directories failed - {}".format( err) return try: stg_object.delete() except RTSLibError as e: self.error = True self.error_msg = "Delete from LIO/backstores failed - {}".format( e) return break
def load_config(self): """ Grab the target, tpg and portal objects from LIO and store in this Gateway object """ try: # since we only support one target/TPG, we just grab the first iterable lio_root = root.RTSRoot() self.target = lio_root.targets.next() self.tpg = self.target.tpgs.next() self.portal = self.tpg.network_portals.next() except RTSLibError as err: self.error_msg = err self.error = True logger.info( "(Gateway.load_config) successfully loaded existing target definition" )
def load_config(self): """ Grab the target, tpg and portal objects from LIO and store in this Gateway object """ try: lio_root = root.RTSRoot() # since we only support one target, we just grab the first iterable self.target = lio_root.targets.next() # but there could/should be multiple tpg's for the target for tpg in self.target.tpgs: self.tpg_list.append(tpg) # self.portal = self.tpg.network_portals.next() except RTSLibError as err: self.error_msg = err self.error = True self.logger.info( "(Gateway.load_config) successfully loaded existing target definition" )
def __init__(self): self.lio_root = root.RTSRoot() self.error = False self.error_msg = '' self.changed = False
def _get_client_count(self): r = root.RTSRoot() nodeACL_list = [client.node_wwn for client in r.node_acls] return len(nodeACL_list)
def valid_client(**kwargs): """ validate a client create or update request, based on mode. :param kwargs: 'mode' is the key field used to determine process flow :return: 'ok' or an error description (str) """ valid_modes = ['create', 'delete', 'auth', 'disk'] parms_passed = set(kwargs.keys()) if 'mode' in kwargs: if kwargs['mode'] not in valid_modes: return ("Invalid client validation mode request - " "asked for {}, available {}".format(kwargs['mode'], valid_modes)) else: return "Invalid call to valid_client - mode is needed" # at this point we have a mode to work with mode = kwargs['mode'] client_iqn = kwargs['client_iqn'] config = get_config() if not config: return "Unable to query the local API for the current config" if mode == 'create': # iqn must be valid if not valid_iqn(client_iqn): return ("Invalid IQN name for iSCSI") # iqn must not already exist if client_iqn in config['clients']: return ("A client with the name '{}' is " "already defined".format(client_iqn)) # Creates can only be done with a minimum number of gw's in place num_gws = len([gw_name for gw_name in config['gateways'] if isinstance(config['gateways'][gw_name], dict)]) if num_gws < settings.config.minimum_gateways: return ("Clients can not be defined until a HA configuration " "has been defined " "(>{} gateways)".format(settings.config.minimum_gateways)) # at this point pre-req's look good return 'ok' elif mode == 'delete': # client must exist in the configuration if client_iqn not in config['clients']: return ("{} is not defined yet - nothing to " "delete".format(client_iqn)) this_client = config['clients'].get(client_iqn) if this_client.get('group_name', None): return ("Unable to delete '{}' - it belongs to " "group {}".format(client_iqn, this_client.get('group_name'))) # client to delete must not be logged in - we're just checking locally, # since *all* nodes are set up the same, and a client login request # would normally login to each gateway lio_root = root.RTSRoot() clients_logged_in = [session['parent_nodeacl'].node_wwn for session in lio_root.sessions if session['state'] == 'LOGGED_IN'] if client_iqn in clients_logged_in: return ("Client '{}' is logged in - unable to delete until" " it's logged out".format(client_iqn)) # at this point, the client looks ok for a DELETE operation return 'ok' elif mode == 'auth': chap = kwargs['chap'] # client iqn must exist if client_iqn not in config['clients']: return ("Client '{}' does not exist".format(client_iqn)) # must provide chap as either '' or a user/password string if 'chap' not in kwargs: return ("Client auth needs 'chap' defined") # credentials string must be valid if chap: if not valid_credentials(chap): return ("Invalid format for CHAP credentials. Refer to 'help' " "or documentation for the correct format") return 'ok' elif mode == 'disk': this_client = config['clients'].get(client_iqn) if this_client.get('group_name', None): return ("Unable to manage disks for '{}' - it belongs to " "group {}".format(client_iqn, this_client.get('group_name'))) if 'image_list' not in parms_passed: return ("Disk changes require 'image_list' to be set, containing" " a comma separated str of rbd images (pool.image)") rqst_disks = set(kwargs['image_list'].split(',')) mapped_disks = set(config['clients'][client_iqn]['luns'].keys()) current_disks = set(config['disks'].keys()) if len(rqst_disks) > len(mapped_disks): # this is an add operation # ensure the image list is 'complete' not just a single disk if not mapped_disks.issubset(rqst_disks): return ("Invalid image list - it must contain existing " "disks AND any additions") # ensure new disk(s) exist - must yield a result since rqst>mapped new_disks = rqst_disks.difference(mapped_disks) if not new_disks.issubset(current_disks): # disks provided are not currently defined return ("Invalid image list - it defines new disks that do " "not current exist") return 'ok' else: # this is a disk removal operation if kwargs['image_list']: if not rqst_disks.issubset(mapped_disks): return ("Invalid image list ({})".format(rqst_disks)) return 'ok' return 'Unknown error in valid_client function'
def add_dev_to_lio(self, in_wwn=None): """ Add an rbd device to the LIO configuration :param in_wwn: optional wwn identifying the rbd image to clients (must match across gateways) :return: LIO LUN object """ self.logger.info("(LUN.add_dev_to_lio) Adding image " "'{}' to LIO".format(self.config_key)) # extract control parameter overrides (if any) or use default controls = {} for k in ['max_data_area_mb', 'hw_max_sectors']: controls[k] = getattr(self, k) control_string = gen_control_string(controls) if control_string: self.logger.debug("control=\"{}\"".format(control_string)) new_lun = None try: # config string = rbd identifier / config_key (pool/image) / # optional osd timeout cfgstring = "rbd/{}/{};osd_op_timeout={}".format( self.pool, self.image, self.osd_op_timeout) if (settings.config.cephconf != '/etc/ceph/ceph.conf'): cfgstring += ";conf={}".format(settings.config.cephconf) # Another app might have created/removed the dev. Force the cache # to update itself. lio_root = root.RTSRoot() lio_root.invalidate_caches() new_lun = UserBackedStorageObject(name=self.config_key, config=cfgstring, size=self.size_bytes, wwn=in_wwn, control=control_string) except (RTSLibError, IOError) as err: self.error = True self.error_msg = ("failed to add {} to LIO - " "error({})".format(self.config_key, str(err))) self.logger.error(self.error_msg) return None try: new_lun.set_attribute("cmd_time_out", 0) new_lun.set_attribute("qfull_time_out", self.qfull_timeout) except RTSLibError as err: self.error = True self.error_msg = ("Could not set LIO device attribute " "cmd_time_out/qfull_time_out for device: {}. " "Kernel not supported. - " "error({})".format(self.config_key, str(err))) self.logger.error(self.error_msg) new_lun.delete() return None self.logger.info("(LUN.add_dev_to_lio) Successfully added {}" " to LIO".format(self.config_key)) return new_lun