Home | History | Annotate | Download | only in rpm_control_system
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 
      6 """This file provides util functions used by RPM infrastructure."""
      7 
      8 
      9 import collections
     10 import csv
     11 import logging
     12 import os
     13 import time
     14 
     15 import common
     16 
     17 import rpm_infrastructure_exception
     18 from config import rpm_config
     19 from autotest_lib.client.common_lib import enum
     20 
     21 
     22 MAPPING_FILE = os.path.join(
     23         os.path.dirname(__file__),
     24         rpm_config.get('CiscoPOE', 'servo_interface_mapping_file'))
     25 
     26 
     27 POWERUNIT_HOSTNAME_KEY = 'powerunit_hostname'
     28 POWERUNIT_OUTLET_KEY = 'powerunit_outlet'
     29 HYDRA_HOSTNAME_KEY = 'hydra_hostname'
     30 DEFAULT_EXPIRATION_SECS = 60 * 30
     31 
     32 class PowerUnitInfo(object):
     33     """A class that wraps rpm/poe information of a device."""
     34 
     35     POWERUNIT_TYPES = enum.Enum('POE', 'RPM', string_value=True)
     36 
     37     def __init__(self, device_hostname, powerunit_type,
     38                  powerunit_hostname, outlet, hydra_hostname=None):
     39         self.device_hostname = device_hostname
     40         self.powerunit_type = powerunit_type
     41         self.powerunit_hostname = powerunit_hostname
     42         self.outlet = outlet
     43         self.hydra_hostname = hydra_hostname
     44 
     45 
     46     @staticmethod
     47     def get_powerunit_info(afe_host):
     48         """Constructe a PowerUnitInfo instance from an afe host.
     49 
     50         @param afe_host: A host object.
     51 
     52         @returns: A PowerUnitInfo object populated with the power management
     53                   unit information of the host.
     54         """
     55         if (not POWERUNIT_HOSTNAME_KEY in afe_host.attributes or
     56             not POWERUNIT_OUTLET_KEY in afe_host.attributes):
     57             raise rpm_infrastructure_exception.RPMInfrastructureException(
     58                     'Can not retrieve complete rpm information'
     59                     'from AFE for %s, please make sure %s and %s are'
     60                     ' in the host\'s attributes.' % (afe_host.hostname,
     61                     POWERUNIT_HOSTNAME_KEY, POWERUNIT_OUTLET_KEY))
     62 
     63         hydra_hostname=(afe_host.attributes[HYDRA_HOSTNAME_KEY]
     64                         if HYDRA_HOSTNAME_KEY in afe_host.attributes
     65                         else None)
     66         return PowerUnitInfo(
     67                 device_hostname=afe_host.hostname,
     68                 powerunit_type=PowerUnitInfo.POWERUNIT_TYPES.RPM,
     69                 powerunit_hostname=afe_host.attributes[POWERUNIT_HOSTNAME_KEY],
     70                 outlet=afe_host.attributes[POWERUNIT_OUTLET_KEY],
     71                 hydra_hostname=hydra_hostname)
     72 
     73 
     74 class LRUCache(object):
     75     """A simple implementation of LRU Cache."""
     76 
     77 
     78     def __init__(self, size, expiration_secs=DEFAULT_EXPIRATION_SECS):
     79         """Initialize.
     80 
     81         @param size: Size of the cache.
     82         @param expiration_secs: The items expire after |expiration_secs|
     83                                 Set to None so that items never expire.
     84                                 Default to DEFAULT_EXPIRATION_SECS.
     85         """
     86         self.size = size
     87         self.cache = collections.OrderedDict()
     88         self.timestamps = {}
     89         self.expiration_secs = expiration_secs
     90 
     91 
     92     def __getitem__(self, key):
     93         """Get an item from the cache"""
     94         # pop and insert the element again so that it
     95         # is moved to the end.
     96         value = self.cache.pop(key)
     97         self.cache[key] = value
     98         return value
     99 
    100 
    101     def __setitem__(self, key, value):
    102         """Insert an item into the cache."""
    103         if key in self.cache:
    104             self.cache.pop(key)
    105         elif len(self.cache) == self.size:
    106             removed_key, _ = self.cache.popitem(last=False)
    107             self.timestamps.pop(removed_key)
    108         self.cache[key] = value
    109         self.timestamps[key] = time.time()
    110 
    111 
    112     def __contains__(self, key):
    113         """Check whether a key is in the cache."""
    114         if (self.expiration_secs is not None and
    115             key in self.timestamps and
    116             time.time() - self.timestamps[key] > self.expiration_secs):
    117             self.cache.pop(key)
    118             self.timestamps.pop(key)
    119         return key in self.cache
    120 
    121 
    122 def load_servo_interface_mapping(mapping_file=MAPPING_FILE):
    123     """
    124     Load servo-switch-interface mapping from a CSV file.
    125 
    126     In the file, the first column represents servo hostnames,
    127     the second column represents switch hostnames, the third column
    128     represents interface names. Columns are saparated by comma.
    129 
    130     chromeos1-rack3-host12-servo,chromeos1-poe-switch1,fa31
    131     chromeos1-rack4-host2-servo,chromeos1-poe-switch1,fa32
    132     ,chromeos1-poe-switch1,fa33
    133     ...
    134 
    135     A row without a servo hostname indicates that no servo
    136     has been connected to the corresponding interface.
    137     This method ignores such rows.
    138 
    139     @param mapping_file: A csv file that stores the mapping.
    140                          If None, the setting in rpm_config.ini will be used.
    141 
    142     @return a dictionary that maps servo host name to a
    143               tuple of switch hostname and interface.
    144               e.g. {
    145               'chromeos1-rack3-host12-servo': ('chromeos1-poe-switch1', 'fa31')
    146                ...}
    147 
    148     @raises: rpm_infrastructure_exception.RPMInfrastructureException
    149              when arg mapping_file is None.
    150     """
    151     if not mapping_file:
    152         raise rpm_infrastructure_exception.RPMInfrastructureException(
    153                 'mapping_file is None.')
    154     servo_interface = {}
    155     with open(mapping_file) as csvfile:
    156         reader = csv.reader(csvfile, delimiter=',')
    157         for row in reader:
    158             servo_hostname = row[0].strip()
    159             switch_hostname = row[1].strip()
    160             interface = row[2].strip()
    161             if servo_hostname:
    162                 servo_interface[servo_hostname] = (switch_hostname, interface)
    163     return servo_interface
    164 
    165 
    166 def reload_servo_interface_mapping_if_necessary(
    167         check_point, mapping_file=MAPPING_FILE):
    168     """Reload the servo-interface mapping file if it is modified.
    169 
    170     This method checks if the last-modified time of |mapping_file| is
    171     later than |check_point|, if so, it reloads the file.
    172 
    173     @param check_point: A float number representing a time, used to determine
    174                         whether we need to reload the mapping file.
    175     @param mapping_file: A csv file that stores the mapping, if none,
    176                          the setting in rpm_config.ini will be used.
    177 
    178     @return: If the file is reloaded, returns a tuple
    179              (last_modified_time, servo_interface) where
    180              the first element is the last_modified_time of the
    181              mapping file, the second element is a dictionary that
    182              maps servo hostname to (switch hostname, interface).
    183              If the file is not reloaded, return None.
    184 
    185     @raises: rpm_infrastructure_exception.RPMInfrastructureException
    186              when arg mapping_file is None.
    187     """
    188     if not mapping_file:
    189         raise rpm_infrastructure_exception.RPMInfrastructureException(
    190                 'mapping_file is None.')
    191     last_modified = os.path.getmtime(mapping_file)
    192     if check_point < last_modified:
    193         servo_interface = load_servo_interface_mapping(mapping_file)
    194         logging.info('Servo-interface mapping file %s is reloaded.',
    195                      mapping_file)
    196         return (last_modified, servo_interface)
    197     return None
    198