472 lines
14 KiB
Python
Executable File
472 lines
14 KiB
Python
Executable File
#!/usr/bin/python2
|
|
|
|
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""
|
|
This script generates a csv file containing the mapping of
|
|
(device_hostname, rpm_hostname, outlet, hydra_hostname) for each
|
|
host in our lab. The csv file is in the following format.
|
|
|
|
chromeos-rack2-host1,chromeos-rack2-rpm1,.A1,chromeos-197-hydra1.mtv
|
|
chromeos-rack2-host2,chromeos-rack2-rpm1,.A2,chromeos-197-hydra1.mtv
|
|
...
|
|
|
|
The generated csv file can be used as input to add_host_powerunit_info.py
|
|
|
|
Workflow:
|
|
<Generate the csv file>
|
|
python generate_rpm_mapping.py --csv mapping_file.csv --server cautotest
|
|
|
|
<Upload mapping information in csv file to AFE>
|
|
python add_host_powerunit_info.py --csv mapping_file.csv
|
|
|
|
"""
|
|
import argparse
|
|
import collections
|
|
import logging
|
|
import re
|
|
import sys
|
|
|
|
import common
|
|
|
|
from autotest_lib.client.common_lib import autotest_enum
|
|
from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
|
|
|
|
CHROMEOS_LABS = autotest_enum.AutotestEnum('OysterBay', 'Atlantis',
|
|
'Chaos', 'Destiny', start_value=1)
|
|
HOST_REGX = 'chromeos(\d+)(-row(\d+))*-rack(\d+)-host(\d+)'
|
|
DeviceHostname = collections.namedtuple(
|
|
'DeviceHostname', ['lab', 'row', 'rack', 'host'])
|
|
|
|
|
|
class BaseLabConfig(object):
|
|
"""Base class for a lab configuration."""
|
|
RPM_OUTLET_MAP = {}
|
|
LAB_NUMBER = -1
|
|
|
|
@classmethod
|
|
def get_rpm_hostname(cls, device_hostname):
|
|
"""Get rpm hostname given a device.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: the rpm hostname, default to empty string.
|
|
|
|
"""
|
|
return ''
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_outlet(cls, device_hostname):
|
|
"""Get rpm outlet given a device.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: the rpm outlet, default to empty string.
|
|
|
|
"""
|
|
return ''
|
|
|
|
|
|
@classmethod
|
|
def get_hydra_hostname(cls, device_hostname):
|
|
"""Get hydra hostname given a device.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: the hydra hostname, default to empty string.
|
|
|
|
"""
|
|
return ''
|
|
|
|
|
|
@classmethod
|
|
def is_device_in_the_lab(cls, device_hostname):
|
|
"""Check whether a dut belongs to the lab.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: True if the dut belongs to the lab,
|
|
False otherwise.
|
|
|
|
"""
|
|
return device_hostname.lab == cls.LAB_NUMBER
|
|
|
|
|
|
class OysterBayConfig(BaseLabConfig):
|
|
"""Configuration for OysterBay"""
|
|
|
|
LAB_NUMBER = CHROMEOS_LABS.OYSTERBAY
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_hostname(cls, device_hostname):
|
|
"""Get rpm hostname.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: hostname of the rpm that has the device.
|
|
|
|
"""
|
|
if not device_hostname.row:
|
|
return ''
|
|
return 'chromeos%d-row%d-rack%d-rpm1' % (
|
|
device_hostname.lab, device_hostname.row,
|
|
device_hostname.rack)
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_outlet(cls, device_hostname):
|
|
"""Get rpm outlet.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: rpm outlet, e.g. '.A1'
|
|
|
|
"""
|
|
if not device_hostname.row:
|
|
return ''
|
|
return '.A%d' % device_hostname.host
|
|
|
|
|
|
class AtlantisConfig(BaseLabConfig):
|
|
"""Configuration for Atlantis lab."""
|
|
|
|
LAB_NUMBER = CHROMEOS_LABS.ATLANTIS
|
|
# chromeos2, hostX -> outlet
|
|
RPM_OUTLET_MAP = {
|
|
1: 1,
|
|
7: 2,
|
|
2: 4,
|
|
8: 5,
|
|
3: 7,
|
|
9: 8,
|
|
4: 9,
|
|
10: 10,
|
|
5: 12,
|
|
11: 13,
|
|
6: 15,
|
|
12: 16}
|
|
|
|
@classmethod
|
|
def get_rpm_hostname(cls, device_hostname):
|
|
"""Get rpm hostname.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: hostname of the rpm that has the device.
|
|
|
|
"""
|
|
return 'chromeos%d-row%d-rack%d-rpm1' % (
|
|
device_hostname.lab, device_hostname.row,
|
|
device_hostname.rack)
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_outlet(cls, device_hostname):
|
|
"""Get rpm outlet.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: rpm outlet, e.g. '.A1'
|
|
|
|
"""
|
|
return '.A%d' % cls.RPM_OUTLET_MAP[device_hostname.host]
|
|
|
|
|
|
@classmethod
|
|
def get_hydra_hostname(cls, device_hostname):
|
|
"""Get hydra hostname.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: hydra hostname
|
|
|
|
"""
|
|
row = device_hostname.row
|
|
rack = device_hostname.rack
|
|
if row >= 1 and row <= 5 and rack >= 1 and rack <= 7:
|
|
return 'chromeos-197-hydra1.cros'
|
|
elif row >= 1 and row <= 5 and rack >= 8 and rack <= 11:
|
|
return 'chromeos-197-hydra2.cros'
|
|
else:
|
|
logging.error('Could not determine hydra for %s',
|
|
device_hostname)
|
|
return ''
|
|
|
|
|
|
class ChaosConfig(BaseLabConfig):
|
|
"""Configuration for Chaos lab."""
|
|
|
|
LAB_NUMBER = CHROMEOS_LABS.CHAOS
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_hostname(cls, device_hostname):
|
|
"""Get rpm hostname.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: hostname of the rpm that has the device.
|
|
|
|
"""
|
|
return 'chromeos%d-row%d-rack%d-rpm1' % (
|
|
device_hostname.lab, device_hostname.row,
|
|
device_hostname.rack)
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_outlet(cls, device_hostname):
|
|
"""Get rpm outlet.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: rpm outlet, e.g. '.A1'
|
|
|
|
"""
|
|
return '.A%d' % device_hostname.host
|
|
|
|
|
|
class DestinyConfig(BaseLabConfig):
|
|
"""Configuration for Desitny lab."""
|
|
|
|
LAB_NUMBER = CHROMEOS_LABS.DESTINY
|
|
# None-densified rack: one host per shelf
|
|
# (rowX % 2, hostY) -> outlet
|
|
RPM_OUTLET_MAP = {
|
|
(1, 1): 1,
|
|
(0, 1): 2,
|
|
(1, 2): 4,
|
|
(0, 2): 5,
|
|
(1, 3): 7,
|
|
(0, 3): 8,
|
|
(1, 4): 9,
|
|
(0, 4): 10,
|
|
(1, 5): 12,
|
|
(0, 5): 13,
|
|
(1, 6): 15,
|
|
(0, 6): 16,
|
|
}
|
|
|
|
# Densified rack: one shelf can have two chromeboxes or one notebook.
|
|
# (rowX % 2, hostY) -> outlet
|
|
DENSIFIED_RPM_OUTLET_MAP = {
|
|
(1, 2): 1, (1, 1): 1,
|
|
(0, 1): 2, (0, 2): 2,
|
|
(1, 4): 3, (1, 3): 3,
|
|
(0, 3): 4, (0, 4): 4,
|
|
(1, 6): 5, (1, 5): 5,
|
|
(0, 5): 6, (0, 6): 6,
|
|
(1, 8): 7, (1, 7): 7,
|
|
(0, 7): 8, (0, 8): 8,
|
|
# outlet 9, 10 are not used
|
|
(1, 10): 11, (1, 9): 11,
|
|
(0, 9): 12, (0, 10): 12,
|
|
(1, 12): 13, (1, 11): 13,
|
|
(0, 11): 14, (0, 12): 14,
|
|
(1, 14): 15, (1, 13): 15,
|
|
(0, 13): 16, (0, 14): 16,
|
|
(1, 16): 17, (1, 15): 17,
|
|
(0, 15): 18, (0, 16): 18,
|
|
(1, 18): 19, (1, 17): 19,
|
|
(0, 17): 20, (0, 18): 20,
|
|
(1, 20): 21, (1, 19): 21,
|
|
(0, 19): 22, (0, 20): 22,
|
|
(1, 22): 23, (1, 21): 23,
|
|
(0, 21): 24, (0, 22): 24,
|
|
}
|
|
|
|
|
|
@classmethod
|
|
def is_densified(cls, device_hostname):
|
|
"""Whether the host is on a densified rack.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: True if on a densified rack, False otherwise.
|
|
"""
|
|
return device_hostname.rack in (0, 12, 13)
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_hostname(cls, device_hostname):
|
|
"""Get rpm hostname.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: hostname of the rpm that has the device.
|
|
|
|
"""
|
|
row = device_hostname.row
|
|
if row == 13:
|
|
logging.warn('Rule not implemented for row 13 in chromeos4')
|
|
return ''
|
|
|
|
# rpm row is like chromeos4-row1_2-rackX-rpmY
|
|
rpm_row = ('%d_%d' % (row - 1, row) if row % 2 == 0 else
|
|
'%d_%d' % (row, row + 1))
|
|
|
|
if cls.is_densified(device_hostname):
|
|
# Densified rack has two rpms, decide which one the host belongs to
|
|
# Rule:
|
|
# odd row number, even host number -> rpm1
|
|
# odd row number, odd host number -> rpm2
|
|
# even row number, odd host number -> rpm1
|
|
# even row number, even host number -> rpm2
|
|
rpm_number = 1 if (row + device_hostname.host) % 2 == 1 else 2
|
|
else:
|
|
# Non-densified rack only has one rpm
|
|
rpm_number = 1
|
|
return 'chromeos%d-row%s-rack%d-rpm%d' % (
|
|
device_hostname.lab,
|
|
rpm_row, device_hostname.rack, rpm_number)
|
|
|
|
|
|
@classmethod
|
|
def get_rpm_outlet(cls, device_hostname):
|
|
"""Get rpm outlet.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: rpm outlet, e.g. '.A1'
|
|
|
|
"""
|
|
try:
|
|
outlet_map = (cls.DENSIFIED_RPM_OUTLET_MAP
|
|
if cls.is_densified(device_hostname) else
|
|
cls.RPM_OUTLET_MAP)
|
|
outlet_number = outlet_map[(device_hostname.row % 2,
|
|
device_hostname.host)]
|
|
return '.A%d' % outlet_number
|
|
except KeyError:
|
|
logging.error('Could not determine outlet for device %s',
|
|
device_hostname)
|
|
return ''
|
|
|
|
|
|
@classmethod
|
|
def get_hydra_hostname(cls, device_hostname):
|
|
"""Get hydra hostname.
|
|
|
|
@param device_hostname: A DeviceHostname named tuple.
|
|
|
|
@returns: hydra hostname
|
|
|
|
"""
|
|
row = device_hostname.row
|
|
rack = device_hostname.rack
|
|
if row >= 1 and row <= 6 and rack >=1 and rack <= 11:
|
|
return 'chromeos-destiny-hydra1.cros'
|
|
elif row >= 7 and row <= 12 and rack >=1 and rack <= 11:
|
|
return 'chromeos-destiny-hydra2.cros'
|
|
elif row >= 1 and row <= 10 and rack >=12 and rack <= 13:
|
|
return 'chromeos-destiny-hydra3.cros'
|
|
elif row in [3, 4, 5, 6, 9, 10] and rack == 0:
|
|
return 'chromeos-destiny-hydra3.cros'
|
|
elif row == 13 and rack >= 0 and rack <= 11:
|
|
return 'chromeos-destiny-hydra3.cros'
|
|
else:
|
|
logging.error('Could not determine hydra hostname for %s',
|
|
device_hostname)
|
|
return ''
|
|
|
|
|
|
def parse_device_hostname(device_hostname):
|
|
"""Parse device_hostname to DeviceHostname object.
|
|
|
|
@param device_hostname: A string, e.g. 'chromeos2-row2-rack4-host3'
|
|
|
|
@returns: A DeviceHostname named tuple or None if the
|
|
the hostname doesn't follow the pattern
|
|
defined in HOST_REGX.
|
|
|
|
"""
|
|
m = re.match(HOST_REGX, device_hostname.strip())
|
|
if m:
|
|
return DeviceHostname(
|
|
lab=int(m.group(1)),
|
|
row=int(m.group(3)) if m.group(3) else None,
|
|
rack=int(m.group(4)),
|
|
host=int(m.group(5)))
|
|
else:
|
|
logging.error('Could not parse %s', device_hostname)
|
|
return None
|
|
|
|
|
|
def generate_mapping(hosts, lab_configs):
|
|
"""Generate device_hostname-rpm-outlet-hydra mapping.
|
|
|
|
@param hosts: hosts objects get from AFE.
|
|
@param lab_configs: A list of configuration classes,
|
|
each one for a lab.
|
|
|
|
@returns: A dictionary that maps device_hostname to
|
|
(rpm_hostname, outlet, hydra_hostname)
|
|
|
|
"""
|
|
# device hostname -> (rpm_hostname, outlet, hydra_hostname)
|
|
rpm_mapping = {}
|
|
for host in hosts:
|
|
device_hostname = parse_device_hostname(host.hostname)
|
|
if not device_hostname:
|
|
continue
|
|
for lab in lab_configs:
|
|
if lab.is_device_in_the_lab(device_hostname):
|
|
rpm_hostname = lab.get_rpm_hostname(device_hostname)
|
|
rpm_outlet = lab.get_rpm_outlet(device_hostname)
|
|
hydra_hostname = lab.get_hydra_hostname(device_hostname)
|
|
if not rpm_hostname or not rpm_outlet:
|
|
logging.error(
|
|
'Skipping device %s: could not determine '
|
|
'rpm hostname or outlet.', host.hostname)
|
|
break
|
|
rpm_mapping[host.hostname] = (
|
|
rpm_hostname, rpm_outlet, hydra_hostname)
|
|
break
|
|
else:
|
|
logging.info(
|
|
'%s is not in a know lab '
|
|
'(oyster bay, atlantis, chaos, destiny)',
|
|
host.hostname)
|
|
return rpm_mapping
|
|
|
|
|
|
def output_csv(rpm_mapping, csv_file):
|
|
"""Dump the rpm mapping dictionary to csv file.
|
|
|
|
@param rpm_mapping: A dictionary that maps device_hostname to
|
|
(rpm_hostname, outlet, hydra_hostname)
|
|
@param csv_file: The name of the file to write to.
|
|
|
|
"""
|
|
with open(csv_file, 'w') as f:
|
|
for hostname, rpm_info in rpm_mapping.iteritems():
|
|
line = ','.join(rpm_info)
|
|
line = ','.join([hostname, line])
|
|
f.write(line + '\n')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
parser = argparse.ArgumentParser(
|
|
description='Generate device_hostname-rpm-outlet-hydra mapping '
|
|
'file needed by add_host_powerunit_info.py')
|
|
parser.add_argument('--csv', type=str, dest='csv_file', required=True,
|
|
help='The path to the csv file where we are going to '
|
|
'write the mapping information to.')
|
|
parser.add_argument('--server', type=str, dest='server', default=None,
|
|
help='AFE server that the script will be talking to. '
|
|
'If not specified, will default to using the '
|
|
'server in global_config.ini')
|
|
options = parser.parse_args()
|
|
|
|
AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10,
|
|
server=options.server)
|
|
logging.info('Connected to %s', AFE.server)
|
|
rpm_mapping = generate_mapping(
|
|
AFE.get_hosts(),
|
|
[OysterBayConfig, AtlantisConfig, ChaosConfig, DestinyConfig])
|
|
output_csv(rpm_mapping, options.csv_file)
|