Exemplo n.º 1
0
    def ui_command_delete(self, group_name):
        """
        Delete a host group definition. The delete process will remove the group
        definition and remove the group name association within any client.

        Note the deletion of a group will not remove the lun masking already
        defined to clients. If this is desired, it will need to be performed
        manually once the group is deleted.
        """

        self.logger.debug("CMD: ../host-groups/ delete {}".format(group_name))

        if group_name not in self.groups:
            self.logger.error("Group '{}' does not exist".format(group_name))
            return

        target_iqn = self.parent.name

        # OK, so the group exists...
        group_api = ('{}://{}:{}/api/hostgroup/'
                     '{}/{}'.format(self.http_mode, "localhost",
                                    settings.config.api_port, target_iqn,
                                    group_name))

        api = APIRequest(group_api)
        api.delete()
        if api.response.status_code != 200:
            self.logger.error("failed to delete group '{}'".format(group_name))
            return

        self.logger.debug("removing group from the UI")
        child = [child for child in self.children
                 if child.name == group_name][0]
        self.delete(child)

        self.logger.info('ok')
Exemplo n.º 2
0
 def delete_disk(self, disk):
     rc = 0
     api_vars = {"disk": disk}
     targetdisk_api = ('{}://localhost:{}/api/'
                       'targetlun/{}'.format(self.http_mode,
                                             settings.config.api_port,
                                             self.target_iqn))
     api = APIRequest(targetdisk_api, data=api_vars)
     api.delete()
     if api.response.status_code == 200:
         target_disk = [target_disk for target_disk in self.children
                        if target_disk.name == disk][0]
         self.remove_child(target_disk)
         self.logger.debug("- TargetDisk '{}' deleted".format(disk))
         ui_root = self.get_ui_root()
         disk = ui_root.disks.disk_lookup[disk]
         disk.owner = ''
         self.logger.debug("- Disk '{}' owner deleted".format(disk))
         self.logger.info('ok')
     else:
         self.logger.error("Failed - {}".format(response_message(api.response,
                                                                 self.logger)))
         rc = 1
     return rc
Exemplo n.º 3
0
    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()
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
def all_client(client_iqn):
    """
    Handle the client create/delete actions across gateways
    :param client_iqn: (str) IQN of the client to create or delete
    **RESTRICTED**
    Examples:
    curl --insecure --user admin:admin -X PUT https://192.168.122.69:5001/api/all_client/iqn.1994-05.com.redhat:myhost4
    curl --insecure --user admin:admin -X DELETE https://192.168.122.69:5001/api/all_client/iqn.1994-05.com.redhat:myhost4
    """

    method = {"PUT": 'create', "DELETE": 'delete'}

    http_mode = 'https' if settings.config.api_secure else 'http'
    local_gw = this_host()
    logger.debug("this host is {}".format(local_gw))
    gateways = [
        key for key in config.config['gateways']
        if isinstance(config.config['gateways'][key], dict)
    ]
    logger.debug("other gateways - {}".format(gateways))
    gateways.remove(local_gw)

    # committing host is the node responsible for updating the config object
    api_vars = {"committing_host": local_gw}

    # validate the PUT/DELETE request first
    client_usable = valid_client(mode=method[request.method],
                                 client_iqn=client_iqn)
    if client_usable != 'ok':
        return jsonify(message=client_usable), 400

    if request.method == 'PUT':

        client_api = '{}://127.0.0.1:{}/api/client/{}'.format(
            http_mode, settings.config.api_port, client_iqn)

        logger.debug("Processing client CREATE for {}".format(client_iqn))
        api = APIRequest(client_api, data=api_vars)
        api.put()
        if api.response.status_code == 200:
            logger.info("Client {} added to local LIO".format(client_iqn))

            for gw in gateways:
                client_api = '{}://{}:{}/api/client/{}'.format(
                    http_mode, gw, settings.config.api_port, client_iqn)

                logger.debug("sending request to {} to create {}".format(
                    gw, client_iqn))
                api = APIRequest(client_api, data=api_vars)
                api.put()

                if api.response.status_code == 200:
                    logger.info("Client '{}' added to {}".format(
                        client_iqn, gw))
                    continue
                else:
                    # client create failed against the remote LIO instance
                    msg = api.response.json()['message']
                    logger.error("Client create for {} failed on {} "
                                 ": {}".format(client_iqn, gw, msg))

                    return jsonify(message=msg), 500

            # all gateways processed return a success state to the caller
            return jsonify(message='ok'), 200

        else:
            # client create failed against the local LIO instance
            msg = api.response.json()['message']
            logger.error("Client create on local LIO instance failed "
                         "for {} : {}".format(client_iqn, msg))
            return jsonify(message=msg), 500

    else:
        # DELETE client request
        # Process flow: remote gateways > local > delete config object entry
        for gw in gateways:
            client_api = '{}://{}:{}/api/client/{}'.format(
                http_mode, gw, settings.config.api_port, client_iqn)
            logger.info("- removing '{}' from {}".format(client_iqn, gw))
            api = APIRequest(client_api, data=api_vars)
            api.delete()

            if api.response.status_code == 200:
                logger.info("- '{}' removed".format(client_iqn))
                continue
            elif api.response.status_code == 400:
                logger.error("- '{}' is in use on {}".format(client_iqn, gw))
                return jsonify(message="Client in use"), 400
            else:
                msg = api.response.json()['message']
                logger.error("Failed to remove {} from {}".format(
                    client_iqn, gw))
                return jsonify(message="failed to remove client '{}' on "
                               "{}".format(client_iqn, msg)), 500

        # At this point the other gateways have removed the client, so
        # remove from the local LIO instance
        client_api = '{}://127.0.0.1:{}/api/client/{}'.format(
            http_mode, settings.config.api_port, client_iqn)
        api = APIRequest(client_api, data=api_vars)
        api.delete()

        if api.response.status_code == 200:
            logger.info("successfully removed '{}'".format(client_iqn))
            return jsonify(message="ok"), 200

        else:
            return jsonify(message="Unable to delete {} from local LIO "
                                   "instance".format(client_iqn)), \
                   api.response.status_code
Exemplo n.º 6
0
def all_disk(image_id):
    """
    Coordinate the create/delete of rbd images across the gateway nodes
    The "all_" method calls the corresponding disk api entrypoints across each
    gateway. Processing is done serially: creation is done locally first,
    then other gateways - whereas, rbd deletion is performed first against
    remote gateways and then the local machine is used to perform the actual
    rbd delete.

    :param image_id: (str) rbd image name of the format pool.image
    **RESTRICTED**
    """

    http_mode = 'https' if settings.config.api_secure else 'http'

    local_gw = this_host()
    logger.debug("this host is {}".format(local_gw))
    gateways = [
        key for key in config.config['gateways']
        if isinstance(config.config['gateways'][key], dict)
    ]
    logger.debug("other gateways - {}".format(gateways))
    gateways.remove(local_gw)
    logger.debug("other gw's {}".format(gateways))

    if request.method == 'PUT':

        pool = request.form.get('pool')
        size = request.form.get('size')
        mode = request.form.get('mode')

        pool, image_name = image_id.split('.')

        disk_usable = valid_disk(pool=pool,
                                 image=image_name,
                                 size=size,
                                 mode=mode)
        if disk_usable != 'ok':
            return jsonify(message=disk_usable), 400

        # make call to local api server first!
        disk_api = '{}://127.0.0.1:{}/api/disk/{}'.format(
            http_mode, settings.config.api_port, image_id)

        api_vars = {
            'pool': pool,
            'size': size,
            'owner': local_gw,
            'mode': mode
        }

        logger.debug("Issuing disk request to the local API "
                     "for {}".format(image_id))

        api = APIRequest(disk_api, data=api_vars)
        api.put()

        if api.response.status_code == 200:
            logger.info("LUN is ready on this host")

            for gw in gateways:
                logger.debug("Adding {} to gw {}".format(image_id, gw))
                disk_api = '{}://{}:{}/api/disk/{}'.format(
                    http_mode, gw, settings.config.api_port, image_id)
                api = APIRequest(disk_api, data=api_vars)
                api.put()

                if api.response.status_code == 200:
                    logger.info("LUN is ready on {}".format(gw))
                else:
                    return jsonify(message=api.response.json()['message']), 500

        else:
            logger.error(api.response.json()['message'])
            return jsonify(message=api.response.json()['message']), 500

        logger.info("LUN defined to all gateways for {}".format(image_id))

        return jsonify(message="ok"), 200

    else:
        # this is a DELETE request
        pool_name, image_name = image_id.split('.')
        disk_usable = valid_disk(mode='delete',
                                 pool=pool_name,
                                 image=image_name)

        if disk_usable != 'ok':
            return jsonify(message=disk_usable), 400

        api_vars = {'purge_host': local_gw}

        # process other gateways first
        for gw_name in gateways:
            disk_api = '{}://{}:{}/api/disk/{}'.format(
                http_mode, gw_name, settings.config.api_port, image_id)

            logger.debug("removing '{}' from {}".format(image_id, gw_name))

            api = APIRequest(disk_api, data=api_vars)
            api.delete()

            if api.response.status_code == 200:
                logger.debug("{} removed from {}".format(image_id, gw_name))

            elif api.response.status_code == 400:
                # 400 means the rbd is still allocated to a client
                msg = api.response.json()['message']
                logger.error(msg)
                return jsonify(message=msg), 400
            else:
                # delete failed - don't know why, pass the error to the
                # admin and abort
                msg = api.response.json()['message']
                return jsonify(message=msg), 500

        # 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(
            http_mode, settings.config.api_port, image_id)

        logger.debug("- removing '{}' from the local "
                     "machine, deleting the rbd".format(image_id))

        api = APIRequest(disk_api, data=api_vars)
        api.delete()

        if api.response.status_code == 200:
            logger.debug("- rbd {} deleted".format(image_id))
            return jsonify(message="ok"), 200
        else:
            return jsonify(message="failed to delete rbd "
                           "{}".format(image_id)), 500
Exemplo n.º 7
0
    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()
Exemplo n.º 8
0
    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