def _update_client(**kwargs): """ Handler function to apply the changes to a specific client definition :param args: """ # convert the comma separated image_list string into a list for GWClient if kwargs['images']: image_list = str(kwargs['images']).split(',') else: image_list = [] client = GWClient(logger, kwargs['client_iqn'], image_list, kwargs['chap']) if client.error: logger.error("Invalid client request - {}".format(client.error_msg)) return 400, "Invalid client request" client.manage('present', committer=kwargs['committing_host']) if client.error: logger.error("client update failed on {} : " "{}".format(kwargs['client_iqn'], client.error_msg)) return 500, "Client update failed" else: config.refresh() return 200, "Client configured successfully"
def define_clients(): """ define the clients (nodeACLs) to the gateway definition """ # Client configurations (NodeACL's) for client_iqn in config.config['clients']: client_metadata = config.config['clients'][client_iqn] client_chap = CHAP(client_metadata['auth']['chap']) image_list = client_metadata['luns'].keys() chap_str = client_chap.chap_str if client_chap.error: logger.debug("Password decode issue : " "{}".format(client_chap.error_msg)) halt("Unable to decode password for " "{}".format(client_iqn)) client = GWClient(logger, client_iqn, image_list, chap_str) client.manage('present') # ensure the client exists
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 ansible_main(): fields = { "client_iqn": { "required": True, "type": "str" }, "image_list": { "required": True, "type": "str" }, "chap": { "required": True, "type": "str" }, "state": { "required": True, "choices": ['present', 'absent'], "type": "str" }, } module = AnsibleModule(argument_spec=fields, supports_check_mode=False) client_iqn = module.params['client_iqn'] if module.params['image_list']: image_list = module.params['image_list'].split(',') else: image_list = [] chap = module.params['chap'] desired_state = module.params['state'] logger.info("START - Client configuration started : {}".format(client_iqn)) # The client is defined using the GWClient class. This class handles client attribute updates, # rados configuration object updates and LIO settings. Since the logic is external to this # custom module, clients can be created/deleted by other methods in the same manner. client = GWClient(logger, client_iqn, image_list, chap) if client.error: module.fail_json(msg=client.error_msg) client.manage(desired_state) if client.error: module.fail_json(msg=client.error_msg) logger.info( "END - Client configuration complete - {} changes made".format( client.change_count)) changes_made = True if client.change_count > 0 else False module.exit_json(changed=changes_made, meta={ "msg": "Client definition completed {} " "changes made".format(client.change_count) })
def apply_config(): """ procesing logic that orchestrates the creation of the iSCSI gateway to LIO. """ # access config_loading from the outer scope, for r/w global config_loading config_loading = True local_gw = this_host() logger.info("Reading the configuration object to update local LIO " "configuration") # first check to see if we have any entries to handle - if not, there is # no work to do.. if "gateways" not in config.config: logger.info("Configuration is empty - nothing to define to LIO") config_loading = False return if local_gw not in config.config['gateways']: logger.info("Configuration does not have an entry for this host({}) - " "nothing to define to LIO".format(local_gw)) config_loading = False return # at this point we have a gateway entry that applies to the running host portals_already_active = portals_active() logger.info("Processing Gateway configuration") gateway = define_gateway() logger.info("Processing LUN configuration") try: LUN.define_luns(logger, config, gateway) except CephiSCSIError as err: halt("Could not define LUNs: {}".format(err)) logger.info("Processing client configuration") try: GWClient.define_clients(logger, config) except CephiSCSIError as err: halt("Could not define clients: {}".format(err)) if not portals_already_active: # The tpgs, luns and clients are all defined, but the active tpg # doesn't have an IP bound to it yet (due to the enable_portals=False # setting above) logger.info("Adding the IP to the enabled tpg, allowing iSCSI logins") gateway.enable_active_tpg(config) if gateway.error: halt("Error enabling the IP with the active TPG: {}".format( gateway.error_msg)) config_loading = False logger.info("iSCSI configuration load complete")
def update_auth(self, tpg, username=None, password=None, mutual_username=None, mutual_password=None): tpg.chap_userid = username tpg.chap_password = password tpg.chap_mutual_userid = mutual_username tpg.chap_mutual_password = mutual_password auth_enabled = (username and password) if auth_enabled: tpg.set_attribute('authentication', '1') else: GWClient.try_disable_auth(tpg)
def ansible_main(): fields = { "client_iqn": {"required": True, "type": "str"}, "image_list": {"required": True, "type": "str"}, "chap": {"required": True, "type": "str"}, "state": { "required": True, "choices": ['present', 'absent'], "type": "str" }, } module = AnsibleModule(argument_spec=fields, # noqa F405 supports_check_mode=False) client_iqn = module.params['client_iqn'] if module.params['image_list']: image_list = module.params['image_list'].split(',') else: image_list = [] chap = module.params['chap'] desired_state = module.params['state'] logger.info("START - Client configuration started : {}".format(client_iqn)) # The client is defined using the GWClient class. This class handles # client attribute updates, rados configuration object updates and LIO # settings. Since the logic is external to this custom module, clients # can be created/deleted by other methods in the same manner. client = GWClient(logger, client_iqn, image_list, chap) if client.error: module.fail_json(msg=client.error_msg) client.manage(desired_state) if client.error: module.fail_json(msg=client.error_msg) logger.info("END - Client configuration complete - {} " "changes made".format(client.change_count)) changes_made = True if client.change_count > 0 else False module.exit_json(changed=changes_made, meta={"msg": "Client definition completed {} " "changes made".format(client.change_count)})
def update_client(self, client_iqn, image_list): client = GWClient(self.logger, client_iqn, image_list, '') client.define_client() # sets up tpg lun list # grab the client's metadata from the config (needed by setup_luns) client.metadata = self.config.config['clients'][client_iqn] client.setup_luns() if client.error: self._set_error(client.error_msg)
def update_client(self, client_iqn, image_list): client = GWClient(self.logger, client_iqn, image_list, '', '', '', '', self.target_iqn) client.manage('reconfigure') # grab the client's metadata from the config (needed by setup_luns) target_config = self.config.config['targets'][self.target_iqn] client.metadata = target_config['clients'][client_iqn] client.setup_luns(self.config.config['disks']) if client.error: self._set_error(client.error_msg)
def update_client(self, client_iqn, image_list): client = GWClient(self.logger, client_iqn, image_list, '') client.define_client() # set up clients ACL # grab the metadata from the current definition client.metadata = self.config.config['clients'][client_iqn] client.setup_luns() if client.error: self._set_error(client.error_msg) return else: self.logger.info("Updating config object for " "client '{}'".format(client_iqn)) client.metadata['group_name'] = self.group_name self.config.update_item("clients", client_iqn, client.metadata)
def logged_in(self): target_iqn = self.parent.parent.name gateways = self.parent.parent.get_child('gateways') local_gw = this_host() is_local_target = len( [child for child in gateways.children if child.name == local_gw]) > 0 if is_local_target: client_info = GWClient.get_client_info(target_iqn, self.client_iqn) self.alias = client_info['alias'] self.ip_address = ','.join(client_info['ip_address']) return client_info['state'] else: self.alias = '' self.ip_address = '' return ''
def define_target(self, target_iqn, gw_ip_list, target_only=False): """ define the iSCSI target and tpgs :param target_iqn: (str) target iqn :param gw_ip_list: (list) gateway ip list :param target_only: (bool) if True only setup target :return: (object) GWTarget object """ # GWTarget Definition : Handle the creation of the Target/TPG(s) and # Portals. Although we create the tpgs, we flick the enable_portal flag # off so the enabled tpg will not have an outside IP address. This # prevents clients from logging in too early, failing and giving up # because the nodeACL hasn't been defined yet (yes Windows I'm looking # at you!) # first check if there are tpgs already in LIO (True) - this would # indicate a restart or reload call has been made. If the tpg count is # 0, this is a boot time request self.logger.info("Setting up {}".format(target_iqn)) target = GWTarget(self.logger, target_iqn, gw_ip_list, enable_portal=self.portals_active(target_iqn)) if target.error: raise CephiSCSIError("Error initializing iSCSI target: " "{}".format(target.error_msg)) target.manage('target') if target.error: raise CephiSCSIError("Error creating the iSCSI target (target, " "TPGs, Portals): {}".format(target.error_msg)) if not target_only: self.logger.info("Processing LUN configuration") try: LUN.define_luns(self.logger, self.config, target) except CephiSCSIError as err: self.logger.error("{} - Could not define LUNs: " "{}".format(target.iqn, err)) raise self.logger.info("{} - Processing client configuration". format(target.iqn)) try: GWClient.define_clients(self.logger, self.config, target.iqn) except CephiSCSIError as err: self.logger.error("Could not define clients: {}".format(err)) raise if not target.enable_portal: # The tpgs, luns and clients are all defined, but the active tpg # doesn't have an IP bound to it yet (due to the # enable_portals=False setting above) self.logger.info("{} - Adding the IP to the enabled tpg, " "allowing iSCSI logins".format(target.iqn)) target.enable_active_tpg(self.config) if target.error: raise CephiSCSIError("{} - Error enabling the IP with the " "active TPG: {}".format(target.iqn, target.error_msg)) return target
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 activate(self): disk = self.config.config['disks'].get(self.config_key, None) if not disk: raise CephiSCSIError("Image {} not found.".format(self.image)) wwn = disk.get('wwn', None) if not wwn: raise CephiSCSIError("LUN {} missing wwn".format(self.image)) # re-add backend storage object so = self.lio_stg_object() if not so: self.add_dev_to_lio(wwn) if self.error: raise CephiSCSIError("LUN activate failure - {}".format( self.error_msg)) # re-add LUN to target local_gw = this_host() targets_items = [ item for item in self.config.config['targets'].items() if self.config_key in item[1]['disks'] and local_gw in item[1]['portals'] ] for target_iqn, target in targets_items: ip_list = target['ip_list'] # Add the mapping for the lun to ensure the block device is # present on all TPG's gateway = GWTarget(self.logger, target_iqn, ip_list) gateway.manage('map') if gateway.error: raise CephiSCSIError("LUN mapping failed - {}".format( gateway.error_msg)) # re-map LUN to hosts client_err = '' for client_iqn in target['clients']: client_metadata = target['clients'][client_iqn] if client_metadata.get('group_name', ''): continue image_list = list(client_metadata['luns'].keys()) if self.config_key not in image_list: continue client_chap = CHAP(client_metadata['auth']['chap']) chap_str = client_chap.chap_str if client_chap.error: raise CephiSCSIError("Password decode issue : " "{}".format(client_chap.error_msg)) client_chap_mutual = CHAP( client_metadata['auth']['chap_mutual']) chap_mutual_str = client_chap_mutual.chap_str if client_chap_mutual.error: raise CephiSCSIError("Password decode issue : " "{}".format( client_chap_mutual.error_msg)) client = GWClient(self.logger, client_iqn, image_list, chap_str, chap_mutual_str, target_iqn) client.manage('present') if client.error: client_err = "LUN mapping failed {} - {}".format( client_iqn, client.error_msg) # re-map LUN to host groups for group_name in target['groups']: host_group = target['groups'][group_name] members = host_group.get('members') disks = host_group.get('disks').keys() if self.config_key not in disks: continue group = Group(self.logger, target_iqn, group_name, members, disks) group.apply() if group.error: client_err = "LUN mapping failed {} - {}".format( group_name, group.error_msg) if client_err: raise CephiSCSIError(client_err)
def logged_in(self): target_iqn = self.parent.parent.name client_info = GWClient.get_client_info(target_iqn, self.client_iqn) self.alias = client_info['alias'] self.ip_address = ','.join(client_info['ip_address']) return client_info['state']