def summary(self): if not self.exists: return 'NOT FOUND', False status = True disk_api = ('{}://localhost:{}/api/' 'disk/{}'.format(self.http_mode, settings.config.api_port, self.image_id)) self.logger.debug("disk GET status for {}".format(self.image_id)) api = APIRequest(disk_api) api.get() state = "State unknown" if api.response.status_code == 200: info = api.response.json() disk_status = info.get("status") if disk_status: state = disk_status.get("state") if state != "Online": status = False msg = [self.image_id, "({}, {})".format(state, self.size_h)] return " ".join(msg), status
def _apply_status(self): disk_api = ('{}://localhost:{}/api/' 'disk/{}'.format(self.http_mode, settings.config.api_port, self.image_id)) self.logger.debug("disk GET status for {}".format(self.image_id)) api = APIRequest(disk_api) api.get() # set both the 'lock_owner' and 'state' to Unknown as default in # case if the api response fails the gwcli command will fail too self.__setattr__('lock_owner', 'Unknown') self.__setattr__('state', 'Unknown') if api.response.status_code == 200: info = api.response.json() status = info.get("status") if status is None: return state = status.get('state') if (state): self.__setattr__('state', state) owner = status.get('lock_owner') if (owner): self.__setattr__('lock_owner', owner)
def _get_state(self): """ Determine iSCSI and gateway API service state using the _ping api endpoint :return: """ lookup = { 200: { "status": "UP", "iscsi": "UP", "api": "UP" }, 401: { "status": "UNAUTHORIZED", "iscsi": "UNKNOWN", "api": "UP" }, 403: { "status": "UNAUTHORIZED", "iscsi": "UNKNOWN", "api": "UP" }, 500: { "status": "UNKNOWN", "iscsi": "UNKNOWN", "api": "UNKNOWN" }, 503: { "status": "PARTIAL", "iscsi": "DOWN", "api": "UP" }, 999: { "status": "UNKNOWN", "iscsi": "UNKNOWN", "api": "UNKNOWN" }, } gw_api = '{}://{}:{}/api/_ping'.format(self.http_mode, self.name, settings.config.api_port) api = APIRequest(gw_api) try: api.get() rc = api.response.status_code if rc not in lookup: rc = 999 except GatewayAPIError: rc = 999 self.state = lookup[rc].get('status') self.service_state['iscsi'] = lookup[rc].get('iscsi') self.service_state['api'] = lookup[rc].get('api')
def create_disk(self, pool=None, image=None, size=None, parent=None): 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 all_ method disk_api = '{}://127.0.0.1:{}/api/all_disk/{}'.format( self.http_mode, settings.config.api_port, disk_key) api_vars = { 'pool': pool, 'size': size.upper(), 'owner': local_gw, '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 is ready on all gateways") ceph_pools = self.parent.ceph.local_ceph.pools ceph_pools.refresh() self.logger.debug("Updating UI for the new disk") disk_api = disk_api.replace('/all_disk/', '/disk/') api = APIRequest(disk_api) api.get() if api.response.status_code == 200: image_config = api.response.json() Disk(parent, disk_key, image_config) self.logger.info('ok') else: raise GatewayAPIError( "Unable to retrieve disk details from the API") else: self.logger.error("Failed : {}".format( api.response.json()['message']))
def ui_command_clearconfig(self, confirm=None): """ The 'clearconfig' command allows you to return the configuration to an unused state: LIO on each gateway will be cleared, and gateway definitions in the configuration object will be removed. > clearconfig confirm=true In order to run the clearconfig command, all clients and disks *must* have already have been removed. """ self.logger.debug("CMD: clearconfig confirm={}".format(confirm)) confirm = self.ui_eval_param(confirm, 'bool', False) if not confirm: self.logger.error("To clear the configuration you must specify " "confirm=true") return # get a new copy of the config dict over the local API # check that there aren't any disks or client listed local_api = ("{}://127.0.0.1:{}/api/" "config".format(self.http_mode, settings.config.api_port)) api = APIRequest(local_api) api.get() if api.response.status_code != 200: self.logger.error("Unable to get fresh copy of the configuration") raise GatewayAPIError try: current_config = api.response.json() except: self.logger.error("Malformed REST API response") raise GatewayAPIError num_clients = len(current_config['clients'].keys()) num_disks = len(current_config['disks'].keys()) if num_clients > 0 or num_disks > 0: self.logger.error("Clients({}) and Disks({}) must be removed first" " before clearing the gateway " "configuration".format(num_clients, num_disks)) return self.clear_config(current_config['gateways'])
def _get_config(self, endpoint=None): if not endpoint: endpoint = self.local_api api = APIRequest(endpoint + "/config") api.get() if api.response.status_code == 200: return api.response.json() else: self.error = True self.error_msg = "REST API failure, code : " \ "{}".format(api.response.status_code) return {}
def _get_config(self, endpoint=None): if not endpoint: endpoint = self.local_api api = APIRequest(endpoint + "/config") api.get() if api.response.status_code == 200: try: return api.response.json() except Exception: self.error = True self.logger.error("Malformed REST API response") return {} else: # 403 maybe due to the ip address is not in the iscsi # gateway trusted ip list self.error = True self.logger.error("REST API failure, code : " "{}".format(api.response.status_code)) return {}
def _get_config(self, endpoint=None): if not endpoint: endpoint = self.local_api api = APIRequest(endpoint + "/config") api.get() # response = get(self.local_api + "/config", # auth=(settings.config.api_user, settings.config.api_password), # verify=settings.config.api_ssl_verify) # except ConnectionError as e: # self.error = True # self.error_msg = "API unavailable @ {}".format(self.local_api) # return {} if api.response.status_code == 200: return api.response.json() else: self.error = True self.error_msg = "REST API failure, code : " \ "{}".format(api.response.status_code) return {}
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 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 create_disk(self, pool=None, image=None, size=None, count=1, 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, '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") ceph_pools = self.parent.ceph.local_ceph.pools ceph_pools.refresh() 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: image_config = api.response.json() 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)) else: self.logger.error("Failed : {}".format( api.response.json()['message'])) rc = 8 return rc
def ui_command_create(self, gateway_name, ip_addresses, 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_addresses ... are the IPv4/IPv6 addresses of the interfaces the iSCSI portals 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-api 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_addresses = [ normalize_ip_address(ip_address) for ip_address in ip_addresses.split(',') ] self.logger.debug("CMD: ../gateways/ create {} {} " "nosync={} skipchecks={}".format( gateway_name, ip_addresses, 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-api" " 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": ','.join(ip_addresses) } 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)) api = APIRequest('{}/sysinfo/hostname'.format(new_gw_endpoint)) api.get() gateway_hostname = api.response.json()['data'] config = self.parent.parent.parent._get_config( endpoint=new_gw_endpoint) target_config = config['targets'][target_iqn] portal_config = target_config['portals'][gateway_hostname] Gateway(self, gateway_hostname, portal_config) self.logger.info('ok')
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 the disk MUST already be defined within the configuration > disk add|remove <disk_name> Adding a disk will result in the disk occupying the client's next available lun id. Removing a disk will preserve existing lun id allocations """ 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 pool, image = disk.split('.') 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} # if action == 'add': # current_luns.append(disk) # else: # current_luns.remove(disk) # # image_list = ','.join(current_luns) # # api_vars = {"image_list": image_list, # "chap": self.auth.get('chap', '')} clientlun_api = '{}://127.0.0.1:{}/api/all_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('/all_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: lun_dict = api.response.json()['message'] lun_id = lun_dict[disk]['lun_id'] MappedLun(self, disk, lun_id) # update the objects lun list (so ui info cmd picks # up the change self.luns[disk] = {'lun_id': lun_id} self.parent.update_lun_map('add', disk, self.client_iqn) active_maps = len(self.parent.lun_map[disk]) - 1 if active_maps > 0: self.logger.warning("Warning: '{}' mapped to {} other " "client(s)".format(disk, active_maps)) 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_child(mlun) del self.luns[disk] self.parent.update_lun_map('remove', disk, self.client_iqn) 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, api.response.json()['message'])) return