171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng# Use of this source code is governed by a BSD-style license that can be
371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng# found in the LICENSE file.
471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng"""This file provides util functions used by RPM infrastructure."""
771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
99c582892f78a51624817411c69132f3ba6d68a98Fang Dengimport collections
1071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Dengimport csv
1171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Dengimport logging
129b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Dengimport os
139b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Dengimport time
1471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
159c582892f78a51624817411c69132f3ba6d68a98Fang Dengimport common
169c582892f78a51624817411c69132f3ba6d68a98Fang Deng
1771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Dengimport rpm_infrastructure_exception
1871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Dengfrom config import rpm_config
199c582892f78a51624817411c69132f3ba6d68a98Fang Dengfrom autotest_lib.client.common_lib import enum
2071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
2171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
225e4e46d80fa532887f4517c63d29590071468535Fang DengMAPPING_FILE = os.path.join(
235e4e46d80fa532887f4517c63d29590071468535Fang Deng        os.path.dirname(__file__),
245e4e46d80fa532887f4517c63d29590071468535Fang Deng        rpm_config.get('CiscoPOE', 'servo_interface_mapping_file'))
2571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
2671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
279c582892f78a51624817411c69132f3ba6d68a98Fang DengPOWERUNIT_HOSTNAME_KEY = 'powerunit_hostname'
289c582892f78a51624817411c69132f3ba6d68a98Fang DengPOWERUNIT_OUTLET_KEY = 'powerunit_outlet'
299c582892f78a51624817411c69132f3ba6d68a98Fang DengHYDRA_HOSTNAME_KEY = 'hydra_hostname'
309b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang DengDEFAULT_EXPIRATION_SECS = 60 * 30
319c582892f78a51624817411c69132f3ba6d68a98Fang Deng
329c582892f78a51624817411c69132f3ba6d68a98Fang Dengclass PowerUnitInfo(object):
339c582892f78a51624817411c69132f3ba6d68a98Fang Deng    """A class that wraps rpm/poe information of a device."""
349c582892f78a51624817411c69132f3ba6d68a98Fang Deng
359c582892f78a51624817411c69132f3ba6d68a98Fang Deng    POWERUNIT_TYPES = enum.Enum('POE', 'RPM', string_value=True)
369c582892f78a51624817411c69132f3ba6d68a98Fang Deng
379c582892f78a51624817411c69132f3ba6d68a98Fang Deng    def __init__(self, device_hostname, powerunit_type,
389c582892f78a51624817411c69132f3ba6d68a98Fang Deng                 powerunit_hostname, outlet, hydra_hostname=None):
399c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.device_hostname = device_hostname
409c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.powerunit_type = powerunit_type
419c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.powerunit_hostname = powerunit_hostname
429c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.outlet = outlet
439c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.hydra_hostname = hydra_hostname
449c582892f78a51624817411c69132f3ba6d68a98Fang Deng
459c582892f78a51624817411c69132f3ba6d68a98Fang Deng
469c582892f78a51624817411c69132f3ba6d68a98Fang Deng    @staticmethod
479c582892f78a51624817411c69132f3ba6d68a98Fang Deng    def get_powerunit_info(afe_host):
489c582892f78a51624817411c69132f3ba6d68a98Fang Deng        """Constructe a PowerUnitInfo instance from an afe host.
499c582892f78a51624817411c69132f3ba6d68a98Fang Deng
509c582892f78a51624817411c69132f3ba6d68a98Fang Deng        @param afe_host: A host object.
519c582892f78a51624817411c69132f3ba6d68a98Fang Deng
529c582892f78a51624817411c69132f3ba6d68a98Fang Deng        @returns: A PowerUnitInfo object populated with the power management
539c582892f78a51624817411c69132f3ba6d68a98Fang Deng                  unit information of the host.
549c582892f78a51624817411c69132f3ba6d68a98Fang Deng        """
559c582892f78a51624817411c69132f3ba6d68a98Fang Deng        if (not POWERUNIT_HOSTNAME_KEY in afe_host.attributes or
569c582892f78a51624817411c69132f3ba6d68a98Fang Deng            not POWERUNIT_OUTLET_KEY in afe_host.attributes):
579c582892f78a51624817411c69132f3ba6d68a98Fang Deng            raise rpm_infrastructure_exception.RPMInfrastructureException(
589c582892f78a51624817411c69132f3ba6d68a98Fang Deng                    'Can not retrieve complete rpm information'
599c582892f78a51624817411c69132f3ba6d68a98Fang Deng                    'from AFE for %s, please make sure %s and %s are'
609c582892f78a51624817411c69132f3ba6d68a98Fang Deng                    ' in the host\'s attributes.' % (afe_host.hostname,
619c582892f78a51624817411c69132f3ba6d68a98Fang Deng                    POWERUNIT_HOSTNAME_KEY, POWERUNIT_OUTLET_KEY))
629c582892f78a51624817411c69132f3ba6d68a98Fang Deng
639c582892f78a51624817411c69132f3ba6d68a98Fang Deng        hydra_hostname=(afe_host.attributes[HYDRA_HOSTNAME_KEY]
649c582892f78a51624817411c69132f3ba6d68a98Fang Deng                        if HYDRA_HOSTNAME_KEY in afe_host.attributes
659c582892f78a51624817411c69132f3ba6d68a98Fang Deng                        else None)
669c582892f78a51624817411c69132f3ba6d68a98Fang Deng        return PowerUnitInfo(
679c582892f78a51624817411c69132f3ba6d68a98Fang Deng                device_hostname=afe_host.hostname,
689c582892f78a51624817411c69132f3ba6d68a98Fang Deng                powerunit_type=PowerUnitInfo.POWERUNIT_TYPES.RPM,
699c582892f78a51624817411c69132f3ba6d68a98Fang Deng                powerunit_hostname=afe_host.attributes[POWERUNIT_HOSTNAME_KEY],
709c582892f78a51624817411c69132f3ba6d68a98Fang Deng                outlet=afe_host.attributes[POWERUNIT_OUTLET_KEY],
719c582892f78a51624817411c69132f3ba6d68a98Fang Deng                hydra_hostname=hydra_hostname)
729c582892f78a51624817411c69132f3ba6d68a98Fang Deng
739c582892f78a51624817411c69132f3ba6d68a98Fang Deng
749c582892f78a51624817411c69132f3ba6d68a98Fang Dengclass LRUCache(object):
759c582892f78a51624817411c69132f3ba6d68a98Fang Deng    """A simple implementation of LRU Cache."""
769c582892f78a51624817411c69132f3ba6d68a98Fang Deng
779c582892f78a51624817411c69132f3ba6d68a98Fang Deng
789b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng    def __init__(self, size, expiration_secs=DEFAULT_EXPIRATION_SECS):
799b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        """Initialize.
809b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng
819b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        @param size: Size of the cache.
829b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        @param expiration_secs: The items expire after |expiration_secs|
839b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng                                Set to None so that items never expire.
849b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng                                Default to DEFAULT_EXPIRATION_SECS.
859b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        """
869c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.size = size
879c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.cache = collections.OrderedDict()
889b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        self.timestamps = {}
899b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        self.expiration_secs = expiration_secs
909c582892f78a51624817411c69132f3ba6d68a98Fang Deng
919c582892f78a51624817411c69132f3ba6d68a98Fang Deng
929c582892f78a51624817411c69132f3ba6d68a98Fang Deng    def __getitem__(self, key):
939c582892f78a51624817411c69132f3ba6d68a98Fang Deng        """Get an item from the cache"""
949c582892f78a51624817411c69132f3ba6d68a98Fang Deng        # pop and insert the element again so that it
959c582892f78a51624817411c69132f3ba6d68a98Fang Deng        # is moved to the end.
969c582892f78a51624817411c69132f3ba6d68a98Fang Deng        value = self.cache.pop(key)
979c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.cache[key] = value
989c582892f78a51624817411c69132f3ba6d68a98Fang Deng        return value
999c582892f78a51624817411c69132f3ba6d68a98Fang Deng
1009c582892f78a51624817411c69132f3ba6d68a98Fang Deng
1019c582892f78a51624817411c69132f3ba6d68a98Fang Deng    def __setitem__(self, key, value):
1029c582892f78a51624817411c69132f3ba6d68a98Fang Deng        """Insert an item into the cache."""
1039c582892f78a51624817411c69132f3ba6d68a98Fang Deng        if key in self.cache:
1049c582892f78a51624817411c69132f3ba6d68a98Fang Deng            self.cache.pop(key)
1059c582892f78a51624817411c69132f3ba6d68a98Fang Deng        elif len(self.cache) == self.size:
1069b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng            removed_key, _ = self.cache.popitem(last=False)
1079b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng            self.timestamps.pop(removed_key)
1089c582892f78a51624817411c69132f3ba6d68a98Fang Deng        self.cache[key] = value
1099b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        self.timestamps[key] = time.time()
1109c582892f78a51624817411c69132f3ba6d68a98Fang Deng
1119c582892f78a51624817411c69132f3ba6d68a98Fang Deng
1129c582892f78a51624817411c69132f3ba6d68a98Fang Deng    def __contains__(self, key):
1139c582892f78a51624817411c69132f3ba6d68a98Fang Deng        """Check whether a key is in the cache."""
1149b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng        if (self.expiration_secs is not None and
1159b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng            key in self.timestamps and
1169b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng            time.time() - self.timestamps[key] > self.expiration_secs):
1179b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng            self.cache.pop(key)
1189b6c73bc6bb083ee20c2114e7595d5dde86b58a8Fang Deng            self.timestamps.pop(key)
1199c582892f78a51624817411c69132f3ba6d68a98Fang Deng        return key in self.cache
1209c582892f78a51624817411c69132f3ba6d68a98Fang Deng
1219c582892f78a51624817411c69132f3ba6d68a98Fang Deng
12271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Dengdef load_servo_interface_mapping(mapping_file=MAPPING_FILE):
12371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    """
12471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    Load servo-switch-interface mapping from a CSV file.
12571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
12671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    In the file, the first column represents servo hostnames,
12771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    the second column represents switch hostnames, the third column
12871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    represents interface names. Columns are saparated by comma.
12971c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
13071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    chromeos1-rack3-host12-servo,chromeos1-poe-switch1,fa31
13171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    chromeos1-rack4-host2-servo,chromeos1-poe-switch1,fa32
13271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    ,chromeos1-poe-switch1,fa33
13371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    ...
13471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
13571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    A row without a servo hostname indicates that no servo
13671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    has been connected to the corresponding interface.
13771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    This method ignores such rows.
13871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
13971c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    @param mapping_file: A csv file that stores the mapping.
14071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng                         If None, the setting in rpm_config.ini will be used.
14171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
14271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    @return a dictionary that maps servo host name to a
14371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng              tuple of switch hostname and interface.
14471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng              e.g. {
14571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng              'chromeos1-rack3-host12-servo': ('chromeos1-poe-switch1', 'fa31')
14671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng               ...}
14771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
14871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    @raises: rpm_infrastructure_exception.RPMInfrastructureException
14971c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng             when arg mapping_file is None.
15071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    """
15171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    if not mapping_file:
15271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        raise rpm_infrastructure_exception.RPMInfrastructureException(
15371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng                'mapping_file is None.')
15471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    servo_interface = {}
15571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    with open(mapping_file) as csvfile:
15671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        reader = csv.reader(csvfile, delimiter=',')
15771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        for row in reader:
15871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng            servo_hostname = row[0].strip()
15971c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng            switch_hostname = row[1].strip()
16071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng            interface = row[2].strip()
16171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng            if servo_hostname:
16271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng                servo_interface[servo_hostname] = (switch_hostname, interface)
16371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    return servo_interface
16471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
16571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
16671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Dengdef reload_servo_interface_mapping_if_necessary(
16771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        check_point, mapping_file=MAPPING_FILE):
16871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    """Reload the servo-interface mapping file if it is modified.
16971c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
17071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    This method checks if the last-modified time of |mapping_file| is
17171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    later than |check_point|, if so, it reloads the file.
17271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
17371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    @param check_point: A float number representing a time, used to determine
17471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng                        whether we need to reload the mapping file.
17571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    @param mapping_file: A csv file that stores the mapping, if none,
17671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng                         the setting in rpm_config.ini will be used.
17771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
17871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    @return: If the file is reloaded, returns a tuple
17971c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng             (last_modified_time, servo_interface) where
18071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng             the first element is the last_modified_time of the
18171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng             mapping file, the second element is a dictionary that
18271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng             maps servo hostname to (switch hostname, interface).
18371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng             If the file is not reloaded, return None.
18471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng
18571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    @raises: rpm_infrastructure_exception.RPMInfrastructureException
18671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng             when arg mapping_file is None.
18771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    """
18871c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    if not mapping_file:
18971c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        raise rpm_infrastructure_exception.RPMInfrastructureException(
19071c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng                'mapping_file is None.')
19171c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    last_modified = os.path.getmtime(mapping_file)
19271c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    if check_point < last_modified:
19371c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        servo_interface = load_servo_interface_mapping(mapping_file)
19471c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        logging.info('Servo-interface mapping file %s is reloaded.',
19571c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng                     mapping_file)
19671c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng        return (last_modified, servo_interface)
19771c4b1f8a48a8a6607e823a90c0e6db8a70e6b7aFang Deng    return None
198