def ui_command_create(self, gateway_name, ip_address, nosync=False, skipchecks='false'): """ Define a gateway to the gateway group for this iscsi target. The first host added should be the gateway running the command gateway_name ... should resolve to the hostname of the gateway ip_address ..... is the IPv4/IPv6 address of the interface the iscsi portal should use nosync ......... by default new gateways are sync'd with the existing configuration by cli. By specifying nosync the sync step is bypassed - so the new gateway will need to have it's rbd-target-gw daemon restarted to apply the current configuration (default = False) skipchecks ..... set this to true to force gateway validity checks to be bypassed(default = False). This is a developer option ONLY. Skipping these checks has the potential to result in an unstable configuration. """ ip_address = normalize_ip_address(ip_address) self.logger.debug("CMD: ../gateways/ create {} {} " "nosync={} skipchecks={}".format(gateway_name, ip_address, nosync, skipchecks)) local_gw = this_host() current_gateways = [tgt.name for tgt in self.children] if gateway_name != local_gw and len(current_gateways) == 0: # the first gateway defined must be the local machine. By doing # this the initial create uses localhost, and places it's portal IP # in the gateway ip list. Once the gateway ip list is defined, the # api server can resolve against the gateways - until the list is # defined only a request from localhost is acceptable to the api self.logger.error("The first gateway defined must be the local " "machine") return if skipchecks not in ['true', 'false']: self.logger.error("skipchecks must be either true or false") return if local_gw in current_gateways: current_gateways.remove(local_gw) config = self.parent.parent.parent._get_config() if not config: self.logger.error("Unable to refresh local config" " over API - sync aborted, restart rbd-target-gw" " on {} to sync".format(gateway_name)) target_iqn = self.parent.name target_config = config['targets'][target_iqn] if nosync: sync_text = "sync skipped" else: sync_text = ("sync'ing {} disk(s) and " "{} client(s)".format(len(target_config['disks']), len(target_config['clients']))) if skipchecks == 'true': self.logger.warning("OS version/package checks have been bypassed") self.logger.info("Adding gateway, {}".format(sync_text)) gw_api = '{}://{}:{}/api'.format(self.http_mode, "localhost", settings.config.api_port) gw_rqst = gw_api + '/gateway/{}/{}'.format(target_iqn, gateway_name) gw_vars = {"nosync": nosync, "skipchecks": skipchecks, "ip_address": ip_address} api = APIRequest(gw_rqst, data=gw_vars) api.put() msg = response_message(api.response, self.logger) if api.response.status_code != 200: self.logger.error("Failed : {}".format(msg)) return self.logger.debug("{}".format(msg)) self.logger.debug("Adding gw to UI") # Target created OK, get the details back from the gateway and # add to the UI. We have to use the new gateway to ensure what # we get back is current (the other gateways will lag until they see # epoch xattr change on the config object) new_gw_endpoint = ('{}://{}:{}/' 'api'.format(self.http_mode, gateway_name, settings.config.api_port)) config = self.parent.parent.parent._get_config(endpoint=new_gw_endpoint) target_config = config['targets'][target_iqn] portal_config = target_config['portals'][gateway_name] Gateway(self, gateway_name, portal_config) self.logger.info('ok')
def ui_command_host(self, action, client_iqn): """ use the 'host' sub-command to add and remove hosts from a host group. Adding a host will automatically map the host group's disks to that specific host. Removing a host however, does not change the hosts disk masking - it simply removes the host from group. e.g. host add|remove iqn.1994-05.com.redhat:rh7-client """ if action not in HostGroup.valid_actions: self.logger.error("Invalid request - must be " "host add|remove <client_iqn>") return target_iqn = self.parent.parent.name # basic checks client_group = self._get_client_group(target_iqn) client_map = client_group.client_map if client_iqn not in client_map: self.logger.error("'{}' is not managed by a " "group".format(client_iqn)) return current_group = client_map[client_iqn].group_name if action == 'add' and current_group: self.logger.error("'{}' already belongs to " "'{}'".format(client_iqn, current_group)) return elif action == 'remove' and current_group != self.name: self.logger.error("'{}' does not belong to this " "group".format(client_iqn)) return # Basic checks passed, hand-off to the API now group_api = ('{}://{}:{}/api/hostgroup/' '{}/{}'.format(self.http_mode, "localhost", settings.config.api_port, target_iqn, self.name)) api_vars = {"action": action, "members": client_iqn} api = APIRequest(group_api, data=api_vars) api.put() self.logger.debug("- api call responded " "{}".format(api.response.status_code)) if api.response.status_code != 200: self.logger.error("Failed :" "{}".format( response_message(api.response, self.logger))) return # group updated, so update the UI self.logger.debug("Updating the UI") if action == 'add': HostGroupMember(self, 'host', client_iqn) self.update_clients_UI([client_iqn], target_iqn) elif action == 'remove': child = [ child for child in self.children if child.name == client_iqn ][0] self.delete(child) self.logger.info('ok')
def ui_command_discovery_auth(self, username=None, password=None, mutual_username=None, mutual_password=None): """ Discovery authentication can be set to use CHAP/CHAP_MUTUAL by supplying username, password, mutual_username, mutual_password Specifying 'nochap' will remove discovery authentication. e.g. auth username=<user> password=<pass> mutual_username=<m_user> mutual_password=<m_pass> """ self.logger.warn( "discovery username={}, password={}, mutual_username={}, " "mutual_password={}".format(username, password, mutual_username, mutual_password)) self.logger.debug("CMD: /iscsi discovery_auth") if not username: self.logger.error( "To set or reset discovery authentication, specify either " "username=<user> password=<password> [mutual_username]=<user> " "[mutual_password]=<password> or nochap") return if username == 'nochap': username = '' password = '' mutual_username = '' mutual_password = '' self.logger.debug( "discovery auth to be set to username='******', password='******', " "mutual_username='******', mutual_password='******'".format( username, password, mutual_username, mutual_password)) api_vars = { "username": username, "password": password, "mutual_username": mutual_username, "mutual_password": mutual_password } discoveryauth_api = ('{}://localhost:{}/api/' 'discoveryauth'.format(self.http_mode, settings.config.api_port)) api = APIRequest(discoveryauth_api, data=api_vars) api.put() if api.response.status_code == 200: self._set_auth(username, password, mutual_username, mutual_password) self.logger.info('ok') else: self.logger.error("Error: {}".format( response_message(api.response, self.logger))) return
def ui_command_delete(self, gateway_name, confirm=None): """ Delete a gateway from the group. This will stop and delete the target running on the gateway. If this is the last gateway the target is mapped to all objects added to it will be removed, and confirm=True is required. """ self.logger.debug("CMD: ../gateways/ delete {} confirm {}".format( gateway_name, confirm)) self.logger.info("Deleting gateway, {}".format(gateway_name)) config = self.parent.parent.parent._get_config() if not config: self.logger.error("Unable to refresh local config over API - sync " "aborted, restart rbd-target-api on {} to " "sync".format(gateway_name)) return target_iqn = self.parent.name gw_cnt = len(config['targets'][target_iqn]['portals']) if gw_cnt == 0: self.logger.error("Target is not mapped to any gateways.") return if gw_cnt == 1: confirm = self.ui_eval_param(confirm, 'bool', False) if not confirm: self.logger.error("Deleting the last gateway will remove all " "objects on this target. Use confirm=true") return gw_api = '{}://{}:{}/api'.format(self.http_mode, "localhost", settings.config.api_port) gw_rqst = gw_api + '/gateway/{}/{}'.format(target_iqn, gateway_name) api = APIRequest(gw_rqst) api.delete() msg = response_message(api.response, self.logger) if api.response.status_code != 200: self.logger.error("Failed : {}".format(msg)) return self.logger.debug("{}".format(msg)) self.logger.debug("Removing gw from UI") gw_object = self.get_child(gateway_name) self.remove_child(gw_object) config = self.parent.parent.parent._get_config() if not config: self.logger.error("Could not refresh disaply. Restart gwcli.") elif not config['targets'][target_iqn]['portals']: # no more gws so everything but the target is dropped. disks_object = self.parent.get_child("disks") disks_object.reset() hosts_grp_object = self.parent.get_child("host-groups") hosts_grp_object.reset() hosts_object = self.parent.get_child("hosts") hosts_object.reset()
def create_disk(self, pool=None, image=None, size=None, count=1, parent=None, create_image=True, backstore=None): rc = 0 if not parent: parent = self local_gw = this_host() disk_key = "{}/{}".format(pool, image) if not self._valid_pool(pool): return self.logger.debug("Creating/mapping disk {}/{}".format(pool, image)) # make call to local api server's disk endpoint disk_api = '{}://localhost:{}/api/disk/{}'.format( self.http_mode, settings.config.api_port, disk_key) api_vars = { 'pool': pool, 'owner': local_gw, 'count': count, 'mode': 'create', 'create_image': 'true' if create_image else 'false', 'backstore': backstore } if size: api_vars['size'] = size.upper() self.logger.debug("Issuing disk create request") api = APIRequest(disk_api, data=api_vars) api.put() if api.response.status_code == 200: # rbd create and map successful across all gateways so request # it's details and add to the UI self.logger.debug("- LUN(s) ready on all gateways") self.logger.info("ok") self.logger.debug("Updating UI for the new disk(s)") for n in range(1, (int(count) + 1), 1): if int(count) > 1: disk_key = "{}/{}{}".format(pool, image, n) else: disk_key = "{}/{}".format(pool, image) disk_api = ('{}://localhost:{}/api/disk/' '{}'.format(self.http_mode, settings.config.api_port, disk_key)) api = APIRequest(disk_api) api.get() if api.response.status_code == 200: try: image_config = api.response.json() except Exception: raise GatewayAPIError("Malformed REST API response") disk_pool = None for current_disk_pool in self.children: if current_disk_pool.name == pool: disk_pool = current_disk_pool break if disk_pool: Disk(disk_pool, disk_key, image_config) else: DiskPool(parent, pool, [image_config]) self.logger.debug("{} added to the UI".format(disk_key)) else: raise GatewayAPIError( "Unable to retrieve disk details " "for '{}' from the API".format(disk_key)) ceph_pools = self.parent.ceph.cluster.pools ceph_pools.refresh() else: self.logger.error("Failed : {}".format( response_message(api.response, self.logger))) rc = 8 return rc
def ui_command_disk(self, action='add', disk=None, size=None): """ Disks can be added or removed from the client one at a time using the 'disk' sub-command. Note that if the disk does not currently exist in the configuration, the cli will attempt to create it for you. e.g. disk add <pool_name.image_name> <size> disk remove <pool_name.image_name> Adding a disk will result in the disk occupying the client's next available lun id. Once allocated removing a LUN will not change the LUN id associations for the client. Note that if the client is a member of a host group, disk management *must* be performed at the group level. Attempting to add/remove disks at the client level will fail. """ self.logger.debug("CMD: ../hosts/<client_iqn> disk action={}" " disk={}".format(action, disk)) valid_actions = ['add', 'remove'] if not disk: self.logger.critical("You must supply a disk name to add/remove " "for this client") return if action not in valid_actions: self.logger.error("you can only add and remove disks - {} is " "invalid ".format(action)) return lun_list = [(lun.rbd_name, lun.lun_id) for lun in self.children] current_luns = Client.get_srtd_names(lun_list) if action == 'add': if disk not in current_luns: ui_root = self.get_ui_root() valid_disk_names = [ defined_disk.image_id for defined_disk in ui_root.disks.children ] else: # disk provided is already mapped, so remind the user self.logger.error("Disk {} already mapped".format(disk)) return else: valid_disk_names = current_luns if disk not in valid_disk_names: # if this is an add operation, we can create the disk on-the-fly # for the admin if action == 'add': ui_root = self.get_ui_root() ui_disks = ui_root.disks if not size: self.logger.error("To auto-define the disk to the client" " you must provide a disk size") return # a disk given here would be of the form pool.image try: pool, image = disk.split('.') except ValueError: self.logger.error( "Invalid format. Use pool_name.disk_name") return rc = ui_disks.create_disk(pool=pool, image=image, size=size) if rc == 0: self.logger.debug("disk auto-define successful") else: self.logger.error("disk auto-define failed({}), try " "using the /disks create " "command".format(rc)) return else: self.logger.error("disk '{}' is not mapped to this " "client ".format(disk)) return # At this point we are either in add/remove mode, with a valid disk # to act upon self.logger.debug("Client '{}' update - {} disk " "{}".format(self.client_iqn, action, disk)) api_vars = {"disk": disk} clientlun_api = ('{}://localhost:{}/api/' 'clientlun/{}'.format(self.http_mode, settings.config.api_port, self.client_iqn)) api = APIRequest(clientlun_api, data=api_vars) if action == 'add': api.put() else: api.delete() if api.response.status_code == 200: self.logger.debug("disk mapping updated successfully") if action == 'add': # The addition of the lun will get a lun id assigned so # we need to query the api server to get the new configuration # to be able to set the local cli entry correctly get_api_vars = {"disk": disk} clientlun_api = clientlun_api.replace('/clientlun/', '/_clientlun/') self.logger.debug("Querying API to get mapped LUN information") api = APIRequest(clientlun_api, data=get_api_vars) api.get() if api.response.status_code == 200: try: lun_dict = api.response.json()['message'] except Exception: self.logger.error("Malformed REST API response") return # now update the UI lun_id = lun_dict[disk]['lun_id'] self.add_lun(disk, lun_id) else: self.logger.error("Query for disk '{}' meta data " "failed".format(disk)) return else: # this was a remove request, so simply delete the child # MappedLun object corresponding to this rbd name mlun = [lun for lun in self.children if lun.rbd_name == disk][0] self.remove_lun(mlun) self.logger.debug("configuration update successful") self.logger.info('ok') else: # the request to add/remove the disk for the client failed self.logger.error("disk {} for '{}' against {} failed" "\n{}".format( action, disk, self.client_iqn, response_message(api.response, self.logger))) return
def set_auth(self, username=None, password=None, mutual_username=None, mutual_password=None): self.logger.debug("username={}, password={}, mutual_username={}, " "mutual_password={}".format(username, password, mutual_username, mutual_password)) self.logger.debug("CMD: ../hosts/<client_iqn> auth *") if not username: self.logger.error( "To set or reset authentication, specify either " "username=<user> password=<password> " "[mutual_username]=<user> [mutual_password]=<password> " "or nochap") return if username == 'nochap': username = '' password = '' mutual_username = '' mutual_password = '' self.logger.debug( "auth to be set to username='******', password='******', mutual_username='******', " "mutual_password='******' for '{}'".format(username, password, mutual_username, mutual_password, self.client_iqn)) target_iqn = self.parent.parent.name api_vars = { "username": username, "password": password, "mutual_username": mutual_username, "mutual_password": mutual_password } clientauth_api = ('{}://localhost:{}/api/' 'clientauth/{}/{}'.format(self.http_mode, settings.config.api_port, target_iqn, self.client_iqn)) api = APIRequest(clientauth_api, data=api_vars) api.put() if api.response.status_code == 200: self.logger.debug("- client credentials updated") self.auth['username'] = username self.auth['password'] = password self.auth['mutual_username'] = mutual_username self.auth['mutual_password'] = mutual_password self.logger.info('ok') else: self.logger.error("Failed to update the client's auth: " "{}".format( response_message(api.response, self.logger))) return
def create_disk(self, pool=None, image=None, size=None, count=1, max_data_area_mb=None, parent=None): rc = 0 if not parent: parent = self local_gw = this_host() disk_key = "{}.{}".format(pool, image) if not self._valid_pool(pool): return self.logger.debug("Creating/mapping disk {}/{}".format(pool, image)) # make call to local api server's disk endpoint disk_api = '{}://127.0.0.1:{}/api/disk/{}'.format( self.http_mode, settings.config.api_port, disk_key) api_vars = { 'pool': pool, 'size': size.upper(), 'owner': local_gw, 'count': count, 'max_data_area_mb': max_data_area_mb, 'mode': 'create' } self.logger.debug("Issuing disk create request") api = APIRequest(disk_api, data=api_vars) api.put() if api.response.status_code == 200: # rbd create and map successful across all gateways so request # it's details and add to the UI self.logger.debug("- LUN(s) ready on all gateways") self.logger.info("ok") self.logger.debug("Updating UI for the new disk(s)") for n in range(1, (int(count) + 1), 1): if count > 1: disk_key = "{}.{}{}".format(pool, image, n) else: disk_key = "{}.{}".format(pool, image) disk_api = ('{}://127.0.0.1:{}/api/disk/' '{}'.format(self.http_mode, settings.config.api_port, disk_key)) api = APIRequest(disk_api) api.get() if api.response.status_code == 200: try: image_config = api.response.json() except: raise GatewayAPIError("Malformed REST API response") Disk(parent, disk_key, image_config) self.logger.debug("{} added to the UI".format(disk_key)) else: raise GatewayAPIError( "Unable to retrieve disk details " "for '{}' from the API".format(disk_key)) ceph_pools = self.parent.ceph.local_ceph.pools ceph_pools.refresh() else: self.logger.error("Failed : {}".format( response_message(api.response, self.logger))) rc = 8 return rc
def ui_command_disk(self, action, disk_name): """ use the 'disk' sub-command to add or remove a disk from a specific host group. Removing disks should be done with care, as the remove operation will be executed across all hosts defined to the host group. e.g. disk add|remove rbd.disk_1 """ if action not in HostGroup.valid_actions: self.logger.error("Invalid request - must be " "disk add|remove <disk_image>") return # simple sanity checks # 1. does the disk exist in the configuration ui_root = self.get_ui_root() if disk_name not in [disk.name for disk in ui_root.disks.children]: self.logger.error("Disk '{}' is not defined within the " "configuration".format(disk_name)) return # 2. For an 'add' request, the disk must not already be in the host # group. Whereas, for a remove request the disk must exist. if action == 'add': if disk_name in self.disks: self.logger.error("'{}' is already defined to this " "host-group".format(disk_name)) return else: if disk_name not in self.disks: self.logger.error("'{}' is not a member of this " "group".format(disk_name)) return # Basic checks passed, hand-off to the API group_api = ('{}://{}:{}/api/hostgroup/' '{}'.format(self.http_mode, "localhost", settings.config.api_port, self.name)) api_vars = {"action": action, "disks": disk_name} api = APIRequest(group_api, data=api_vars) api.put() self.logger.debug("- api call responded {}".format( api.response.status_code)) if api.response.status_code != 200: self.logger.error("Failed: " "{}".format( response_message(api.response, self.logger))) return # group updated, so update the host-groups UI elements self.logger.debug("Updating the UI") if action == 'add': HostGroupMember(self, 'disk', disk_name) elif action == 'remove': child = [ child for child in self.children if child.name == disk_name ][0] self.delete(child) self.update_clients_UI(self.members) self.logger.info('ok')
def ui_command_auth(self, chap=None): """ Client authentication can be set to use CHAP by supplying the a string of the form <username>/<password> e.g. auth chap=username/password | nochap username ... the username is 8-64 character string. Each character may either be an alphanumeric or use one of the following special characters .,:,-,@. Consider using the hosts 'shortname' or the initiators IQN value as the username password ... the password must be between 12-16 chars in length containing alphanumeric characters, plus the following special characters @,_,- WARNING: Using unsupported special characters may result in truncation, resulting in failed logins. Specifying 'nochap' will remove chap authentication for the client across all gateways. """ self.logger.debug("CMD: ../hosts/<client_iqn> auth *") if not chap: self.logger.error("To set or reset authentication, specify either " "chap=<user>/<password> or nochap") return if chap == 'nochap': chap = '' else: # string could have been supplied as chap=user/password or # simply user/password - either way all we see is user/password if '/' not in chap: self.logger.error( "CHAP format is invalid - must be a <username>/<password> " "format. Use 'help auth' to show the correct syntax and " "supported characters") return self.logger.debug( "CHAP to be set to '{}' for '{}'".format(chap, self.client_iqn)) api_vars = {"chap": chap} clientauth_api = ('{}://127.0.0.1:{}/api/' 'clientauth/{}'.format(self.http_mode, settings.config.api_port, self.client_iqn)) api = APIRequest(clientauth_api, data=api_vars) api.put() if api.response.status_code == 200: self.logger.debug("- client credentials updated") self.auth['chap'] = chap self.logger.info('ok') else: self.logger.error("Failed to update the client's auth" " :{}".format(response_message(api.response, self.logger))) return
def ui_command_auth(self, username=None, password=None, mutual_username=None, mutual_password=None): """ Target authentication can be set to use CHAP/CHAP_MUTUAL by supplying username, password, mutual_username, mutual_password e.g. auth username=<user> password=<pass> mutual_username=<m_user> mutual_password=<m_pass> username / mutual_username ... the username is 8-64 character string. Each character may either be an alphanumeric or use one of the following special characters .,:,-,@. Consider using the hosts 'shortname' or the initiators IQN value as the username password / mutual_password ... the password must be between 12-16 chars in length containing alphanumeric characters, plus the following special characters @,_,-,/ """ self.logger.debug("CMD: /iscsi-targets/<target_iqn> auth *") if not username: self.logger.error( "To set authentication, specify " "username=<user> password=<password> " "[mutual_username]=<user> [mutual_password]=<password> " "or nochap") return if username == 'nochap': username = '' password = '' mutual_username = '' mutual_password = '' self.logger.debug( "auth to be set to username='******', password='******', mutual_username='******', " "mutual_password='******'".format(username, password, mutual_username, mutual_password)) target_iqn = self.name api_vars = { "username": username, "password": password, "mutual_username": mutual_username, "mutual_password": mutual_password } targetauth_api = ('{}://localhost:{}/api/' 'targetauth/{}'.format(self.http_mode, settings.config.api_port, target_iqn)) api = APIRequest(targetauth_api, data=api_vars) api.put() if api.response.status_code == 200: self.logger.debug("- target credentials updated") self.auth['username'] = username self.auth['password'] = password self.auth['mutual_username'] = mutual_username self.auth['mutual_password'] = mutual_password self.logger.info('ok') else: self.logger.error("Failed to update target auth: " "{}".format( response_message(api.response, self.logger))) return
def set_auth(self, chap=None, chap_mutual=None): self.logger.warn("chap={}, chap_mutual={}".format(chap, chap_mutual)) self.logger.debug("CMD: ../hosts/<client_iqn> auth *") if not chap: self.logger.error( "To set or reset authentication, specify either " "chap=<user>/<password> [chap_mutual]=<user>/<password> or nochap" ) return if chap == 'nochap': chap = '' else: # string could have been supplied as chap=user/password or # simply user/password - either way all we see is user/password if '/' not in chap: self.logger.error( "CHAP format is invalid - must be a <username>/<password> " "format. Use 'help auth' to show the correct syntax and " "supported characters") return if not chap_mutual: chap_mutual = '' else: if '/' not in chap_mutual: self.logger.error( "CHAP_MUTUAL format is invalid - must be a <username>/<password> " "format. Use 'help auth' to show the correct syntax and " "supported characters") return self.logger.debug( "auth to be set to chap='{}', chap_mutual='{}' for '{}'".format( chap, chap_mutual, self.client_iqn)) target_iqn = self.parent.parent.name api_vars = {"chap": chap, "chap_mutual": chap_mutual} clientauth_api = ('{}://localhost:{}/api/' 'clientauth/{}/{}'.format(self.http_mode, settings.config.api_port, target_iqn, self.client_iqn)) api = APIRequest(clientauth_api, data=api_vars) api.put() if api.response.status_code == 200: self.logger.debug("- client credentials updated") if chap != '': self.auth['chap'] = chap else: self.auth['chap'] = "None" if chap_mutual != '': self.auth['chap_mutual'] = chap_mutual else: self.auth['chap_mutual'] = "None" self.logger.info('ok') else: self.logger.error("Failed to update the client's auth" " :{}".format( response_message(api.response, self.logger))) return
def ui_command_delete(self, gateway_name, confirm=None): """ Delete a gateway from the group. This will stop and delete the target running on the gateway. If this is the last gateway the target is mapped to all objects added to it will be removed, and confirm=True is required. """ self.logger.debug("CMD: ../gateways/ delete {} confirm {}".format( gateway_name, confirm)) self.logger.info("Deleting gateway, {}".format(gateway_name)) confirm = self.ui_eval_param(confirm, 'bool', False) config = self.parent.parent.parent._get_config() if not config: self.logger.error("Unable to refresh local config over API - sync " "aborted, restart rbd-target-api on {0} to " "sync".format(gateway_name)) return target_iqn = self.parent.name gw_cnt = len(config['targets'][target_iqn]['portals']) if gw_cnt == 0: self.logger.error("Target is not mapped to any gateways.") return if gw_cnt == 1: if not confirm: self.logger.error("Deleting the last gateway will remove all " "objects on this target. Use confirm=true") return gw_api = '{}://{}:{}/api'.format(self.http_mode, "localhost", settings.config.api_port) gw_rqst = gw_api + '/gateway/{}/{}'.format(target_iqn, gateway_name) if confirm: gw_vars = {"force": 'true'} else: gw_vars = {"force": 'false'} api = APIRequest(gw_rqst, data=gw_vars) api.delete() msg = response_message(api.response, self.logger) if api.response.status_code != 200: if "unavailable:" + gateway_name in msg: self.logger.error( "Could not contact {}. If the gateway is " "permanently down. Use confirm=true to " "force removal. WARNING: Forcing removal of " "a gateway that can still be reached by an " "initiator may result in data corruption.".format( gateway_name)) else: self.logger.error("Failed : {}".format(msg)) return self.logger.debug("{}".format(msg)) self.logger.debug("Removing gw from UI") self.thread_lock.acquire() gw_object = self.get_child(gateway_name) self.remove_child(gw_object) self.thread_lock.release() config = self.parent.parent.parent._get_config() if not config: self.logger.error("Could not refresh display. Restart gwcli.") return elif not config['targets'][target_iqn]['portals']: # no more gws so everything but the target is dropped. disks_object = self.parent.get_child("disks") disks_object.reset() hosts_grp_object = self.parent.get_child("host-groups") hosts_grp_object.reset() hosts_object = self.parent.get_child("hosts") hosts_object.reset()