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