| # 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 |