示例#1
0
    def test_write_cache(self):
        """
        Test cache.write_cache().

        Returns
        -------
            No return value.
        """
        ts0 = write_cache(cache_fname=self.file3,
                          cache_content={'hello': 'world'})
        self.assertNotEqual(ts0, None,
                            "New cache write return None as timestamp")
        ts = get_timestamp(self.file3)
        self.assertEqual(
            ts0, ts, "timestamp returned from get_timestamp differ form "
            "one returned by write_cache")
        self.assertEqual(load_cache(self.file3), (ts, {
            'hello': 'world'
        }), 'Unexpected return values from load_cache()')
        self.assertFalse(
            write_cache(cache_fname='/proc/foo1', cache_content={}))
        self.assertFalse(
            write_cache(cache_fname='/proc/foo1',
                        fallback_fname='/proc/foo3',
                        cache_content={}))
        self.assertTrue(
            write_cache(cache_fname='/proc/foo1',
                        fallback_fname=self.file3,
                        cache_content={'hello': 'again'}))
        ts = get_timestamp(self.file3)
        self.assertEqual(load_cache(self.file3), (ts, {'hello': 'again'}))
示例#2
0
    def test_load_cache(self):
        """
        Tests cache.load_cache().

        Returns
        -------
            No return value.
        """
        self.assertEqual(load_cache(self.file1, self.file2),
                         (self.ts2, testOciCache.file2_content))

        self.assertEqual(
            load_cache(self.file1,
                       self.nofile), (self.ts1, testOciCache.file1_content),
            'load_cache(file1, NOFILE) did not return '
            'content of file1')

        self.assertEqual(
            load_cache(self.file1, self.nofile,
                       max_age=timedelta(minutes=100)),
            (self.ts1, testOciCache.file1_content),
            'load_cache(file1, NOFILE, max_age) did not return '
            'content of file1')
        self.assertEqual(
            load_cache(self.nofile, max_age=timedelta(seconds=1)), (0, None),
            'load_cache(file1, NOFILE, small max age) did not '
            'return None')
示例#3
0
def save_chap_secret(iqn, user, password):
    """
    Save the login information for the given iqn in the chap secrets file.

    Parameters
    ----------
    iqn: str
        The iSCSI qualified name.
    user: str
        The iscsiadm username.
    password: str
        The iscsiadm password.

    Returns
    -------
        No return value.
    """
    _, chap_passwords = \
        load_cache(oci_utils.__chap_password_file)
    if chap_passwords is None:
        chap_passwords = {}
    chap_passwords[iqn] = (user, password)
    write_cache(cache_content=chap_passwords,
                cache_fname=oci_utils.__chap_password_file,
                mode=0o600)
示例#4
0
def get_chap_secret(iqn):
    """
    Look for a saved (user,password) pair for iqn in the chap secrets file.

    Parameters
    ----------
    iqn: str
        The iSCSI qualified name.

    Returns
    -------
        tuple
            The (timestamp, password) on success, (None,None) otherwise.

    """
    _, chap_passwords = load_cache(oci_utils.__chap_password_file)
    if chap_passwords is None:
        return None, None
    if iqn in chap_passwords:
        return chap_passwords[iqn]
    return None, None
示例#5
0
def iscsi_func(context, func_logger):
    """
    OCID thread function for discovering and attaching/detaching block
    volumes; context must include 'max_volumes' and 'auto_detach'.

    Parameters
    ----------
    context: dict
        The thread context.
    func_logger: logger

    Returns
    -------
    dict
        The new context.
    """
    if 'oci_sess' not in context:
        oci_sess = None
        try:
            oci_sess = oci_utils.oci_api.OCISession()
        except Exception as e:
            func_logger.debug('Failed to get a session: %s' % str(e))

        max_volumes = 8
        if 'max_volumes' in context:
            max_volumes = int(context['max_volumes'])

        auto_detach = True
        if 'auto_detach' in context:
            auto_detach = context['auto_detach']

        # the number of iterations to wait before detaching an offline volume
        detach_retry = 5
        if 'detach_retry' in context:
            detach_retry = int(context['detach_retry'])

        if max_volumes > _MAX_VOLUMES_LIMIT:
            func_logger.warn("Your configured max_volumes(%s) is over the limit(%s)\n"
                             % (max_volumes, _MAX_VOLUMES_LIMIT))
            max_volumes = _MAX_VOLUMES_LIMIT

        context = {'ignore_file_ts': 0,
                   'ignore_iqns': [],
                   'attach_failed': {},
                   'chap_pw_ts': 0,
                   'chap_pws': {},
                   'oci_sess': oci_sess,
                   'max_volumes': max_volumes,
                   'offline_vols': {},
                   'auto_detach': auto_detach,
                   'detach_retry': detach_retry, }

    # devices currently attached
    session_devs = oci_utils.iscsiadm.session()

    # Load the saved passwords
    chap_passwords = context['chap_pws']
    if context['chap_pw_ts'] == 0 \
            or get_timestamp(oci_utils.__chap_password_file) > context['chap_pw_ts']:
        # the password file has changed or was never loaded
        context['chap_pw_ts'], chap_passwords = load_cache(oci_utils.__chap_password_file)
    if chap_passwords is None:
        chap_passwords = {}

    # save for the next iteration
    context['chap_pws'] = chap_passwords

    # volumes that are offline in this iteration
    new_offline_vols = {}

    all_iqns = {}

    # -------------------------------------------------------------------------------------
    # possible change for LINUX-11440; comment out the in-between
    # verify if user has authorisation to list volumes; if not, scan for new volumes.
    # volumes = None
    # if context['oci_sess'] is not None:
    #     try:
    #         #
    #         # get a list of volumes attached to the instance
    #         instance = context['oci_sess'].this_instance()
    #         if instance is None:
    #             func_logger.debug('Cannot get current instance.')
    #         else:
    #             volumes = instance.all_volumes()
    #     except Exception as e:
    #         func_logger.debug('User is not authorized to list all volumes.')
    # -------------------------------------------------------------------------------------
    #
    # volumes connected to this instance
    inst_volumes = []
    if context['oci_sess'] is not None:
        #
        # get a list of volumes attached to the instance
        instance = context['oci_sess'].this_instance()
        if instance is None:
            func_logger.debug('Cannot get current instance.')
        else:
            volumes = instance.all_volumes()
            for v in volumes:
                vol = {'iqn': v.get_iqn(),
                       'ipaddr': v.get_portal_ip(),
                       'user': v.get_user(),
                       'password': v.get_password()}
                inst_volumes.append(vol)
                if v.get_portal_ip() in all_iqns:
                    all_iqns[v.get_portal_ip()].append(v.get_iqn())
                else:
                    all_iqns[v.get_portal_ip()] = [v.get_iqn()]
            func_logger.debug('All volumes: %s', all_iqns)
    # -------------------------------------------------------------------------------------
    #
    # possible change for LINUX-11440; comment out the above
    # if bool(volumes):
    #     for v in volumes:
    #         vol = {'iqn': v.get_iqn(),
    #                'ipaddr': v.get_portal_ip(),
    #                'user': v.get_user(),
    #                'password': v.get_password()}
    #         inst_volumes.append(vol)
    #         if v.get_portal_ip() in all_iqns:
    #             all_iqns[v.get_portal_ip()].append(v.get_iqn())
    #         else:
    #             all_iqns[v.get_portal_ip()] = [v.get_iqn()]
    #     func_logger.debug('All volumes: %s', all_iqns)
    #
    # -------------------------------------------------------------------------------------
    else:
        #
        # fall back to scanning
        func_logger.debug('Scan for volumes.')
        for r in range(context['max_volumes'] + 1):
            ipaddr = "169.254.2.%d" % (r + 1)
            iqns = oci_utils.iscsiadm.discovery(ipaddr)
            all_iqns[ipaddr] = iqns
            for iqn in iqns:
                vol = {'iqn': iqn,
                       'ipaddr': ipaddr,
                       'user': None,
                       'password': None}
                # look for a saved password
                if iqn in chap_passwords:
                    vol['user'] = chap_passwords[iqn][0]
                    vol['password'] = chap_passwords[iqn][1]
                inst_volumes.append(vol)
            func_logger.debug('Scanned volumes: %s', inst_volumes)
    #
    # Load the list of volumes that were detached using oci-iscsi-config.
    # ocid shouldn't attach these automatically.
    ignore_iqns = context['ignore_iqns']
    if context['ignore_file_ts'] == 0 or get_timestamp(oci_utils.__ignore_file) > context['ignore_file_ts']:
        #
        # the list of detached volumes changed since last reading the file
        context['ignore_file_ts'], ignore_iqns = load_cache(oci_utils.__ignore_file)
    if ignore_iqns is None:
        ignore_iqns = []
    #
    # save for next iteration
    context['ignore_iqns'] = ignore_iqns
    #
    # volumes that failed to attach in an earlier iteration
    attach_failed = context['attach_failed']
    #
    # do we need to cache files?
    cache_changed = False
    ign_changed = False
    chap_changed = False
    #
    # if inst_volumes is empty, clean iscsiadm-cache to.
    if not bool(inst_volumes):
        all_iqns = {}
        write_cache(cache_content=[all_iqns, attach_failed], cache_fname=oci_utils.iscsiadm.ISCSIADM_CACHE)
    #
    # check if all discovered iscsi devices are configured and attached
    for vol in inst_volumes:
        func_logger.debug('iqn: %s', vol['iqn'])
        if vol['iqn'] in ignore_iqns:
            # a device that was manually detached, so don't
            # re-attach it automatically
            continue
        if vol['iqn'] not in session_devs:
            if vol['iqn'] in attach_failed:
                # previous attempt to attach failed, ignore
                continue
            cache_changed = True
            # configure and attach the device
            __ocid_logger.info("Attaching iscsi device: %s:%s (%s)", vol['ipaddr'], "3260", vol['iqn'])
            if vol['user'] is not None:
                attach_result = oci_utils.iscsiadm.attach(vol['ipaddr'],
                                                          3260,
                                                          vol['iqn'],
                                                          vol['user'],
                                                          vol['password'],
                                                          auto_startup=True)
                if vol['iqn'] not in chap_passwords:
                    chap_passwords[vol['iqn']] = (vol['user'], vol['password'])
                    chap_changed = True
            else:
                attach_result = oci_utils.iscsiadm.attach(vol['ipaddr'],
                                                          3260,
                                                          vol['iqn'],
                                                          auto_startup=True)
            if attach_result != 0:
                func_logger.info("Failed to attach device: %s"
                                 % oci_utils.iscsiadm.error_message_from_code(attach_result))
                attach_failed[vol['iqn']] = attach_result
                cache_changed = True
        else:
            #
            # iqn is in session_devs but not in iscsiadm cache
            write_cache(cache_content=[all_iqns, attach_failed], cache_fname=oci_utils.iscsiadm.ISCSIADM_CACHE)

    # look for previously failed volumes that are now in the session
    # (e.g. the user supplied the password using oci-iscsi-config)
    for iqn in list(attach_failed.keys()):
        if iqn in session_devs:
            del attach_failed[iqn]
            cache_changed = True

    detach_retry = 5
    if 'detach_retry' in context:
        detach_retry = int(context['detach_retry'])

    # look for disconnected devices in the current session
    # these devices were disconnected from the instance in the console,
    # we now have to detach them from at the OS level
    for iqn in session_devs:
        #
        # ignore the boot device
        if iqn.endswith('boot:uefi'):
            continue
        if 'state' not in session_devs[iqn]:
            continue
        if session_devs[iqn]['state'] in ['blocked', 'transport-offline']:
            func_logger.debug("Checking iqn %s (state %s)\n" % (iqn, session_devs[iqn]['state']))
            #
            # is the iqn discoverable at the portal?
            if iqn not in inst_volumes:
                # Not found by iscsiadm discovery.
                # To allow time for the volume to recover, wait for detach_retry
                # iterations where the volume was offline before detaching it
                if iqn not in context['offline_vols']:
                    func_logger.info("iSCSI volume appears to be offline: %s" % iqn)
                    new_offline_vols[iqn] = 1
                    continue

                if context['offline_vols'][iqn] < detach_retry:
                    new_offline_vols[iqn] = context['offline_vols'][iqn] + 1
                    func_logger.info("iSCSI volume still offline (%d): %s" % (new_offline_vols[iqn], iqn))
                    continue

                if not context['auto_detach']:
                    func_logger.info("Volume still offline, but iSCSI auto_detach disabled: %s" % iqn)
                    new_offline_vols[iqn] = detach_retry + 1
                    continue

                cache_changed = True
                ipaddr = session_devs[iqn]['persistent_portal_ip']
                func_logger.info("Detaching iSCSI device: %s:%s (%s)" % (ipaddr, "3260", iqn))
                oci_utils.iscsiadm.detach(ipaddr, 3260, iqn)
                #
                # delete from list of previously offline volumes so it
                # doesn't get reported as 'now online'
                del context['offline_vols'][iqn]
                #
                # device is gone, remove from "ignore" list
                if iqn in ignore_iqns:
                    ignore_iqns.remove(iqn)
                    ign_changed = True
                #
                # remove from attach_failed list if present
                if iqn in attach_failed:
                    del attach_failed[iqn]
                    cache_changed = True
    #
    # look for devices that were previously offline but now back online
    # (just for printing a message that it's now online)
    for iqn in context['offline_vols']:
        if iqn not in new_offline_vols:
            func_logger.info("iSCSI volume now online: %s" % iqn)
    context['offline_vols'] = new_offline_vols
    #
    # check if the devices that were previously manually detached are still
    # connected to the instance
    inst_iqns = [vol['iqn'] for vol in inst_volumes]
    for iqn in ignore_iqns:
        # if iqn not in inst_iqns:
        # GT
        if iqn in inst_iqns:
            func_logger.debug("Removing iqn %s from ignore list" % iqn)
            ignore_iqns.remove(iqn)
            ign_changed = True

    # rewrite changed cache files
    if ign_changed:
        context['ignore_file_ts'] = \
            write_cache(cache_content=ignore_iqns, cache_fname=oci_utils.__ignore_file)
    if chap_changed:
        context['chap_pw_ts'] = \
            write_cache(cache_content=chap_passwords, cache_fname=oci_utils.__chap_password_file, mode=0o600)
    if cache_changed or not os.path.exists(oci_utils.iscsiadm.ISCSIADM_CACHE):
        write_cache(cache_content=[all_iqns, attach_failed], cache_fname=oci_utils.iscsiadm.ISCSIADM_CACHE)
    else:
        try:
            os.utime(oci_utils.iscsiadm.ISCSIADM_CACHE, None)
        except Exception as e:
            func_logger.warn("Failed to update cache timestamp: %s" % e)

    return context
示例#6
0
def main():
    """
    Main.

    Returns
    -------
        int
            Return value of the operation, if any.
            0 otherwise.
    """

    parser = get_args_parser()
    args = parser.parse_args()
    if args.command is None:
        # default to 'sync' command
        args.command = "sync"

    if args.command == 'usage':
        parser.print_help()
        sys.exit(0)

    oci_sess = None
    try:
        oci_sess = oci_utils.oci_api.OCISession()
    except Exception as e:
        _logger.debug('Cannot get OCI session: %s', str(e))

    system_disks = lsblk.list()
    iscsiadm_session = iscsiadm.session()

    if args.command == 'show':
        display_current_devices(oci_sess, iscsiadm_session, system_disks)
        if args.compartments:
            api_display_available_block_volumes(oci_sess, args.compartments,
                                                args.all)
        else:
            api_display_available_block_volumes(oci_sess, None, args.all)
        return 0

    max_volumes = OCIUtilsConfiguration.getint('iscsi', 'max_volumes')
    if max_volumes > oci_utils._MAX_VOLUMES_LIMIT:
        _logger.error("Your configured max_volumes(%s) is over the limit(%s)",
                      max_volumes, oci_utils._MAX_VOLUMES_LIMIT)
        max_volumes = oci_utils._MAX_VOLUMES_LIMIT

    ocid_cache = load_cache(iscsiadm.ISCSIADM_CACHE,
                            max_age=timedelta(minutes=2))[1]
    if ocid_cache is None:
        _logger.debug('updating the cache')
        # run ocid once, to update the cache
        ocid_refresh(wait=True)
        # now try to load again
        ocid_cache = load_cache(iscsiadm.ISCSIADM_CACHE,
                                max_age=timedelta(minutes=2))[1]
    if ocid_cache is None:
        targets, attach_failed = None, None
    else:
        targets, attach_failed = ocid_cache

    detached_volume_iqns = load_cache(__ignore_file)[1]
    if detached_volume_iqns is None:
        detached_volume_iqns = []

    if args.command == 'sync' and not detached_volume_iqns and not attach_failed:
        # nothing to do, stop here
        print("All known devices are attached.")
        print("Use the -s or --show option for details.")

    # starting from here, nothing works if we are not root
    _user_euid = os.geteuid()
    if _user_euid != 0:
        _logger.error("You must run this program with root privileges")
        return 1

    if args.command == 'sync':
        # we still have volume not attached, process them.
        retval = 0
        _did_something = False
        if detached_volume_iqns:
            print()
            print("Detached devices:")
            for iqn in detached_volume_iqns:
                display_detached_iscsi_device(iqn, targets)
                if args.apply or args.interactive:
                    if args.yes:
                        ans = True
                    else:
                        ans = ask_yes_no(
                            "Would you like to attach this device?")
                    if ans:
                        try:
                            _do_iscsiadm_attach(oci_sess, iqn, targets)
                            _did_something = True
                        except Exception as e:
                            _logger.error('[%s] attachment failed: %s', iqn,
                                          str(e))
                            retval = 1

        if attach_failed:
            _logger.info("Devices that could not be attached automatically:")
            for iqn in list(attach_failed.keys()):
                display_detached_iscsi_device(iqn, targets, attach_failed)
                _attach_user_name = None
                _attach_user_passwd = None
                _give_it_a_try = False
                if args.apply or args.interactive:
                    if attach_failed[iqn] != 24:
                        # not authentication error
                        if args.yes or ask_yes_no(
                                "Would you like to retry attaching this device?"
                        ):
                            _give_it_a_try = True
                    else:
                        # authentication error
                        if args.yes or ask_yes_no(
                                "Would you like to configure this device?"):
                            _give_it_a_try = True
                            if oci_sess is not None:
                                oci_vols = oci_sess.find_volumes(iqn=iqn)
                                if len(oci_vols) != 1:
                                    _logger.error('volume [%s] not found', iqn)
                                    _give_it_a_try = False
                                _attach_user_name = oci_vols[0].get_user()
                                _attach_user_passwd = oci_vols[0].get_password(
                                )
                            else:
                                (_attach_user_name,
                                 _attach_user_passwd) = get_chap_secret(iqn)
                                if _attach_user_name is None:
                                    _logger.error(
                                        'Cannot retreive chap credentials')
                                    _give_it_a_try = False
                    if _give_it_a_try:
                        try:
                            _do_iscsiadm_attach(iqn, targets,
                                                _attach_user_name,
                                                _attach_user_passwd)
                            _did_something = True
                        except Exception as e:
                            _logger.error(
                                "Failed to configure device automatically: %s",
                                str(e))
                            retval = 1

        if _did_something:
            ocid_refresh()
        return retval

    if args.command == 'create':
        if len(system_disks) > max_volumes:
            _logger.error("This instance reached the max_volumes(%s)",
                          max_volumes)
            return 1
        try:
            do_create_volume(oci_sess,
                             size=args.size,
                             display_name=args.volume_name,
                             attach_it=args.attach_volume)
        except Exception as e:
            _logger.error('volume creation has failed: %s', str(e))
            return 1

        if args.show:
            display_current_devices(oci_sess, iscsiadm_session, system_disks)
            api_display_available_block_volumes(oci_sess)
        return 0

    if args.command == 'destroy':
        # destroy command used to be for only one volume
        # changed the behavior to be more aligned with attach/dettach commands
        # i.e : taking more than one ocid and doing best effort
        retval = 0
        if not args.yes:
            for ocid in args.ocids:
                _logger.info("volume : %s", ocid)
            if not ask_yes_no(
                    "WARNING: the volume(s) will be destroyed.  This is irreversible.  Continue?"
            ):
                return 0
        for ocid in args.ocids:
            try:
                _logger.debug('Destroying [%s]', ocid)
                do_destroy_volume(oci_sess, ocid)
                _logger.info("Volume [%s] is destroyed", ocid)
            except Exception as e:
                _logger.error('volume [%s] deletion has failed: %s', ocid,
                              str(e))
                retval = 1

        if args.show:
            display_current_devices(oci_sess, iscsiadm_session, system_disks)
            api_display_available_block_volumes(oci_sess)
        return retval

    if args.command == 'detach':
        retval = 0
        for iqn in args.iqns:
            if iqn in detached_volume_iqns:
                _logger.error("Target %s is already detached", iqn)
                retval = 1
                continue
            if iqn not in iscsiadm_session or 'device' not in iscsiadm_session[
                    iqn]:
                _logger.error("Target %s not found", iqn)
                retval = 1
                continue

            _logger.debug('unmounting the block volume')
            if not unmount_device(iscsiadm_session, iqn, system_disks):
                _logger.debug('Unmounting has failed')
                if not args.force:
                    if not ask_yes_no(
                            "Failed to unmount volume, Continue detaching anyway?"
                    ):
                        continue
                else:
                    _logger.info(
                        'unmount failed, force option selected,continue anyway'
                    )
            try:
                _logger.debug('Detaching [%s]', iqn)
                do_detach_volume(oci_sess, iscsiadm_session, iqn)
                _logger.info("Volume [%s] is detached", iqn)
                detached_volume_iqns.append(iqn)
            except Exception as e:
                _logger.error('volume [%s] detach has failed: %s', iqn, str(e))
                retval = 1
        if args.show:
            display_current_devices(oci_sess, iscsiadm_session, system_disks)
            api_display_available_block_volumes(oci_sess)

        _logger.info("Updating detached volume cache file: %s",
                     detached_volume_iqns)
        write_cache(cache_content=detached_volume_iqns,
                    cache_fname=__ignore_file)
        _logger.debug('trigger ocid refresh')
        ocid_refresh()

        return retval

    if args.command == 'attach':
        if len(system_disks) > max_volumes:
            _logger.error(
                "This instance reached the maximum number of volumes attached (%s)",
                max_volumes)
            return 1

        retval = 0

        for iqn in args.iqns:
            _iqn_to_use = iqn
            _save_chap_cred = False
            if iqn in iscsiadm_session:
                _logger.info("Target %s is already attached.", iqn)
                continue

            if _iqn_to_use.startswith('ocid1.volume.oc'):
                _logger.debug('given IQN [%s] is an ocid, attaching it',
                              _iqn_to_use)
                bs_volume = None
                try:
                    bs_volume = _do_attach_oci_block_volume(
                        oci_sess, _iqn_to_use)
                    _logger.info("Volume [%s] is attached", _iqn_to_use)
                    # user/pass coming from volume itself
                    _attachment_username = bs_volume.get_user()
                    _attachment_password = bs_volume.get_password()
                    _iqn_to_use = bs_volume.get_iqn()
                except Exception as e:
                    _logger.error('Failed to attach volume [%s]: %s',
                                  _iqn_to_use, str(e))
                    retval = 1
                    continue
            else:
                _logger.debug('given IQN [%s] is not an ocid', _iqn_to_use)
                if args.username is not None and args.password is not None:
                    _attachment_username = args.username
                    _attachment_password = args.password
                else:
                    # user/pass not provided , asking for it
                    (_attachment_username,
                     _attachment_password) = get_chap_secret(iqn)
                    _save_chap_cred = True

            _logger.debug('attaching [%s] to iSCSI session', _iqn_to_use)
            try:
                _do_iscsiadm_attach(_iqn_to_use,
                                    targets,
                                    user=_attachment_username,
                                    passwd=_attachment_password,
                                    iscsi_portal_ip=bs_volume.get_portal_ip())
                _logger.debug('attach ok')
                if _iqn_to_use in detached_volume_iqns:
                    detached_volume_iqns.remove(_iqn_to_use)
            except Exception as e:
                _logger.error("Failed to attach target %s: %s", _iqn_to_use,
                              str(e))
                _save_chap_cred = False
                retval = 1
                continue

            if _save_chap_cred:
                _logger.debug('attachment OK: saving chap creds')
                save_chap_secret(_iqn_to_use, _attachment_username,
                                 _attachment_password)

        if args.show:
            display_current_devices(oci_sess, iscsiadm_session, system_disks)
            api_display_available_block_volumes(oci_sess)

        if retval == 0:
            _logger.info("Updating detached volume cache file: %s",
                         detached_volume_iqns)
            write_cache(cache_content=detached_volume_iqns,
                        cache_fname=__ignore_file)
            _logger.debug('trigger ocid refresh')
            ocid_refresh()

        return retval

    if not args.show and not attach_failed and not detached_volume_iqns:
        print("All known devices are attached.")
        print("Use the -s or --show option for details.")

    return 0
def main():
    """
    Main.

    Returns
    -------
        int
            Return value of the operation, if any.
            0 otherwise.
    """
    global USE_OCI_SDK
    global oci_sdk_error
    global _user_euid
    oci_sdk_error = None
    oci_sess = None

    args = parse_args()

    _user_euid = os.geteuid()

    if oci_utils.oci_api.HAVE_OCI_SDK:
        try:
            oci_sess = oci_utils.oci_api.OCISession()
            USE_OCI_SDK = True
        except Exception as e:
            oci_sdk_error = str(e)
            USE_OCI_SDK = False
            if args.debug:
                raise

    if not os.path.isfile("/var/run/ocid.pid"):
        _logger.error("Warning:\n"
                      "For full functionality of this utility the ocid "
                      "service must be running\n"
                      "The administrator can start it using this command:\n"
                      "    sudo systemctl start ocid.service\n")

    max_volumes = OCIUtilsConfiguration.getint('iscsi', 'max_volumes')
    if max_volumes > oci_utils._MAX_VOLUMES_LIMIT:
        _logger.error(
            "Your configured max_volumes(%s) is over the limit(%s)\n"
            % (max_volumes, oci_utils._MAX_VOLUMES_LIMIT))
        max_volumes = oci_utils._MAX_VOLUMES_LIMIT

    ocid_cache = load_cache(iscsiadm.ISCSIADM_CACHE,
                            max_age=timedelta(minutes=2))[1]
    if ocid_cache is None and _user_euid == 0:
        # run ocid once, to update the cache
        ocid_refresh(wait=True, debug=args.debug)
        # now try to load again
        ocid_cache = load_cache(iscsiadm.ISCSIADM_CACHE,
                                max_age=timedelta(minutes=2))[1]
    if ocid_cache is None:
        targets, attach_failed = None, None
    else:
        targets, attach_failed = ocid_cache
    disks = lsblk.list()
    session = iscsiadm.session()
    detached = load_cache(__ignore_file)[1]
    if detached is None:
        detached = []

    if args.create_volume:
        if _user_euid != 0:
            _logger.error("You must run this program with root privileges "
                             "to create and attach iSCSI devices.\n")
            return 1
        if len(disks) > max_volumes:
            _logger.error(
                "This instance reached the max_volumes(%s)\n" % max_volumes)
            return 1

        # FIXME: use_chap
        retval = do_create_volume(oci_sess, size=args.create_volume,
                                  display_name=args.volume_name)
    elif args.destroy_volume:
        if _user_euid != 0:
            _logger.error("You must run this program with root privileges "
                             "to destroy a iSCSI volume.\n")
            return 1
        retval = do_destroy_volume(oci_sess, args.destroy_volume,
                                   args.interactive)
        if retval == 0:
            print "Volume %s is destroyed." % args.destroy_volume
        return retval

    elif args.detach_iqns:
        if _user_euid != 0:
            _logger.error("You must run this program with root privileges "
                             "to detach iSCSI devices.\n")
            return 1

        write_ignore_file = False
        retval = 0
        do_refresh = False
        for iqn in args.detach_iqns:
            if not iqn.startswith("iqn."):
                _logger.error("Invalid IQN %s\n" % iqn)
                retval = 1
                continue
            if iqn in detached:
                _logger.error("Target %s is already detached\n" % iqn)
                retval = 1
                continue
            if iqn not in session:
                _logger.error("Target %s not found\n" % iqn)
                retval = 1
                continue
            if 'boot:uefi' in iqn:
                _logger.error("IQN %s is the boot device, cannot "
                                 "detach.\n" % iqn)
                retval = 1
                continue
            if not unmount_device(session, iqn, disks):
                if args.interactive:
                    cont = ask_yes_no("Failed to unmount volume.  "
                                      "Continue detaching anyway?")
                    if not cont:
                        return 1
                else:
                    return 1

            api_detached = False

            if USE_OCI_SDK:
                api_detached = api_detach(oci_sess, iqn)

            if not iscsiadm.detach(session[iqn]['persistent_portal_ip'],
                                   session[iqn]['persistent_portal_port'],
                                   iqn):
                _logger.error("Failed to detach target %s\n" % iqn)
                retval = 1
            else:
                if not api_detached:
                    detached.append(iqn)
                    write_ignore_file = True
                    do_refresh = True
        if write_ignore_file:
            _logger.error("Updating ignore file: %s\n" % detached)
            write_cache(cache_content=detached,
                        cache_fname=__ignore_file)
        if do_refresh:
            ocid_refresh(debug=args.debug)
        return retval

    elif args.attach_iqns:
        if _user_euid != 0:
            _logger.error("You must run this program with root "
                             "privileges to attach iSCSI devices.\n")
            return 1

        if len(disks) > max_volumes:
            _logger.error(
                "This instance reached the max_volumes(%s)\n" % max_volumes)
            return 1

        retval = 0
        write_ignore_file = False
        do_refresh = False

        for iqn in args.attach_iqns:
            if iqn.startswith('ocid1.volume.oc'):
                # it's an OCID
                if not do_attach_ocid(oci_sess, iqn):
                    retval = 1
                continue
            elif not iqn.startswith("iqn."):
                _logger.error("Invalid IQN %s \n" % iqn)
                retval = 1
                continue

            if iqn in session:
                print "Target %s is already attached." % iqn
                continue
            if iqn not in detached and iqn not in attach_failed:
                _logger.error("Target %s not found\n" % iqn)
                retval = 1
                continue
            user = args.username
            passwd = args.password
            if user is None or passwd is None:
                (user, passwd) = get_chap_secret(iqn)
            if do_attach(oci_sess, iqn, targets,
                         user=user, passwd=passwd) != 0:
                _logger.error("Failed to attach target %s\n" % iqn)
                retval = 1
            else:
                do_refresh = True
                if iqn in detached:
                    detached.remove(iqn)
                if args.username is not None:
                    save_chap_secret(iqn, args.username, args.password)
                write_ignore_file = True
        if write_ignore_file:
            write_cache(cache_content=detached,
                        cache_fname=__ignore_file)
        if do_refresh:
            ocid_refresh(debug=args.debug)

        return retval

    if args.show:
        display_current_devices(oci_sess, session, disks)
        api_display_available_devices(oci_sess, args)

    if args.create_volume or args.destroy_volume:
        return retval

    if detached:
        print
        print "Detached devices:"

        do_refresh = False
        write_ignore_file = False
        for iqn in detached:
            display_detached_device(iqn, targets)
            if args.interactive:
                if _user_euid != 0:
                    print "You must run this program with root privileges " \
                          "to attach iSCSI devices.\n"
                    ans = False
                else:
                    ans = ask_yes_no("Would you like to attach this device?")
                if ans:
                    retval = do_attach(oci_sess, iqn, targets)
                    do_refresh = True
                    if retval == 24:
                        # authentication error
                        attach_failed[iqn] = 24
                    if iqn in detached:
                        detached.remove(iqn)
                        write_ignore_file = True
        if write_ignore_file:
            write_cache(cache_content=detached,
                        cache_fname=__ignore_file)
        if do_refresh:
            ocid_refresh(debug=args.debug)

    if attach_failed:
        print
        print "Devices that could not be attached automatically:"

        auth_errors = 0
        for iqn in attach_failed.keys():
            if attach_failed[iqn] == 24:
                auth_errors += 1

        for iqn in attach_failed.keys():
            display_attach_failed_device(iqn, targets, attach_failed)
            do_refresh = False
            if args.interactive:
                if attach_failed[iqn] != 24:
                    # not authentication error
                    ans = True
                    while ans:
                        if _user_euid != 0:
                            print "You must run this program with root  " \
                                  "privileges to attach iSCSI devices.\n"
                            ans = False
                        else:
                            ans = ask_yes_no("Would you like to retry "
                                             "attaching this device?")
                        if ans:
                            retval = do_attach(oci_sess, iqn, targets)
                            if retval == 0:
                                ans = False
                                do_refresh = True
                        else:
                            ans = False
                else:
                    # authentication error
                    if _user_euid != 0:
                        print "You must run this program with root " \
                              "privileges to configure iSCSI devices.\n"
                        ans = False
                    else:
                        ans = ask_yes_no("Would you like to configure this "
                                         "device?")
                    if ans:
                        retval = 1
                        if USE_OCI_SDK:
                            # try and get the user and password from the API
                            retval = do_attach(oci_sess, iqn, targets,
                                               None, None)
                        else:
                            (user, passwd) = get_chap_secret(iqn)
                            if user is not None:
                                retval = do_attach(oci_sess, iqn, targets,
                                                   user, passwd)
                        if retval == 0:
                            print "Device configured automatically."
                            do_refresh = True
                        else:
                            myocid = get_instance_ocid()
                            while ans:
                                print "To find the CHAP username and " \
                                      "password for this device, go to"
                                print "https://console.us-phoenix-1." \
                                      "oraclecloud.com/#/a/compute/instances" \
                                      "/%s/disks?jt=listing" % \
                                      myocid
                                print "Select the Block Volume, then click " \
                                      "the \"iSCSI Commands & Information\" " \
                                      "button."
                                print "CHAP username:"******"CHAP password:"******"Attaching iSCSI device..."
                                retval = do_attach(oci_sess, iqn, targets,
                                                   user, passwd)
                                if retval != 0:
                                    ans = ask_yes_no("Would you like to try "
                                                     "again?")
                                else:
                                    ans = False
                                    do_refresh = True
        if do_refresh:
            ocid_refresh(debug=args.debug)
        if not args.interactive and auth_errors:
            print
            print "Use the -i or --interactive mode to configure " \
                  "devices that require authentication information"

    if not args.show and not attach_failed and not detached:
        print "All known devices are attached."
        print "Use the -s or --show option for details."

    return 0