Exemple #1
0
    def summary(self):
        if not self.exists:
            return 'NOT FOUND', False

        status = True

        disk_api = ('{}://localhost:{}/api/'
                    'disk/{}'.format(self.http_mode, settings.config.api_port,
                                     self.image_id))

        self.logger.debug("disk GET status for {}".format(self.image_id))
        api = APIRequest(disk_api)
        api.get()

        state = "State unknown"
        if api.response.status_code == 200:
            info = api.response.json()
            disk_status = info.get("status")
            if disk_status:
                state = disk_status.get("state")
                if state != "Online":
                    status = False

        msg = [self.image_id, "({}, {})".format(state, self.size_h)]

        return " ".join(msg), status
Exemple #2
0
    def _apply_status(self):
        disk_api = ('{}://localhost:{}/api/'
                    'disk/{}'.format(self.http_mode,
                                     settings.config.api_port, self.image_id))
        self.logger.debug("disk GET status for {}".format(self.image_id))
        api = APIRequest(disk_api)
        api.get()

        # set both the 'lock_owner' and 'state' to Unknown as default in
        # case if the api response fails the gwcli command will fail too
        self.__setattr__('lock_owner', 'Unknown')
        self.__setattr__('state', 'Unknown')

        if api.response.status_code == 200:
            info = api.response.json()
            status = info.get("status")

            if status is None:
                return

            state = status.get('state')
            if (state):
                self.__setattr__('state', state)

            owner = status.get('lock_owner')
            if (owner):
                self.__setattr__('lock_owner', owner)
Exemple #3
0
    def _get_state(self):
        """
        Determine iSCSI and gateway API service state using the _ping api
        endpoint
        :return:
        """

        lookup = {
            200: {
                "status": "UP",
                "iscsi": "UP",
                "api": "UP"
            },
            401: {
                "status": "UNAUTHORIZED",
                "iscsi": "UNKNOWN",
                "api": "UP"
            },
            403: {
                "status": "UNAUTHORIZED",
                "iscsi": "UNKNOWN",
                "api": "UP"
            },
            500: {
                "status": "UNKNOWN",
                "iscsi": "UNKNOWN",
                "api": "UNKNOWN"
            },
            503: {
                "status": "PARTIAL",
                "iscsi": "DOWN",
                "api": "UP"
            },
            999: {
                "status": "UNKNOWN",
                "iscsi": "UNKNOWN",
                "api": "UNKNOWN"
            },
        }

        gw_api = '{}://{}:{}/api/_ping'.format(self.http_mode, self.name,
                                               settings.config.api_port)
        api = APIRequest(gw_api)
        try:
            api.get()
            rc = api.response.status_code
            if rc not in lookup:
                rc = 999
        except GatewayAPIError:
            rc = 999

        self.state = lookup[rc].get('status')
        self.service_state['iscsi'] = lookup[rc].get('iscsi')
        self.service_state['api'] = lookup[rc].get('api')
Exemple #4
0
    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']))
Exemple #5
0
    def ui_command_clearconfig(self, confirm=None):
        """
        The 'clearconfig' command allows you to return the configuration to an
        unused state: LIO on each gateway will be cleared, and gateway
        definitions in the configuration object will be removed.

        > clearconfig confirm=true

        In order to run the clearconfig command, all clients and disks *must*
        have already have been removed.
        """

        self.logger.debug("CMD: clearconfig confirm={}".format(confirm))

        confirm = self.ui_eval_param(confirm, 'bool', False)
        if not confirm:
            self.logger.error("To clear the configuration you must specify "
                              "confirm=true")
            return

        # get a new copy of the config dict over the local API
        # check that there aren't any disks or client listed
        local_api = ("{}://127.0.0.1:{}/api/"
                     "config".format(self.http_mode,
                                     settings.config.api_port))

        api = APIRequest(local_api)
        api.get()

        if api.response.status_code != 200:
            self.logger.error("Unable to get fresh copy of the configuration")
            raise GatewayAPIError

        try:
            current_config = api.response.json()
        except:
            self.logger.error("Malformed REST API response")
            raise GatewayAPIError

        num_clients = len(current_config['clients'].keys())
        num_disks = len(current_config['disks'].keys())

        if num_clients > 0 or num_disks > 0:
            self.logger.error("Clients({}) and Disks({}) must be removed first"
                              " before clearing the gateway "
                              "configuration".format(num_clients,
                                                     num_disks))
            return

        self.clear_config(current_config['gateways'])
Exemple #6
0
    def _get_config(self, endpoint=None):

        if not endpoint:
            endpoint = self.local_api

        api = APIRequest(endpoint + "/config")
        api.get()

        if api.response.status_code == 200:
            return api.response.json()
        else:
            self.error = True
            self.error_msg = "REST API failure, code : " \
                             "{}".format(api.response.status_code)
            return {}
Exemple #7
0
    def _get_config(self, endpoint=None):

        if not endpoint:
            endpoint = self.local_api

        api = APIRequest(endpoint + "/config")
        api.get()

        if api.response.status_code == 200:
            try:
                return api.response.json()
            except Exception:
                self.error = True
                self.logger.error("Malformed REST API response")
                return {}
        else:
            # 403 maybe due to the ip address is not in the iscsi
            # gateway trusted ip list
            self.error = True
            self.logger.error("REST API failure, code : "
                              "{}".format(api.response.status_code))
            return {}
Exemple #8
0
    def _get_config(self, endpoint=None):

        if not endpoint:
            endpoint = self.local_api

        api = APIRequest(endpoint + "/config")
        api.get()
        # response = get(self.local_api + "/config",
        #                auth=(settings.config.api_user, settings.config.api_password),
        #                verify=settings.config.api_ssl_verify)

        # except ConnectionError as e:
        #     self.error = True
        #     self.error_msg = "API unavailable @ {}".format(self.local_api)
        #     return {}

        if api.response.status_code == 200:
            return api.response.json()
        else:
            self.error = True
            self.error_msg = "REST API failure, code : " \
                             "{}".format(api.response.status_code)
            return {}
Exemple #9
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
Exemple #10
0
    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
Exemple #11
0
    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
Exemple #12
0
    def ui_command_create(self,
                          gateway_name,
                          ip_addresses,
                          nosync=False,
                          skipchecks='false'):
        """
        Define a gateway to the gateway group for this iscsi target. The
        first host added should be the gateway running the command

        gateway_name ... should resolve to the hostname of the gateway
        ip_addresses ... are the IPv4/IPv6 addresses of the interfaces the
                         iSCSI portals should use
        nosync ......... by default new gateways are sync'd with the
                         existing configuration by cli. By specifying nosync
                         the sync step is bypassed - so the new gateway
                         will need to have it's rbd-target-api daemon
                         restarted to apply the current configuration
                         (default = False)
        skipchecks ..... set this to true to force gateway validity checks
                         to be bypassed(default = false). This is a developer
                         option ONLY. Skipping these checks has the potential
                         to result in an unstable configuration.
        """

        ip_addresses = [
            normalize_ip_address(ip_address)
            for ip_address in ip_addresses.split(',')
        ]
        self.logger.debug("CMD: ../gateways/ create {} {} "
                          "nosync={} skipchecks={}".format(
                              gateway_name, ip_addresses, nosync, skipchecks))

        local_gw = this_host()
        current_gateways = [tgt.name for tgt in self.children]

        if gateway_name != local_gw and len(current_gateways) == 0:
            # the first gateway defined must be the local machine. By doing
            # this the initial create uses localhost, and places it's portal IP
            # in the gateway ip list. Once the gateway ip list is defined, the
            # api server can resolve against the gateways - until the list is
            # defined only a request from localhost is acceptable to the api
            self.logger.error("The first gateway defined must be the local "
                              "machine")
            return

        if skipchecks not in ['true', 'false']:
            self.logger.error("skipchecks must be either true or false")
            return

        if local_gw in current_gateways:
            current_gateways.remove(local_gw)

        config = self.parent.parent.parent._get_config()
        if not config:
            self.logger.error(
                "Unable to refresh local config"
                " over API - sync aborted, restart rbd-target-api"
                " on {} to sync".format(gateway_name))

        target_iqn = self.parent.name
        target_config = config['targets'][target_iqn]
        if nosync:
            sync_text = "sync skipped"
        else:
            sync_text = ("sync'ing {} disk(s) and "
                         "{} client(s)".format(len(target_config['disks']),
                                               len(target_config['clients'])))
        if skipchecks == 'true':
            self.logger.warning("OS version/package checks have been bypassed")

        self.logger.info("Adding gateway, {}".format(sync_text))

        gw_api = '{}://{}:{}/api'.format(self.http_mode, "localhost",
                                         settings.config.api_port)
        gw_rqst = gw_api + '/gateway/{}/{}'.format(target_iqn, gateway_name)
        gw_vars = {
            "nosync": nosync,
            "skipchecks": skipchecks,
            "ip_address": ','.join(ip_addresses)
        }

        api = APIRequest(gw_rqst, data=gw_vars)
        api.put()

        msg = response_message(api.response, self.logger)
        if api.response.status_code != 200:
            self.logger.error("Failed : {}".format(msg))
            return

        self.logger.debug("{}".format(msg))
        self.logger.debug("Adding gw to UI")

        # Target created OK, get the details back from the gateway and
        # add to the UI. We have to use the new gateway to ensure what
        # we get back is current (the other gateways will lag until they see
        # epoch xattr change on the config object)
        new_gw_endpoint = ('{}://{}:{}/'
                           'api'.format(self.http_mode, gateway_name,
                                        settings.config.api_port))

        api = APIRequest('{}/sysinfo/hostname'.format(new_gw_endpoint))
        api.get()
        gateway_hostname = api.response.json()['data']
        config = self.parent.parent.parent._get_config(
            endpoint=new_gw_endpoint)
        target_config = config['targets'][target_iqn]
        portal_config = target_config['portals'][gateway_hostname]
        Gateway(self, gateway_hostname, portal_config)

        self.logger.info('ok')
Exemple #13
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