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 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 = 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 74class 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 122def 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 166def 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