Example #1
0
    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')
Example #2
0
    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)))
Example #3
0
    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']))
Example #4
0
    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)))
Example #5
0
    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')
Example #6
0
    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')
Example #7
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']))
Example #8
0
    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)))
Example #9
0
 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 ''
Example #10
0
    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)
Example #11
0
    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')
Example #12
0
    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')
Example #13
0
    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')
Example #14
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
Example #15
0
    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')
Example #16
0
    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')
Example #17
0
    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')
Example #18
0
    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')
Example #19
0
    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')
Example #20
0
    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')
Example #21
0
    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')
Example #22
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