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'}))
def setUp(self): """ Initialise the OCI cache test. Returns ------- No return value. """ super(testOciCache, self).setUp() # create 2 files, one newer than the other to verify get_newer() self.file1 = "/tmp/oci-test-%s" % uuid.uuid1() self.file2 = "/tmp/oci-test-%s" % uuid.uuid1() self.file3 = "/tmp/oci-test-%s" % uuid.uuid1() # this one won't be created self.nofile = "/tmp/oci-test-%s" % uuid.uuid1() with open(self.file1, "w") as _f: json.dump(testOciCache.file1_content, _f) _f.close() time.sleep(1) with open(self.file2, "w") as _f: json.dump(testOciCache.file2_content, _f) _f.close() self.ts1 = get_timestamp(self.file1) self.ts2 = get_timestamp(self.file2)
def test_get_timestamp(self): """ Tests cache.get_timestamp(). Returns ------- No return value. """ self.assertEqual(get_timestamp(None), 0, 'get_timestamp(None) != 0') self.assertEqual(get_timestamp('file_path_which_do_not_exists '), 0, 'get_timestamp() on non-existing file did not return 0') self.assertGreater(get_timestamp(tempfile.gettempdir()), 0, 'get_timestamp() on existing path did not return ' 'positive value')
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