Ejemplo n.º 1
0
def valid_iqn(iqn):
    """
    confirm whether the given iqn is in an acceptable format
    :param iqn: (str) iqn name to check
    :return: (bool) True if iqn is valid for iSCSI
    """

    try:
        normalize_wwn(['iqn'], iqn)
    except RTSLibError:
        return False

    return True
Ejemplo n.º 2
0
    def ui_command_delete(self, target_iqn):
        """
        Delete an iSCSI target.
        """

        self.logger.debug("CMD: /iscsi delete {}".format(target_iqn))

        try:
            target_iqn, iqn_type = normalize_wwn(['iqn'], target_iqn)
        except RTSLibError:
            self.logger.error("IQN name '{}' is not valid for "
                              "iSCSI".format(target_iqn))
            return

        gw_api = ('{}://localhost:{}/api/'
                  'target/{}'.format(self.http_mode,
                                     settings.config.api_port,
                                     target_iqn))

        api = APIRequest(gw_api)
        api.delete()

        if api.response.status_code == 200:
            self.logger.info('ok')
            # delete the target entry from the UI tree
            target_object = [target for target in self.children
                             if target.name == target_iqn][0]
            self.remove_child(target_object)
        else:
            self.logger.error("Failed - {}".format(response_message(api.response, self.logger)))
Ejemplo n.º 3
0
    def __init__(self, logger, client_iqn, image_list, chap):
        """
        Instantiate an instance of an LIO client
        :param client_iqn: iscsi iqn string
        :param image_list: list of rbd images (pool/image) to attach to this client
        :param chap: chap credentials in the format 'user/password'
        :return:
        """

        self.iqn = client_iqn
        self.requested_images = image_list  # images are in comma separated pool.image_name format

        self.chap = chap  # parameters for auth
        self.mutual = ''
        self.tpgauth = ''
        self.metadata = {}
        self.acl = None
        self.client_luns = {}
        self.tpg = None
        self.tpg_luns = {}
        self.lun_id_list = range(256)  # available LUN ids 0..255
        self.change_count = 0
        self.commit_enabled = True  # enable commit to the config for changes by default
        self.logger = logger
        self.current_config = {}

        try:
            valid_iqn = normalize_wwn(['iqn'], client_iqn)
        except RTSLibError as err:
            self.error = True
            self.error_msg = "Invalid client name for iSCSI - {}".format(err)
        else:
            self.error = False
            self.error_msg = ''
Ejemplo n.º 4
0
def manage_client(client_iqn):
    """
    Manage a client definition to the local gateway
    Internal Use ONLY
    :param client_iqn: iscsi name for the client
    **RESTRICTED**
    """

    if request.method == 'GET':

        if client_iqn in config.config['clients']:
            return jsonify(config.config["clients"][client_iqn]), 200
        else:
            return jsonify(message="Client does not exist"), 404

    elif request.method == 'PUT':

        try:
            valid_iqn = normalize_wwn(['iqn'], client_iqn)
        except RTSLibError:
            return jsonify(message="'{}' is not a valid name for "
                                   "iSCSI".format(client_iqn)), 400

        committing_host = request.form['committing_host']

        image_list = request.form.get('image_list', '')

        chap = request.form.get('chap', '')

        status_code, status_text = _update_client(client_iqn=client_iqn,
                                                  images=image_list,
                                                  chap=chap,
                                                  committing_host=committing_host)

        logger.debug("client create: {}".format(status_code))
        logger.debug("client create: {}".format(status_text))
        return jsonify(message=status_text), status_code

    else:
        # DELETE request
        committing_host = request.form['committing_host']

        # Make sure the delete request is for a client we have defined
        if client_iqn in config.config['clients'].keys():
            client = GWClient(logger, client_iqn, '', '')
            client.manage('absent', committer=committing_host)

            if client.error:
                logger.error("Failed to remove client : "
                             "{}".format(client.error_msg))
                return jsonify(message="Failed to remove client"), 500

            else:
                if committing_host == this_host():
                    config.refresh()

                return jsonify(message="Client deleted ok"), 200
        else:
            logger.error("Delete request for non existent client!")
            return jsonify(message="Client does not exist!"), 404
Ejemplo n.º 5
0
    def ui_command_delete(self, client_iqn):
        """
        You may delete a client from the configuration, but you must ensure
        the client has logged out of the iscsi gateways. Attempting to delete a
        client that has an open session will fail the request

        e.g.
        delete <client_iqn>

        """

        self.logger.debug("CMD: ../hosts/ delete {}".format(client_iqn))

        self.logger.debug("Client DELETE for {}".format(client_iqn))

        # is the IQN usable?
        try:
            client_iqn, iqn_type = normalize_wwn(['iqn'], client_iqn)
        except RTSLibError:
            self.logger.error("IQN name '{}' is not valid for "
                              "iSCSI".format(client_iqn))
            return

        target_iqn = self.parent.name

        client_api = ('{}://{}:{}/api/'
                      'client/{}/{}'.format(self.http_mode,
                                            "localhost",
                                            settings.config.api_port,
                                            target_iqn,
                                            client_iqn))
        api = APIRequest(client_api)
        api.delete()

        if api.response.status_code == 200:
            # Delete successful across all gateways
            self.logger.debug("- '{}' removed and configuration "
                              "updated".format(client_iqn))

            client = [client for client in self.children
                      if client.name == client_iqn][0]

            # remove any rbd maps from the lun_map for this client
            rbds_mapped = [lun.rbd_name for lun in client.children]
            for rbd in rbds_mapped:
                self.update_lun_map('remove', rbd, client_iqn)

            self.delete(client)

            self.logger.info('ok')
        else:
            # client delete request failed
            self.logger.error(response_message(api.response, self.logger))
Ejemplo n.º 6
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. Once a client is created the admin is automatically placed in the
        context of the new client definition for auth and disk configuration
        operations.

        e.g.
        > create <client_iqn>

        """
        self.logger.debug("CMD: ../hosts/ create {}".format(client_iqn))
        cli_seed = {"luns": {}, "auth": {}}

        # is the IQN usable?
        try:
            client_iqn, iqn_type = normalize_wwn(['iqn'], client_iqn)
        except RTSLibError:
            self.logger.error("IQN name '{}' is not valid for "
                              "iSCSI".format(client_iqn))
            return

        target_iqn = self.parent.name

        # Issue the API call to create the client
        client_api = ('{}://localhost:{}/api/'
                      'client/{}/{}'.format(self.http_mode,
                                            settings.config.api_port,
                                            target_iqn,
                                            client_iqn))

        self.logger.debug("Client CREATE for {}".format(client_iqn))
        api = APIRequest(client_api)
        api.put()

        if api.response.status_code == 200:
            Client(self, client_iqn, cli_seed)
            self.config = get_config()
            self.logger.debug("- Client '{}' added".format(client_iqn))
            self.logger.info('ok')

        else:
            self.logger.error("Failed: {}".format(response_message(api.response,
                                                                   self.logger)))
            return

        # switch the current directory to the new client for auth or disk
        # definitions as part of the users workflow
        return self.ui_command_cd(client_iqn)
Ejemplo n.º 7
0
    def ui_command_create(self, target_iqn):
        """
        Create an iSCSI target. This target is defined across all gateway nodes,
        providing the client with a single 'image' for iscsi discovery.
        """

        self.logger.debug("CMD: /iscsi create {}".format(target_iqn))

        # is the IQN usable?
        try:
            target_iqn, iqn_type = normalize_wwn(['iqn'], target_iqn)
        except RTSLibError:
            self.logger.error("IQN name '{}' is not valid for "
                              "iSCSI".format(target_iqn))
            return

        # 'safe' to continue with the definition
        self.logger.debug("Create an iscsi target definition in the UI")

        local_api = ('{}://localhost:{}/api/'
                     'target/{}'.format(self.http_mode,
                                        settings.config.api_port, target_iqn))

        api = APIRequest(local_api)
        api.put()

        if api.response.status_code == 200:
            self.logger.info('ok')
            # create the target entry in the UI tree
            target_exists = len([
                target for target in self.children if target.name == target_iqn
            ]) > 0
            if not target_exists:
                Target(target_iqn, self)
        else:
            self.logger.error("Failed to create the target on the local node")

            raise GatewayAPIError("iSCSI target creation failed - "
                                  "{}".format(
                                      response_message(api.response,
                                                       self.logger)))
Ejemplo n.º 8
0
    def __init__(self, logger, client_iqn, image_list, username, password, mutual_username,
                 mutual_password, target_iqn):
        """
        Instantiate an instance of an LIO client
        :param client_iqn: (str) iscsi iqn string
        :param image_list: (list) list of rbd images (pool/image) to attach
                           to this client or list of tuples (disk, lunid)
        :param username: (str) chap username
        :param password: (str) chap password
        :param mutual_username: (str) chap mutual username
        :param mutual_password: (str) chap mutual password
        :param target_iqn: (str) target iqn string
        :return:
        """

        self.target_iqn = target_iqn
        self.lun_lookup = {}        # only used for hostgroup based definitions
        self.requested_images = []

        # image_list is normally a list of strings (pool/image_name) but
        # group processing forces a specific lun id allocation to masked disks
        # in this scenario the image list is a tuple
        if image_list:

            if isinstance(image_list[0], tuple):
                # tuple format ('disk_name', {'lun_id': 0})...
                for disk_item in image_list:
                    disk_name = disk_item[0]
                    lun_id = disk_item[1].get('lun_id')
                    self.requested_images.append(disk_name)
                    self.lun_lookup[disk_name] = lun_id
            else:
                self.requested_images = image_list

        self.username = username
        self.password = password
        self.mutual_username = mutual_username
        self.mutual_password = mutual_password
        self.mutual = ''
        self.tpgauth = ''
        self.metadata = {}
        self.acl = None
        self.client_luns = {}
        self.tpg = None
        self.tpg_luns = {}
        self.lun_id_list = list(range(256))           # available LUN ids 0..255
        self.change_count = 0

        # enable commit to the config for changes by default
        self.commit_enabled = True

        self.logger = logger
        self.current_config = {}
        self.error = False
        self.error_msg = ''

        try:
            client_iqn, iqn_type = normalize_wwn(['iqn'], client_iqn)
        except RTSLibError as err:
            self.error = True
            self.error_msg = "Invalid iSCSI client name - {}".format(err)

        self.iqn = client_iqn

        # Validate the images list doesn't contain duplicate entries
        dup_images = set([rbd for rbd in image_list
                          if image_list.count(rbd) >= 2])
        if len(dup_images) > 0:
            self.error = True
            dup_string = ','.join(dup_images)
            self.error_msg = ("Client's image list contains duplicate rbd's"
                              ": {}".format(dup_string))

        try:
            super(GWClient, self).__init__('targets', target_iqn, logger,
                                           GWClient.SETTINGS)
        except CephiSCSIError as err:
            self.error = True
            self.error_msg = err
Ejemplo n.º 9
0
    def __init__(self, logger, iqn, gateway_ip_list, enable_portal=True):
        """
        Instantiate the class
        :param iqn: iscsi iqn name for the gateway
        :param gateway_ip_list: list of IP addresses to be defined as portals
                to LIO
        :return: gateway object
        """

        self.error = False
        self.error_msg = ''

        self.enable_portal = enable_portal  # boolean to trigger portal IP creation
        self.logger = logger  # logger object

        try:
            iqn, iqn_type = normalize_wwn(['iqn'], iqn)
        except RTSLibError as err:
            self.error = True
            self.error_msg = "Invalid iSCSI target name - {}".format(err)
        self.iqn = iqn

        # Ensure IPv6 addresses are in the normalized address (not literal) format
        gateway_ip_list = [normalize_ip_address(x) for x in gateway_ip_list]

        # If the ip list received has data in it, this is a target we need to
        # act on the IP's provided, otherwise just set to null
        if gateway_ip_list:
            # if the ip list provided doesn't match any ip of this host, abort
            # the assumption here is that we'll only have one matching ip in
            # the list!
            matching_ip = set(gateway_ip_list).intersection(ip_addresses())
            if len(list(matching_ip)) == 0:
                self.error = True
                self.error_msg = ("gateway IP addresses provided do not match"
                                  " any ip on this host")
                return

            self.active_portal_ip = list(matching_ip)[0]
            self.logger.debug("active portal will use "
                              "{}".format(self.active_portal_ip))

            self.gateway_ip_list = gateway_ip_list
            self.logger.debug("tpg's will be defined in this order"
                              " - {}".format(self.gateway_ip_list))
        else:
            # without gateway_ip_list passed in this is a 'init' or
            # 'clearconfig' request
            self.gateway_ip_list = []
            self.active_portal_ip = []

        self.changes_made = False
        self.config_updated = False

        # self.portal = None
        self.target = None
        self.tpg = None
        self.tpg_list = []

        try:
            super(GWTarget, self).__init__('targets', iqn, logger,
                                           GWTarget.SETTINGS)
        except CephiSCSIError as err:
            self.error = True
            self.error_msg = err
Ejemplo n.º 10
0
def valid_client(**kwargs):
    """
    validate a client create or update request, based on mode.
    :param kwargs: 'mode' is the key field used to determine process flow
    :return: 'ok' or an error description (str)
    """

    valid_modes = ['create', 'delete', 'auth', 'disk']
    parms_passed = set(kwargs.keys())

    if 'mode' in kwargs:
        if kwargs['mode'] not in valid_modes:
            return ("Invalid client validation mode request - "
                    "asked for {}, available {}".format(
                        kwargs['mode'], valid_modes))
    else:
        return "Invalid call to valid_client - mode is needed"

    # at this point we have a mode to work with

    mode = kwargs['mode']
    client_iqn = kwargs['client_iqn']
    target_iqn = kwargs['target_iqn']
    config = get_config()
    if not config:
        return "Unable to query the local API for the current config"
    target_config = config['targets'][target_iqn]

    if mode == 'create':
        # iqn must be valid
        try:
            normalize_wwn(['iqn'], client_iqn)
        except RTSLibError:
            return ("Invalid IQN name for iSCSI")

        # iqn must not already exist
        if client_iqn in target_config['clients']:
            return ("A client with the name '{}' is "
                    "already defined".format(client_iqn))

        # Creates can only be done with a minimum number of gw's in place
        num_gws = len([
            gw_name for gw_name in config['gateways']
            if isinstance(config['gateways'][gw_name], dict)
        ])
        if num_gws < settings.config.minimum_gateways:
            return ("Clients can not be defined until a HA configuration "
                    "has been defined "
                    "(>{} gateways)".format(settings.config.minimum_gateways))

        # at this point pre-req's look good
        return 'ok'

    elif mode == 'delete':

        # client must exist in the configuration
        if client_iqn not in target_config['clients']:
            return ("{} is not defined yet - nothing to "
                    "delete".format(client_iqn))

        this_client = target_config['clients'].get(client_iqn)
        if this_client.get('group_name', None):
            return ("Unable to delete '{}' - it belongs to "
                    "group {}".format(client_iqn,
                                      this_client.get('group_name')))

        # client to delete must not be logged in - we're just checking locally,
        # since *all* nodes are set up the same, and a client login request
        # would normally login to each gateway
        client_info = GWClient.get_client_info(target_iqn, client_iqn)
        if client_info['state'] == 'LOGGED_IN':
            return ("Client '{}' is logged in to {}- unable to delete until"
                    " it's logged out".format(client_iqn, target_iqn))

        # at this point, the client looks ok for a DELETE operation
        return 'ok'

    elif mode == 'auth':
        # client iqn must exist
        if client_iqn not in target_config['clients']:
            return ("Client '{}' does not exist".format(client_iqn))

        username = kwargs['username']
        password = kwargs['password']
        mutual_username = kwargs['mutual_username']
        mutual_password = kwargs['mutual_password']

        error_msg = valid_credentials(username, password, mutual_username,
                                      mutual_password)
        if error_msg:
            return error_msg

        return 'ok'

    elif mode == 'disk':

        this_client = target_config['clients'].get(client_iqn)
        if this_client.get('group_name', None):
            return ("Unable to manage disks for '{}' - it belongs to "
                    "group {}".format(client_iqn,
                                      this_client.get('group_name')))

        if 'image_list' not in parms_passed:
            return ("Disk changes require 'image_list' to be set, containing"
                    " a comma separated str of rbd images (pool/image)")

        rqst_disks = set(kwargs['image_list'].split(','))
        mapped_disks = set(target_config['clients'][client_iqn]['luns'].keys())
        current_disks = set(config['disks'].keys())

        if len(rqst_disks) > len(mapped_disks):
            # this is an add operation

            # ensure the image list is 'complete' not just a single disk
            if not mapped_disks.issubset(rqst_disks):
                return ("Invalid image list - it must contain existing "
                        "disks AND any additions")

            # ensure new disk(s) exist - must yield a result since rqst>mapped
            new_disks = rqst_disks.difference(mapped_disks)
            if not new_disks.issubset(current_disks):
                # disks provided are not currently defined
                return ("Invalid image list - it defines new disks that do "
                        "not current exist")

            return 'ok'

        else:

            # this is a disk removal operation
            if kwargs['image_list']:
                if not rqst_disks.issubset(mapped_disks):
                    return ("Invalid image list ({})".format(rqst_disks))

            return 'ok'

    return 'Unknown error in valid_client function'
Ejemplo n.º 11
0
    def __init__(self, logger, client_iqn, image_list, chap):
        """
        Instantiate an instance of an LIO client
        :param client_iqn: (str) iscsi iqn string
        :param image_list: (list) list of rbd images (pool/image) to attach
                           to this client or list of tuples (disk, lunid)
        :param chap: (str) chap credentials in the format 'user/password'
        :return:
        """

        self.iqn = client_iqn
        self.lun_lookup = {}  # only used for hostgroup based definitions
        self.requested_images = []

        # image_list is normally a list of strings (pool.image_name) but
        # group processing forces a specific lun id allocation to masked disks
        # in this scenario the image list is a tuple
        if image_list:

            if isinstance(image_list[0], tuple):
                # tuple format ('disk_name', {'lun_id': 0})...
                for disk_item in image_list:
                    disk_name = disk_item[0]
                    lun_id = disk_item[1].get('lun_id')
                    self.requested_images.append(disk_name)
                    self.lun_lookup[disk_name] = lun_id
            else:
                self.requested_images = image_list

        self.chap = chap  # parameters for auth
        self.mutual = ''
        self.tpgauth = ''
        self.metadata = {}
        self.acl = None
        self.client_luns = {}
        self.tpg = None
        self.tpg_luns = {}
        self.lun_id_list = range(256)  # available LUN ids 0..255
        self.change_count = 0

        # enable commit to the config for changes by default
        self.commit_enabled = True

        self.logger = logger
        self.current_config = {}
        self.error = False
        self.error_msg = ''

        try:
            valid_iqn = normalize_wwn(['iqn'], client_iqn)
        except RTSLibError as err:
            self.error = True
            self.error_msg = "Invalid client name for iSCSI - {}".format(err)

        # Validate the images list doesn't contain duplicate entries
        dup_images = set(
            [rbd for rbd in image_list if image_list.count(rbd) >= 2])
        if len(dup_images) > 0:
            self.error = True
            dup_string = ','.join(dup_images)
            self.error_msg = ("Client's image list contains duplicate rbd's"
                              ": {}".format(dup_string))
Ejemplo n.º 12
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')