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_create(self, target_iqn): """ Create an iSCSI target. This target is defined across all gateway nodes, providing the client with a single 'image' for iscsi discovery. Only ONE iSCSI target is supported, at this time. """ self.logger.debug("CMD: /iscsi create {}".format(target_iqn)) defined_targets = [tgt.name for tgt in self.children] if len(defined_targets) > 0: self.logger.error("Only ONE iscsi target image is supported") return # We need LIO to be empty, so check there aren't any targets defined local_lio = root.RTSRoot() current_target_names = [tgt.wwn for tgt in local_lio.targets] if current_target_names: self.logger.error("Local LIO instance already has LIO configured " "with a target - unable to continue") return # OK - this request is valid, but is the IQN usable? if not valid_iqn(target_iqn): self.logger.error("IQN name '{}' is not valid for " "iSCSI".format(target_iqn)) return # 'safe' to continue with the definition self.logger.debug("Create an iscsi target definition in the UI") local_api = ('{}://127.0.0.1:{}/api/' 'target/{}'.format(self.http_mode, settings.config.api_port, target_iqn)) api = APIRequest(local_api) api.put() if api.response.status_code == 200: self.logger.info('ok') # create the target entry in the UI tree Target(target_iqn, self) else: self.logger.error("Failed to create the target on the local node") raise GatewayAPIError("iSCSI target creation failed - " "{}".format(response_message(api.response, self.logger)))
def ui_command_create(self, target_iqn): """ Create an iSCSI target. This target is defined across all gateway nodes, providing the client with a single 'image' for iscsi discovery. """ self.logger.debug("CMD: /iscsi create {}".format(target_iqn)) # is the IQN usable? try: target_iqn, iqn_type = normalize_wwn(['iqn'], target_iqn) except RTSLibError: self.logger.error("IQN name '{}' is not valid for " "iSCSI".format(target_iqn)) return # 'safe' to continue with the definition self.logger.debug("Create an iscsi target definition in the UI") local_api = ('{}://localhost:{}/api/' 'target/{}'.format(self.http_mode, settings.config.api_port, target_iqn)) api = APIRequest(local_api) api.put() if api.response.status_code == 200: self.logger.info('ok') # create the target entry in the UI tree target_exists = len([ target for target in self.children if target.name == target_iqn ]) > 0 if not target_exists: Target(target_iqn, self) else: self.logger.error("Failed to create the target on the local node") raise GatewayAPIError("iSCSI target creation failed - " "{}".format( response_message(api.response, self.logger)))
def 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, 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_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_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_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_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_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_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')