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