def clear_config(self, gateway_group): # we need to process the gateways, leaving the local machine until # last to ensure we don't fall foul of the api auth check gw_list = [ gw_name for gw_name in gateway_group if isinstance(gateway_group[gw_name], dict) ] gw_list.remove(this_host()) gw_list.append(this_host()) for gw_name in gw_list: gw_api = ('{}://{}:{}/api/' '_gateway/{}'.format(self.http_mode, gw_name, settings.config.api_port, gw_name)) api = APIRequest(gw_api) api.delete() if api.response.status_code != 200: msg = api.response.json()['message'] self.logger.error("Delete of {} failed : {}".format( gw_name, msg)) raise GatewayAPIError else: self.logger.debug("- deleted {}".format(gw_name)) # gateways removed, so lets delete the objects from the UI tree self.reset() # remove any bookmarks stored in the prefs.bin file del self.shell.prefs['bookmarks'] self.logger.info('ok')
def reconfigure(self, attribute, value): allowed_attributes = ['max_data_area_mb'] if not attribute in allowed_attributes: self.logger.error("supported attributes: {}".format( ",".join(allowed_attributes))) return local_gw = this_host() # Issue the api request for reconfigure disk_api = ('{}://127.0.0.1:{}/api/' 'disk/{}'.format(self.http_mode, settings.config.api_port, self.image_id)) api_vars = { 'pool': self.pool, 'owner': local_gw, attribute: value, 'mode': 'reconfigure' } self.logger.debug("Issuing reconfigure request: attribute={}, " "value={}".format(attribute, value)) api = APIRequest(disk_api, data=api_vars) api.put() if api.response.status_code == 200: self.logger.info('ok') self._refresh_config() else: self.logger.error("Failed to reconfigure : " "{}".format( response_message(api.response, self.logger)))
def resize(self, size=None): """ Perform the resize operation, and sync the disk size across each of the gateways :param size: (int) new size for the rbd image :return: """ # resize is actually managed by the same lun and api endpoint as # create so this logic is very similar to a 'create' request # if not size: # self.logger.error("Specify a size value (current size " # "is {})".format(self.size_h)) # return # size_rqst = size.upper() # if not valid_size(size_rqst): # self.logger.error("Size parameter value is not valid syntax " # "(must be of the form 100G, or 1T)") # return # # new_size = convert_2_bytes(size_rqst) # if self.size >= new_size: # # current size is larger, so nothing to do # self.logger.error("New size isn't larger than the current " # "image size, ignoring request") # return # At this point the size request needs to be honoured self.logger.debug("Resizing {} to {}".format(self.image_id, size_rqst)) local_gw = this_host() # Issue the api request for the resize disk_api = '{}://127.0.0.1:{}/api/all_disk/{}'.format( self.http_mode, settings.config.api_port, self.image_id) api_vars = { 'pool': self.pool, 'size': size_rqst, 'owner': local_gw, 'mode': 'resize' } self.logger.debug("Issuing resize request") api = APIRequest(disk_api, data=api_vars) api.put() if api.response.status_code == 200: # at this point the resize request was successful, so we need to # update the ceph pool meta data (%commit etc) self._update_pool() self.size_h = size_rqst self.size = convert_2_bytes(size_rqst) self.logger.info('ok') else: self.logger.error("Failed to resize : " "{}".format(api.response.json()['message']))
def reconfigure(self, attribute, value): controls = {attribute: value} controls_json = json.dumps(controls) local_gw = this_host() # Issue the api request for reconfigure disk_api = ('{}://localhost:{}/api/' 'disk/{}'.format(self.http_mode, settings.config.api_port, self.image_id)) api_vars = { 'pool': self.pool, 'owner': local_gw, 'controls': controls_json, 'mode': 'reconfigure' } self.logger.debug("Issuing reconfigure request: attribute={}, " "value={}".format(attribute, value)) api = APIRequest(disk_api, data=api_vars) api.put() if api.response.status_code == 200: self.logger.info('ok') self._refresh_config() else: self.logger.error("Failed to reconfigure : " "{}".format( response_message(api.response, self.logger)))
def ui_command_delete(self, image_id): """ Delete a given rbd image from the configuration and ceph. This is a destructive action that could lead to data loss, so please ensure the rbd image name is correct! > delete <disk_name> e.g. > delete rbd.disk_1 "disk_name" refers to the name of the disk as shown in the UI, for example rbd.disk_1. Also note that the delete process is a synchronous task, so the larger the rbd image is, the longer the delete will take to run. """ # Perform a quick 'sniff' test on the request if image_id not in [disk.image_id for disk in self.children]: self.logger.error("Disk '{}' is not defined to the " "configuration".format(image_id)) return self.logger.debug("CMD: /disks delete {}".format(image_id)) self.logger.debug("Starting delete for rbd {}".format(image_id)) local_gw = this_host() # other_gateways = get_other_gateways(self.parent.target.children) api_vars = {'purge_host': local_gw} disk_api = '{}://{}:{}/api/disk/{}'.format(self.http_mode, local_gw, settings.config.api_port, image_id) api = APIRequest(disk_api, data=api_vars) api.delete() if api.response.status_code == 200: self.logger.debug("- rbd removed from all gateways, and deleted") disk_object = [ disk for disk in self.children if disk.name == image_id ][0] self.remove_child(disk_object) del self.disk_info[image_id] del self.disk_lookup[image_id] else: self.logger.debug("delete request failed - " "{}".format(api.response.status_code)) self.logger.error("{}".format( response_message(api.response, self.logger))) return ceph_pools = self.parent.ceph.local_ceph.pools ceph_pools.refresh() self.logger.info('ok')
def delete_disk(self, image_id, preserve_image): all_disks = [] for pool in self.children: for disk in pool.children: all_disks.append(disk) # Perform a quick 'sniff' test on the request if image_id not in [disk.image_id for disk in all_disks]: self.logger.error("Disk '{}' is not defined to the " "configuration".format(image_id)) return self.logger.debug("CMD: /disks delete {}".format(image_id)) self.logger.debug("Starting delete for rbd {}".format(image_id)) local_gw = this_host() api_vars = { 'purge_host': local_gw, 'preserve_image': 'true' if preserve_image else 'false' } disk_api = '{}://{}:{}/api/disk/{}'.format(self.http_mode, local_gw, settings.config.api_port, image_id) api = APIRequest(disk_api, data=api_vars) api.delete() if api.response.status_code == 200: self.logger.debug("- rbd removed from all gateways, and deleted") disk_object = [disk for disk in all_disks if disk.image_id == image_id][0] pool, _ = image_id.split('/') pool_object = [pool_object for pool_object in self.children if pool_object.name == pool][0] pool_object.remove_child(disk_object) if len(pool_object.children) == 0: self.remove_child(pool_object) del self.disk_info[image_id] del self.disk_lookup[image_id] else: self.logger.debug("delete request failed - " "{}".format(api.response.status_code)) self.logger.error("{}".format(response_message(api.response, self.logger))) return ceph_pools = self.parent.ceph.cluster.pools ceph_pools.refresh() self.logger.info('ok')
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 resize(self, size): """ Perform the resize operation, and sync the disk size across each of the gateways :param size: (int) new size for the rbd image :return: """ # resize is actually managed by the same lun and api endpoint as # create so this logic is very similar to a 'create' request size_rqst = size.upper() if not valid_size(size_rqst): self.logger.error("Size is invalid") return # At this point the size request needs to be honoured self.logger.debug("Resizing {} to {}".format(self.image_id, size_rqst)) local_gw = this_host() # Issue the api request for the resize disk_api = ('{}://localhost:{}/api/' 'disk/{}'.format(self.http_mode, settings.config.api_port, self.image_id)) api_vars = { 'pool': self.pool, 'size': size_rqst, 'owner': local_gw, 'mode': 'resize' } self.logger.debug("Issuing resize request") api = APIRequest(disk_api, data=api_vars) api.put() if api.response.status_code == 200: # at this point the resize request was successful, so we need to # update the ceph pool meta data (%commit etc) self._update_pool() self.size_h = size_rqst self.size = convert_2_bytes(size_rqst) self.logger.info('ok') else: self.logger.error("Failed to resize : " "{}".format( response_message(api.response, self.logger)))
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 export_ansible(self, config): this_gw = this_host() ansible_vars = [] ansible_vars.append("seed_monitor: {}".format( self.ceph.local_ceph.healthy_mon)) ansible_vars.append("cluster_name: {}".format( settings.config.cluster_name)) ansible_vars.append("gateway_keyring: {}".format( settings.config.gateway_keyring)) ansible_vars.append("deploy_settings: true") ansible_vars.append("perform_system_checks: true") ansible_vars.append('gateway_iqn: "{}"'.format( config['gateways']['iqn'])) ansible_vars.append('gateway_ip_list: "{}"'.format(",".join( config['gateways']['ip_list']))) ansible_vars.append("# rbd device definitions") ansible_vars.append("rbd_devices:") disk_template = (" - {{ pool: '{}', image: '{}', size: '{}', " "host: '{}', state: 'present' }}") for disk in self.disks.children: ansible_vars.append( disk_template.format(disk.pool, disk.image, disk.size_h, this_gw)) ansible_vars.append("# client connections") ansible_vars.append("client_connections:") client_template = (" - {{ client: '{}', image_list: '{}', " "chap: '{}', status: 'present' }}") for client in sorted(config['clients'].keys()): client_metadata = config['clients'][client] lun_data = client_metadata['luns'] sorted_luns = [ s[0] for s in sorted(lun_data.iteritems(), key=lambda (x, y): y['lun_id']) ] chap = CHAP(client_metadata['auth']['chap']) ansible_vars.append( client_template.format(client, ','.join(sorted_luns), chap.chap_str)) for var in ansible_vars: print(var)
def ui_command_delete(self, image_id): """ Delete a given rbd image from the configuration and ceph. This is a destructive action that could lead to data loss, so please ensure the rbd image name is correct! > delete <rbd_image_name> Also note that the delete process is a synchronous task, so the larger the rbd image is, the longer the delete will take to run. """ self.logger.debug("CMD: /disks delete {}".format(image_id)) self.logger.debug("Starting delete for rbd {}".format(image_id)) local_gw = this_host() # other_gateways = get_other_gateways(self.parent.target.children) api_vars = {'purge_host': local_gw} disk_api = '{}://{}:{}/api/all_disk/{}'.format( self.http_mode, local_gw, settings.config.api_port, image_id) api = APIRequest(disk_api, data=api_vars) api.delete() if api.response.status_code == 200: self.logger.debug("- rbd removed from all gateways, and deleted") disk_object = [ disk for disk in self.children if disk.name == image_id ][0] self.remove_child(disk_object) del self.disk_info[image_id] del self.disk_lookup[image_id] else: raise GatewayLIOError("Failed to remove the device from the " "local machine") ceph_pools = self.parent.ceph.local_ceph.pools ceph_pools.refresh() self.logger.info('ok')
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_delete(self, client_iqn): """ You may delete a client from the configuration, but you must ensure that the client has logged out of the iscsi gateways. Attempting to delete a client that has an open session will fail the request > delete <client_iqn> """ # check the iqn given matches one of the child objects - i.e. it's valid client_names = [child.name for child in self.children] if client_iqn not in client_names: self.logger.error( "Host with an iqn of '{}' is not defined...mis-typed?".format( client_iqn)) return 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: self.logger.error( "Host '{}' is logged in - unable to delete until it's logged out" .format(client_iqn)) return # At this point we know the client requested is defined to the configuration # and is not currently logged in (at least to this host), OK to delete self.logger.debug("Client DELETE for {}".format(client_iqn)) client = [ client for client in self.children if client.name == client_iqn ][0] # Process flow: remote gateways > local > delete config object entry other_gateways = get_other_gateways( self.parent.parent.parent.target.children) api_vars = {"committing_host": this_host()} for gw in other_gateways: client_api = '{}://{}:{}/api/client/{}'.format( self.http_mode, gw, settings.config.api_port, client_iqn) response = delete(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("- '{}' removed from {}".format( client_iqn, gw)) continue elif response.status_code == 400: self.logger.critical("- '{}' is in use on {}".format( client_iqn, gw)) return else: raise GatewayAPIError(response.text) # At this point the other gateways have removed the client, so # remove from the local instance and delete from the interface client_api = '{}://127.0.0.1:{}/api/client/{}'.format( self.http_mode, settings.config.api_port, client_iqn) response = delete(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( "- '{}' removed from local gateway, configuration updated". format(client_iqn)) self.delete(client) self.logger.info('ok')
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_auth(self, chap=None): """ Client authentication can be set to use CHAP by supplying the a string of the form <username>/<password> > auth chap=myserver/mypassword2016 username ... The username is freeform, but would normally be the hostname or iqn password ... the password must be between 12-16 chars in length containing alphanumeric characters plus the following special characters !,&,_ """ if not chap: self.logger.error( "To set CHAP authentication provide a string of the format 'user/password'" ) return else: # validate the chap credentials are acceptable if not Client.valid_credentials(chap, auth_type='chap'): self.logger.error( "-> the format of the CHAP string is invalid, use 'help auth' for examples" ) return self.logger.debug("Client '{}' AUTH update : {}".format( self.client_iqn, chap)) # get list of children (luns) to build current image list image_list = ','.join(self._get_lun_names()) other_gateways = get_other_gateways( self.parent.parent.parent.parent.target.children) api_vars = { "committing_host": this_host(), "image_list": image_list, "chap": chap } clientauth_api = '{}://127.0.0.1:{}/api/clientauth/{}'.format( self.http_mode, settings.config.api_port, self.client_iqn) response = put(clientauth_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("- Local environment updated") self.auth['chap'] = chap for gw in other_gateways: clientauth_api = '{}://{}:{}/api/clientauth/{}'.format( self.http_mode, gw, settings.config.api_port, self.client_iqn) response = put(clientauth_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("- {} updated".format(gw)) continue else: raise GatewayAPIError(response.text) else: raise GatewayAPIError(response.text) self.logger.info('ok')
def ui_command_disk(self, action='add', disk=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 """ valid_actions = ['add', 'remove'] current_luns = self._get_lun_names() if action == 'add': valid_disk_names = [ defined_disk.image_id for defined_disk in self.parent.parent.parent.parent.disks.children ] else: valid_disk_names = current_luns if not disk: self.logger.critical( "You must supply a disk name to add/remove from this client") return if action not in valid_actions: self.logger.error( "you can only add and remove disks - {} is invalid ".format( action)) return if disk not in valid_disk_names: self.logger.critical( "the request to {} disk '{}' is invalid".format(action, 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)) if action == 'add': current_luns.append(disk) else: current_luns.remove(disk) image_list = ','.join(current_luns) other_gateways = get_other_gateways( self.parent.parent.parent.parent.target.children) api_vars = { "committing_host": this_host(), "image_list": image_list, "chap": self.auth['chap'] } clientlun_api = '{}://127.0.0.1:{}/api/clientlun/{}'.format( self.http_mode, settings.config.api_port, self.client_iqn) response = put(clientlun_api, data=api_vars, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: 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} response = get(clientlun_api, data=get_api_vars, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: lun_dict = json.loads(response.text)['message'] lun_id = lun_dict[disk]['lun_id'] MappedLun(self, disk, lun_id) else: raise GatewayAPIError(response.text) 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) self.logger.debug("- local environment updated") for gw in other_gateways: clientlun_api = '{}://{}:{}/api/clientlun/{}'.format( self.http_mode, gw, settings.config.api_port, self.client_iqn) response = put(clientlun_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("- gateway '{}' updated".format(gw)) continue else: raise GatewayAPIError(response.text) else: raise GatewayAPIError(response.text) self.logger.info('ok')
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')
def ui_command_delete(self, image_id): """ Delete a given rbd image from the configuration and ceph. This is a destructive action that could lead to data loss, so please ensure the rbd image is correct! > delete <rbd_image_name> Also note that the delete process is a synchronous task, so the larger the rbd image is, the longer the delete will take to run. """ # 1st does the image id given exist? rbd_list = [disk.name for disk in self.children] if image_id not in rbd_list: self.logger.error( "- the disk '{}' does not exist in this configuration".format( image_id)) return # Although the LUN class will check that the lun is unallocated before attempting # a delete, it seems cleaner and more responsive to check through the object model # here before sending a delete request disk_users = self.disk_in_use(image_id) if disk_users: self.logger.error( "- Unable to delete '{}', it is currently allocated to:". format(image_id)) # error_str = "- Unable to delete '{}', it is currently allocated to:\n".format(image_id) for client in disk_users: self.logger.error(" - {}".format(client)) return self.logger.debug("Deleting rbd {}".format(image_id)) local_gw = this_host() other_gateways = get_other_gateways(self.parent.target.children) api_vars = {'purge_host': local_gw} # process other gateways first for gw_name in other_gateways: disk_api = '{}://{}:{}/api/disk/{}'.format( self.http_mode, gw_name, settings.config.api_port, image_id) self.logger.debug("- removing '{}' from {}".format( image_id, gw_name)) response = delete(disk_api, data=api_vars, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: pass elif response.status_code == 400: # 400 means the rbd is still allocated to a client msg = json.loads(response.text)['message'] self.logger.error(msg) return else: # delete failed - don't know why, pass the error to the # admin and abort raise GatewayAPIError(response.text) # at this point the remote gateways are cleaned up, now perform the # purge on the local host which will also purge the rbd disk_api = '{}://127.0.0.1:{}/api/disk/{}'.format( self.http_mode, settings.config.api_port, image_id) self.logger.debug( "- removing '{}' from the local machine".format(image_id)) response = delete(disk_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("- rbd removed") disk_object = [ disk for disk in self.children if disk.name == image_id ][0] self.remove_child(disk_object) else: raise GatewayLIOError( "--> Failed to remove the device from the local machine") ceph_pools = self.parent.ceph.pools ceph_pools.refresh() self.logger.info('ok')
def ui_command_resize(self, size=None): """ The resize command allows you to increase the size of an existing rbd image. Attempting to decrease the size of an rbd will be ignored. size: new size including unit suffix e.g. 300G """ # resize is actually managed by the same lun and api endpoint as # create so this logic is very similar to a 'create' request if not size: self.logger.error( "Specify a size value (current size is {})".format( self.size_h)) return size_rqst = size.upper() if not valid_size(size_rqst): self.logger.error( "Size parameter value is not valid syntax (must be of the form 100G, or 1T)" ) return new_size = convert_2_bytes(size_rqst) if self.size >= new_size: # current size is larger, so nothing to do self.logger.error( "New size isn't larger than the current image size, ignoring request" ) return # At this point the size request needs to be honoured self.logger.debug("Resizing {} to {}".format(self.image_id, size_rqst)) local_gw = this_host() other_gateways = get_other_gateways(self.parent.parent.target.children) # make call to local api server first! disk_api = '{}://127.0.0.1:{}/api/disk/{}'.format( self.http_mode, settings.config.api_port, self.image_id) api_vars = { 'pool': self.pool, 'size': size_rqst, 'owner': local_gw, 'mode': 'resize' } self.logger.debug("Processing local LIO instance") response = put(disk_api, data=api_vars, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: # rbd resize request successful, so update the local information self.logger.debug("- LUN resize complete") self.get_meta_data() self.logger.debug("Processing other gateways") for gw in other_gateways: disk_api = '{}://{}:{}/api/disk/{}'.format( self.http_mode, gw, settings.config.api_port, self.image_id) response = put(disk_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( "- LUN resize registered on {}".format(gw)) else: raise GatewayAPIError(response.text) else: raise GatewayAPIError(response.text) self.logger.info('ok')
def ui_command_create(self, pool=None, image=None, size=None): """ Create a LUN and assign to the gateway. The create process needs the pool name, rbd image name and the size parameter. 'size' should be a numeric suffixed by either M, G or T (representing the allocation unit) """ # NB the text above is shown on a help create request in the CLI if not self._valid_request(pool, image, size): return # get pool, image, and size ; use this host as the creator local_gw = this_host() disk_key = "{}.{}".format(pool, image) other_gateways = get_other_gateways(self.parent.target.children) if len(other_gateways) < 1: self.logger.error( "At least 2 gateways must be defined before disks can be added" ) return self.logger.debug("Creating/mapping disk {}/{}".format(pool, image)) # make call to local api server first! 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, 'mode': 'create' } self.logger.debug("Processing local LIO instance") response = put(disk_api, data=api_vars, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: # rbd create and map successful, so request it's details and add # to the gwcli self.logger.debug("- LUN is ready on local") response = get(disk_api, auth=(settings.config.api_user, settings.config.api_password), verify=settings.config.api_ssl_verify) if response.status_code == 200: image_config = response.json() Disk(self, disk_key, image_config) self.logger.debug("Processing other gateways") for gw in other_gateways: disk_api = '{}://{}:{}/api/disk/{}'.format( self.http_mode, gw, settings.config.api_port, disk_key) response = put(disk_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("- LUN is ready on {}".format(gw)) else: raise GatewayAPIError(response.text) else: raise GatewayLIOError( "- Error defining the rbd image to the local gateway") ceph_pools = self.parent.ceph.pools ceph_pools.refresh() self.logger.info('ok')
def ui_command_create(self, gateway_name, ip_address, nosync=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 IP v4 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 """ self.logger.debug("CMD: ../gateways/ create {} {} " "nosync={}".format(gateway_name, ip_address, nosync)) 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 127.0.0.1, 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 127.0.0.1 is acceptable to the api self.logger.error("The first gateway defined must be the local " "machine") 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)) if nosync: sync_text = "sync skipped" else: sync_text = ("sync'ing {} disk(s) and " "{} client(s)".format(len(config['disks']), len(config['clients']))) self.logger.info("Adding gateway, {}".format(sync_text)) gw_api = '{}://{}:{}/api'.format(self.http_mode, "127.0.0.1", settings.config.api_port) gw_rqst = gw_api + '/gateway/{}'.format(gateway_name) gw_vars = {"nosync": nosync, "ip_address": ip_address} api = APIRequest(gw_rqst, data=gw_vars) api.put() if api.response.status_code != 200: msg = api.response.json()['message'] self.logger.error("Failed : {}".format(msg)) return self.logger.debug("{}".format(api.response.json()['message'])) 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) gw_config = config['gateways'][gateway_name] Gateway(self, gateway_name, gw_config) self.logger.info('ok')
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