def valid_iqn(iqn): """ confirm whether the given iqn is in an acceptable format :param iqn: (str) iqn name to check :return: (bool) True if iqn is valid for iSCSI """ try: normalize_wwn(['iqn'], iqn) except RTSLibError: return False return True
def ui_command_delete(self, target_iqn): """ Delete an iSCSI target. """ self.logger.debug("CMD: /iscsi delete {}".format(target_iqn)) try: target_iqn, iqn_type = normalize_wwn(['iqn'], target_iqn) except RTSLibError: self.logger.error("IQN name '{}' is not valid for " "iSCSI".format(target_iqn)) return gw_api = ('{}://localhost:{}/api/' 'target/{}'.format(self.http_mode, settings.config.api_port, target_iqn)) api = APIRequest(gw_api) api.delete() if api.response.status_code == 200: self.logger.info('ok') # delete the target entry from the UI tree target_object = [target for target in self.children if target.name == target_iqn][0] self.remove_child(target_object) else: self.logger.error("Failed - {}".format(response_message(api.response, self.logger)))
def __init__(self, logger, client_iqn, image_list, chap): """ Instantiate an instance of an LIO client :param client_iqn: iscsi iqn string :param image_list: list of rbd images (pool/image) to attach to this client :param chap: chap credentials in the format 'user/password' :return: """ self.iqn = client_iqn self.requested_images = image_list # images are in comma separated pool.image_name format self.chap = chap # parameters for auth self.mutual = '' self.tpgauth = '' self.metadata = {} self.acl = None self.client_luns = {} self.tpg = None self.tpg_luns = {} self.lun_id_list = range(256) # available LUN ids 0..255 self.change_count = 0 self.commit_enabled = True # enable commit to the config for changes by default self.logger = logger self.current_config = {} try: valid_iqn = normalize_wwn(['iqn'], client_iqn) except RTSLibError as err: self.error = True self.error_msg = "Invalid client name for iSCSI - {}".format(err) else: self.error = False self.error_msg = ''
def manage_client(client_iqn): """ Manage a client definition to the local gateway Internal Use ONLY :param client_iqn: iscsi name for the client **RESTRICTED** """ if request.method == 'GET': if client_iqn in config.config['clients']: return jsonify(config.config["clients"][client_iqn]), 200 else: return jsonify(message="Client does not exist"), 404 elif request.method == 'PUT': try: valid_iqn = normalize_wwn(['iqn'], client_iqn) except RTSLibError: return jsonify(message="'{}' is not a valid name for " "iSCSI".format(client_iqn)), 400 committing_host = request.form['committing_host'] image_list = request.form.get('image_list', '') chap = request.form.get('chap', '') status_code, status_text = _update_client(client_iqn=client_iqn, images=image_list, chap=chap, committing_host=committing_host) logger.debug("client create: {}".format(status_code)) logger.debug("client create: {}".format(status_text)) return jsonify(message=status_text), status_code else: # DELETE request committing_host = request.form['committing_host'] # Make sure the delete request is for a client we have defined if client_iqn in config.config['clients'].keys(): client = GWClient(logger, client_iqn, '', '') client.manage('absent', committer=committing_host) if client.error: logger.error("Failed to remove client : " "{}".format(client.error_msg)) return jsonify(message="Failed to remove client"), 500 else: if committing_host == this_host(): config.refresh() return jsonify(message="Client deleted ok"), 200 else: logger.error("Delete request for non existent client!") return jsonify(message="Client does not exist!"), 404
def ui_command_delete(self, client_iqn): """ You may delete a client from the configuration, but you must ensure the client has logged out of the iscsi gateways. Attempting to delete a client that has an open session will fail the request e.g. delete <client_iqn> """ self.logger.debug("CMD: ../hosts/ delete {}".format(client_iqn)) self.logger.debug("Client DELETE for {}".format(client_iqn)) # is the IQN usable? try: client_iqn, iqn_type = normalize_wwn(['iqn'], client_iqn) except RTSLibError: self.logger.error("IQN name '{}' is not valid for " "iSCSI".format(client_iqn)) return target_iqn = self.parent.name client_api = ('{}://{}:{}/api/' 'client/{}/{}'.format(self.http_mode, "localhost", settings.config.api_port, target_iqn, client_iqn)) api = APIRequest(client_api) api.delete() if api.response.status_code == 200: # Delete successful across all gateways self.logger.debug("- '{}' removed and configuration " "updated".format(client_iqn)) client = [client for client in self.children if client.name == client_iqn][0] # remove any rbd maps from the lun_map for this client rbds_mapped = [lun.rbd_name for lun in client.children] for rbd in rbds_mapped: self.update_lun_map('remove', rbd, client_iqn) self.delete(client) self.logger.info('ok') else: # client delete request failed self.logger.error(response_message(api.response, self.logger))
def ui_command_create(self, client_iqn): """ Clients may be created using the 'create' sub-command. The initial definition will be added to each gateway without any authentication set. Once a client is created the admin is automatically placed in the context of the new client definition for auth and disk configuration operations. e.g. > create <client_iqn> """ self.logger.debug("CMD: ../hosts/ create {}".format(client_iqn)) cli_seed = {"luns": {}, "auth": {}} # is the IQN usable? try: client_iqn, iqn_type = normalize_wwn(['iqn'], client_iqn) except RTSLibError: self.logger.error("IQN name '{}' is not valid for " "iSCSI".format(client_iqn)) return target_iqn = self.parent.name # Issue the API call to create the client client_api = ('{}://localhost:{}/api/' 'client/{}/{}'.format(self.http_mode, settings.config.api_port, target_iqn, client_iqn)) self.logger.debug("Client CREATE for {}".format(client_iqn)) api = APIRequest(client_api) api.put() if api.response.status_code == 200: Client(self, client_iqn, cli_seed) self.config = get_config() self.logger.debug("- Client '{}' added".format(client_iqn)) self.logger.info('ok') else: self.logger.error("Failed: {}".format(response_message(api.response, self.logger))) return # switch the current directory to the new client for auth or disk # definitions as part of the users workflow return self.ui_command_cd(client_iqn)
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. """ self.logger.debug("CMD: /iscsi create {}".format(target_iqn)) # is the IQN usable? try: target_iqn, iqn_type = normalize_wwn(['iqn'], target_iqn) except RTSLibError: 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 = ('{}://localhost:{}/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_exists = len([ target for target in self.children if target.name == target_iqn ]) > 0 if not target_exists: 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 __init__(self, logger, client_iqn, image_list, username, password, mutual_username, mutual_password, target_iqn): """ Instantiate an instance of an LIO client :param client_iqn: (str) iscsi iqn string :param image_list: (list) list of rbd images (pool/image) to attach to this client or list of tuples (disk, lunid) :param username: (str) chap username :param password: (str) chap password :param mutual_username: (str) chap mutual username :param mutual_password: (str) chap mutual password :param target_iqn: (str) target iqn string :return: """ self.target_iqn = target_iqn self.lun_lookup = {} # only used for hostgroup based definitions self.requested_images = [] # image_list is normally a list of strings (pool/image_name) but # group processing forces a specific lun id allocation to masked disks # in this scenario the image list is a tuple if image_list: if isinstance(image_list[0], tuple): # tuple format ('disk_name', {'lun_id': 0})... for disk_item in image_list: disk_name = disk_item[0] lun_id = disk_item[1].get('lun_id') self.requested_images.append(disk_name) self.lun_lookup[disk_name] = lun_id else: self.requested_images = image_list self.username = username self.password = password self.mutual_username = mutual_username self.mutual_password = mutual_password self.mutual = '' self.tpgauth = '' self.metadata = {} self.acl = None self.client_luns = {} self.tpg = None self.tpg_luns = {} self.lun_id_list = list(range(256)) # available LUN ids 0..255 self.change_count = 0 # enable commit to the config for changes by default self.commit_enabled = True self.logger = logger self.current_config = {} self.error = False self.error_msg = '' try: client_iqn, iqn_type = normalize_wwn(['iqn'], client_iqn) except RTSLibError as err: self.error = True self.error_msg = "Invalid iSCSI client name - {}".format(err) self.iqn = client_iqn # Validate the images list doesn't contain duplicate entries dup_images = set([rbd for rbd in image_list if image_list.count(rbd) >= 2]) if len(dup_images) > 0: self.error = True dup_string = ','.join(dup_images) self.error_msg = ("Client's image list contains duplicate rbd's" ": {}".format(dup_string)) try: super(GWClient, self).__init__('targets', target_iqn, logger, GWClient.SETTINGS) except CephiSCSIError as err: self.error = True self.error_msg = err
def __init__(self, logger, iqn, gateway_ip_list, enable_portal=True): """ Instantiate the class :param iqn: iscsi iqn name for the gateway :param gateway_ip_list: list of IP addresses to be defined as portals to LIO :return: gateway object """ self.error = False self.error_msg = '' self.enable_portal = enable_portal # boolean to trigger portal IP creation self.logger = logger # logger object try: iqn, iqn_type = normalize_wwn(['iqn'], iqn) except RTSLibError as err: self.error = True self.error_msg = "Invalid iSCSI target name - {}".format(err) self.iqn = iqn # Ensure IPv6 addresses are in the normalized address (not literal) format gateway_ip_list = [normalize_ip_address(x) for x in gateway_ip_list] # If the ip list received has data in it, this is a target we need to # act on the IP's provided, otherwise just set to null if gateway_ip_list: # if the ip list provided doesn't match any ip of this host, abort # the assumption here is that we'll only have one matching ip in # the list! matching_ip = set(gateway_ip_list).intersection(ip_addresses()) if len(list(matching_ip)) == 0: self.error = True self.error_msg = ("gateway IP addresses provided do not match" " any ip on this host") return self.active_portal_ip = list(matching_ip)[0] self.logger.debug("active portal will use " "{}".format(self.active_portal_ip)) self.gateway_ip_list = gateway_ip_list self.logger.debug("tpg's will be defined in this order" " - {}".format(self.gateway_ip_list)) else: # without gateway_ip_list passed in this is a 'init' or # 'clearconfig' request self.gateway_ip_list = [] self.active_portal_ip = [] self.changes_made = False self.config_updated = False # self.portal = None self.target = None self.tpg = None self.tpg_list = [] try: super(GWTarget, self).__init__('targets', iqn, logger, GWTarget.SETTINGS) except CephiSCSIError as err: self.error = True self.error_msg = err
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'] target_iqn = kwargs['target_iqn'] config = get_config() if not config: return "Unable to query the local API for the current config" target_config = config['targets'][target_iqn] if mode == 'create': # iqn must be valid try: normalize_wwn(['iqn'], client_iqn) except RTSLibError: return ("Invalid IQN name for iSCSI") # iqn must not already exist if client_iqn in target_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 target_config['clients']: return ("{} is not defined yet - nothing to " "delete".format(client_iqn)) this_client = target_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 client_info = GWClient.get_client_info(target_iqn, client_iqn) if client_info['state'] == 'LOGGED_IN': return ("Client '{}' is logged in to {}- unable to delete until" " it's logged out".format(client_iqn, target_iqn)) # at this point, the client looks ok for a DELETE operation return 'ok' elif mode == 'auth': # client iqn must exist if client_iqn not in target_config['clients']: return ("Client '{}' does not exist".format(client_iqn)) username = kwargs['username'] password = kwargs['password'] mutual_username = kwargs['mutual_username'] mutual_password = kwargs['mutual_password'] error_msg = valid_credentials(username, password, mutual_username, mutual_password) if error_msg: return error_msg return 'ok' elif mode == 'disk': this_client = target_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(target_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 __init__(self, logger, client_iqn, image_list, chap): """ Instantiate an instance of an LIO client :param client_iqn: (str) iscsi iqn string :param image_list: (list) list of rbd images (pool/image) to attach to this client or list of tuples (disk, lunid) :param chap: (str) chap credentials in the format 'user/password' :return: """ self.iqn = client_iqn self.lun_lookup = {} # only used for hostgroup based definitions self.requested_images = [] # image_list is normally a list of strings (pool.image_name) but # group processing forces a specific lun id allocation to masked disks # in this scenario the image list is a tuple if image_list: if isinstance(image_list[0], tuple): # tuple format ('disk_name', {'lun_id': 0})... for disk_item in image_list: disk_name = disk_item[0] lun_id = disk_item[1].get('lun_id') self.requested_images.append(disk_name) self.lun_lookup[disk_name] = lun_id else: self.requested_images = image_list self.chap = chap # parameters for auth self.mutual = '' self.tpgauth = '' self.metadata = {} self.acl = None self.client_luns = {} self.tpg = None self.tpg_luns = {} self.lun_id_list = range(256) # available LUN ids 0..255 self.change_count = 0 # enable commit to the config for changes by default self.commit_enabled = True self.logger = logger self.current_config = {} self.error = False self.error_msg = '' try: valid_iqn = normalize_wwn(['iqn'], client_iqn) except RTSLibError as err: self.error = True self.error_msg = "Invalid client name for iSCSI - {}".format(err) # Validate the images list doesn't contain duplicate entries dup_images = set( [rbd for rbd in image_list if image_list.count(rbd) >= 2]) if len(dup_images) > 0: self.error = True dup_string = ','.join(dup_images) self.error_msg = ("Client's image list contains duplicate rbd's" ": {}".format(dup_string))
def ui_command_create(self, client_iqn): """ Clients may be created using the 'create' sub-command. The initial definition will be added to each gateway without any authentication set, so once the client is created you must 'cd' to the client and add authentication (auth) and any desired disks (disk). > create <client_iqn> """ cli_seed = {"luns": {}, "auth": {}} # make sure the iqn isn't already defined existing_clients = [client.name for client in self.children] if client_iqn in existing_clients: self.logger.error( "Client '{}' is already defined".format(client_iqn)) return try: valid_iqn = normalize_wwn(['iqn'], client_iqn) except RTSLibError: self.logger.critical( "An iqn of '{}' is not a valid name for iSCSI".format( client_iqn)) return # run the create locally - to seed the config object other_gateways = get_other_gateways( self.parent.parent.parent.target.children) api_vars = {"committing_host": this_host()} client_api = '{}://127.0.0.1:{}/api/client/{}'.format( self.http_mode, settings.config.api_port, client_iqn) self.logger.debug("Client CREATE for {}".format(client_iqn)) response = put(client_api, data=api_vars, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: Client(self, client_iqn, cli_seed) self.logger.debug("- Client '{}' added locally".format(client_iqn)) # defined locally OK, so let's apply to the other gateways for gw in other_gateways: client_api = '{}://{}:{}/api/client/{}'.format( self.http_mode, gw, settings.config.api_port, client_iqn) response = put(client_api, data=api_vars, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: self.logger.debug("- Client '{}' added to {}".format( client_iqn, gw)) continue else: raise GatewayAPIError(response.text) else: raise GatewayAPIError(response.text) self.logger.info('ok')