# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """This file provides util functions used by RPM infrastructure.""" import collections import csv import logging import os import time import common import rpm_infrastructure_exception from config import rpm_config from autotest_lib.client.common_lib import enum MAPPING_FILE = os.path.join( os.path.dirname(__file__), rpm_config.get('CiscoPOE', 'servo_interface_mapping_file')) POWERUNIT_HOSTNAME_KEY = 'powerunit_hostname' POWERUNIT_OUTLET_KEY = 'powerunit_outlet' HYDRA_HOSTNAME_KEY = 'hydra_hostname' DEFAULT_EXPIRATION_SECS = 60 * 30 class PowerUnitInfo(object): """A class that wraps rpm/poe information of a device.""" POWERUNIT_TYPES = enum.Enum('POE', 'RPM', string_value=True) def __init__(self, device_hostname, powerunit_type, powerunit_hostname, outlet, hydra_hostname=None): self.device_hostname = device_hostname self.powerunit_type = powerunit_type self.powerunit_hostname = powerunit_hostname self.outlet = outlet self.hydra_hostname = hydra_hostname @staticmethod def get_powerunit_info(afe_host): """Constructe a PowerUnitInfo instance from an afe host. @param afe_host: A host object. @returns: A PowerUnitInfo object populated with the power management unit information of the host. """ if (not POWERUNIT_HOSTNAME_KEY in afe_host.attributes or not POWERUNIT_OUTLET_KEY in afe_host.attributes): raise rpm_infrastructure_exception.RPMInfrastructureException( 'Can not retrieve complete rpm information' 'from AFE for %s, please make sure %s and %s are' ' in the host\'s attributes.' % (afe_host.hostname, POWERUNIT_HOSTNAME_KEY, POWERUNIT_OUTLET_KEY)) hydra_hostname=(afe_host.attributes[HYDRA_HOSTNAME_KEY] if HYDRA_HOSTNAME_KEY in afe_host.attributes else None) return PowerUnitInfo( device_hostname=afe_host.hostname, powerunit_type=PowerUnitInfo.POWERUNIT_TYPES.RPM, powerunit_hostname=afe_host.attributes[POWERUNIT_HOSTNAME_KEY], outlet=afe_host.attributes[POWERUNIT_OUTLET_KEY], hydra_hostname=hydra_hostname) class LRUCache(object): """A simple implementation of LRU Cache.""" def __init__(self, size, expiration_secs=DEFAULT_EXPIRATION_SECS): """Initialize. @param size: Size of the cache. @param expiration_secs: The items expire after |expiration_secs| Set to None so that items never expire. Default to DEFAULT_EXPIRATION_SECS. """ self.size = size self.cache = collections.OrderedDict() self.timestamps = {} self.expiration_secs = expiration_secs def __getitem__(self, key): """Get an item from the cache""" # pop and insert the element again so that it # is moved to the end. value = self.cache.pop(key) self.cache[key] = value return value def __setitem__(self, key, value): """Insert an item into the cache.""" if key in self.cache: self.cache.pop(key) elif len(self.cache) == self.size: removed_key, _ = self.cache.popitem(last=False) self.timestamps.pop(removed_key) self.cache[key] = value self.timestamps[key] = time.time() def __contains__(self, key): """Check whether a key is in the cache.""" if (self.expiration_secs is not None and key in self.timestamps and time.time() - self.timestamps[key] > self.expiration_secs): self.cache.pop(key) self.timestamps.pop(key) return key in self.cache def load_servo_interface_mapping(mapping_file=MAPPING_FILE): """ Load servo-switch-interface mapping from a CSV file. In the file, the first column represents servo hostnames, the second column represents switch hostnames, the third column represents interface names. Columns are saparated by comma. chromeos1-rack3-host12-servo,chromeos1-poe-switch1,fa31 chromeos1-rack4-host2-servo,chromeos1-poe-switch1,fa32 ,chromeos1-poe-switch1,fa33 ... A row without a servo hostname indicates that no servo has been connected to the corresponding interface. This method ignores such rows. @param mapping_file: A csv file that stores the mapping. If None, the setting in rpm_config.ini will be used. @return a dictionary that maps servo host name to a tuple of switch hostname and interface. e.g. { 'chromeos1-rack3-host12-servo': ('chromeos1-poe-switch1', 'fa31') ...} @raises: rpm_infrastructure_exception.RPMInfrastructureException when arg mapping_file is None. """ if not mapping_file: raise rpm_infrastructure_exception.RPMInfrastructureException( 'mapping_file is None.') servo_interface = {} with open(mapping_file) as csvfile: reader = csv.reader(csvfile, delimiter=',') for row in reader: servo_hostname = row[0].strip() switch_hostname = row[1].strip() interface = row[2].strip() if servo_hostname: servo_interface[servo_hostname] = (switch_hostname, interface) return servo_interface def reload_servo_interface_mapping_if_necessary( check_point, mapping_file=MAPPING_FILE): """Reload the servo-interface mapping file if it is modified. This method checks if the last-modified time of |mapping_file| is later than |check_point|, if so, it reloads the file. @param check_point: A float number representing a time, used to determine whether we need to reload the mapping file. @param mapping_file: A csv file that stores the mapping, if none, the setting in rpm_config.ini will be used. @return: If the file is reloaded, returns a tuple (last_modified_time, servo_interface) where the first element is the last_modified_time of the mapping file, the second element is a dictionary that maps servo hostname to (switch hostname, interface). If the file is not reloaded, return None. @raises: rpm_infrastructure_exception.RPMInfrastructureException when arg mapping_file is None. """ if not mapping_file: raise rpm_infrastructure_exception.RPMInfrastructureException( 'mapping_file is None.') last_modified = os.path.getmtime(mapping_file) if check_point < last_modified: servo_interface = load_servo_interface_mapping(mapping_file) logging.info('Servo-interface mapping file %s is reloaded.', mapping_file) return (last_modified, servo_interface) return None