proving_grounds: Checkin code for go/wifi-down dashboard
BUG=None
TEST=None
Change-Id: Ifa8a80b09e89b8556ad5f80f69dfcc86e21b850e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crostestutils/+/3084226
Tested-by: Shijin Abraham <shijinabraham@google.com>
Reviewed-by: Harpreet Grewal <harpreet@chromium.org>
Commit-Queue: Shijin Abraham <shijinabraham@google.com>
diff --git a/provingground/connectivity/wifidown_dashboard/README b/provingground/connectivity/wifidown_dashboard/README
new file mode 100644
index 0000000..8f0de57
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/README
@@ -0,0 +1,17 @@
+# Copyright 2021 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.
+
+# How to run
+# Run python3 populate_wificell_dashboard.py to and go to go/wifi-down the
+ see devices that needs repair
+
+# Troubleshooting
+# Check paths in constants.py
+# Make sure dhcp file is latest. (Do repo sync in the folder)
+# If a new tab is added in go/cros_conn_device_lifecycle, it has to be added
+ to get_wifisheet_data.py
+# If number of wificell devices exceeded 1000, update swarming query in
+ get_wificell_data.py
+# change logging level to DEBUG for more logs
+# debug_main can be used to write intermediate stages to file
diff --git a/provingground/connectivity/wifidown_dashboard/constants.py b/provingground/connectivity/wifidown_dashboard/constants.py
new file mode 100644
index 0000000..8d451aa
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/constants.py
@@ -0,0 +1,22 @@
+# Copyright 2021 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.
+
+import os
+import sys
+
+USERNAME = os.environ['USER']
+SWARMING_PATH = '/usr/local/google/home/%s/chromiumos/chromite/third_party/swarming.client/' % USERNAME
+DHCP_PATH = '/usr/local/google/home/%s/chromiumos/chromeos-admin/puppet/modules/lab/files/dhcp-server/dhcpd.conf' % USERNAME
+
+
+def main():
+ if int(sys.version.split(' ')[0].split('.')[0]) != 3:
+ print("Please invoke with python3")
+ sys.exit()
+ print('SWARMING PATH is %s' % SWARMING_PATH)
+ print('DHCP_PATH is %s' % DHCP_PATH)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/provingground/connectivity/wifidown_dashboard/credentials.py b/provingground/connectivity/wifidown_dashboard/credentials.py
new file mode 100755
index 0000000..afdfbff
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/credentials.py
@@ -0,0 +1,5 @@
+# Copyright 2021 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.
+
+json_keyfile = 'wifi-sheets-api-989c7d546825.json'
diff --git a/provingground/connectivity/wifidown_dashboard/get_dhcp_data.py b/provingground/connectivity/wifidown_dashboard/get_dhcp_data.py
new file mode 100644
index 0000000..1f45932
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/get_dhcp_data.py
@@ -0,0 +1,78 @@
+# Copyright 2021 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.
+
+import logging
+import sys
+import re
+
+from constants import DHCP_PATH
+
+logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
+
+
+def get_data():
+ """
+ Read the DHCP file and get devices in chromeos15
+ @returns (hosts, other_devices) where hosts are anything that matches
+ chromeos15-row*-rack*-host* and other_devices are anything that
+ are not hosts but starts with chromeos15-
+ """
+ data = [i.strip() for i in open(DHCP_PATH).read().split('\n')]
+ data = [
+ i.split(' ')[1] for i in data
+ if i != '' and i[0] != '#' and 'chromeos15' in i and i[:5] == 'host '
+ ]
+ pattern = 'chromeos15-row.*-rack.*-host.*'
+ peer_pattern = 'chromeos15-row.*-rack.*-host.*-.*'
+ metro_pattern = 'chromeos15-row.*-metro.*-host.*'
+ metro_peer_pattern = 'chromeos15-row.*-metro.*-host.*-.*'
+ hosts = []
+ peer_devices = []
+ other_devices = []
+ for i in data:
+ if re.match(pattern, i) is not None:
+ if re.match(peer_pattern, i) is not None:
+ peer_devices.append(i)
+ else:
+ hosts.append(i)
+ elif re.match(metro_pattern, i) is not None:
+ if re.match(metro_peer_pattern, i) is not None:
+ peer_devices.append(i)
+ else:
+ hosts.append(i)
+ else:
+ other_devices.append(i)
+ return (hosts, peer_devices, other_devices)
+
+
+def main():
+ if int(sys.version.split(' ')[0].split('.')[0]) != 3:
+ print("Please invoke with python3")
+ sys.exit()
+ (hosts, peer_devices, other_devices) = get_data()
+ print('hosts')
+ for i in hosts:
+ print(i)
+
+ print('peer_devices')
+ for i in peer_devices:
+ print(i)
+
+ print('other devices')
+ for i in other_devices:
+ print(i)
+
+ print("%s hosts found" % len(hosts))
+ print("%s peer_devices found" % len(peer_devices))
+ print("%s other_devices found" % len(other_devices))
+
+ #h = 'chromeos15-row8-rack1-host4'
+ #if h not in hosts and h not in other_devices and h not in peer_devices:
+ # print("%s not found" % h)
+ #else:
+ # print("%s found" % h)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/provingground/connectivity/wifidown_dashboard/get_wificell_data.py b/provingground/connectivity/wifidown_dashboard/get_wificell_data.py
new file mode 100644
index 0000000..4c185ad
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/get_wificell_data.py
@@ -0,0 +1,142 @@
+# Copyright 2021 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.
+
+import subprocess
+import json
+import sys
+import logging
+
+from constants import SWARMING_PATH
+
+#
+# Execute this script to download swarming info
+#
+
+logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
+
+
+def _get_wificell_data():
+ cmd = "python2 {}swarming.py query --swarming https://chromeos-swarming.appspot.com 'bots/list?dimensions=pool%3AChromeOSSkylab&dimensions=label-wificell%3ATrue&quarantined=FALSE&limit=1000'".format(
+ SWARMING_PATH)
+ print("executing %s" % cmd)
+ output = json.loads(subprocess.check_output(cmd, shell=True))
+ # keys (['death_timeout', 'items', 'now']
+ return output['items']
+
+
+def parse_swarming_entry(i):
+ try:
+ is_bt = False
+ is_bt_new = []
+ b = None
+ m = None
+ name = None
+ p = None
+ deleted = i['deleted']
+ is_dead = i['is_dead']
+ bluetooth_label = None
+ wifichip = None
+ conductive = False
+ hw_phase = None
+ servo_v3 = False
+ wificell = False
+ for j in i['dimensions']:
+ if j['key'] == 'label-wificell':
+ wificell = j['value'][0] == 'True'
+ if j['key'] == 'label-pool':
+ p = j['value'][0]
+ if j['key'] == 'label-board':
+ b = j['value'][0]
+ if j['key'] == 'label-bluetooth':
+ bluetooth_label = True
+ if j['key'] == 'label-model':
+ m = j['value'][0]
+ if j['key'] == 'dut_name':
+ name = j['value'][0]
+ if j['key'] == 'label-chameleon_type':
+ is_bt = 'CHAMELEON_TYPE_BT_PEER' in j['value']
+ if j['key'] == 'label-working_bluetooth_btpeer':
+ is_bt_new.extend(j['value'])
+ if j['key'] == 'label-wifi_chip':
+ wifichip = j['value'][0]
+ if j['key'] == 'dut_state':
+ dut_state = j['value'][0]
+ if j['key'] == 'label-conductive':
+ conductive = True
+ if j['key'] == 'label-phase':
+ hw_phase = j['value'][0]
+ if j['key'] == 'servo_type':
+ servo_v3 = j['value'][0] == 'servo_v3'
+
+ res = {
+ 'host': name,
+ 'board': b,
+ 'model': m.lower(),
+ 'bt_label': is_bt,
+ 'bt_peers': is_bt_new[:],
+ 'pool': p,
+ 'is_dead': is_dead,
+ 'deleted': deleted,
+ 'bluetooth_label': bluetooth_label,
+ 'wifichip': wifichip,
+ 'conductive': conductive,
+ 'hw_phase': hw_phase,
+ 'missing': False,
+ 'wificell': wificell,
+ 'servo': servo_v3
+ }
+ if name is None:
+ logging.debug(i)
+ logging.debug(res)
+ return None
+ return res
+ except Exception as e:
+ logging.error("exception in parse_swarming entry '%s'", str(e))
+ return None
+
+
+def parse_raw_data(raw_data):
+ d = {}
+ for k, v in raw_data.items():
+ if k != 'dimensions':
+ d[k] = v
+ for l in raw_data['dimensions']:
+ k = l['key']
+ v = l['value']
+ if k in d:
+ logging.debug("key %s already present" % k)
+ d[k] = v
+ logging.debug(d)
+ return d
+
+
+def get_data():
+ raw_data = _get_wificell_data()
+ data = {}
+ for rd in raw_data:
+ d = parse_swarming_entry(rd)
+ if d is None:
+ continue
+ if d['is_dead']:
+ logging.debug('igoring dead dut %s' % d)
+ continue
+ host = d['host']
+ data[host] = d
+ return data
+
+
+def main():
+ if int(sys.version.split(' ')[0].split('.')[0]) != 3:
+ print("Please invoke with python3")
+ sys.exit()
+ data = get_data()
+ for i in data.items():
+ print(i)
+ # Save host info to a file for debugging purpose
+ with open('/tmp/skylab_hosts.json', 'w') as fp:
+ json.dump(data, fp)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/provingground/connectivity/wifidown_dashboard/get_wifisheet_data.py b/provingground/connectivity/wifidown_dashboard/get_wifisheet_data.py
new file mode 100644
index 0000000..bcad63a
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/get_wifisheet_data.py
@@ -0,0 +1,148 @@
+# Copyright 2021 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.
+
+import logging
+import gspread
+import sys
+from oauth2client.service_account import ServiceAccountCredentials
+from credentials import json_keyfile
+
+logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
+
+#
+# Read data from go/conn-device-life-cycle
+#
+
+SHEETS_LIST = [
+ 'Row1 & 2 (Conductive/PerBuild)',
+ 'Row4 (OTA)',
+ 'Row5 (BT perbuild)',
+ 'Row7',
+ 'Row8',
+ 'Row8-metro',
+ 'Row3 (Grover)',
+ 'Row3 (Pre-Cq)',
+ 'Row4 (Pre-Cq)',
+ 'Row6(Pre-Cq)',
+]
+
+
+def read_wifi_device_sheet():
+ """ Read data from go/conn-device-lifecycle sheet"""
+ scope = [
+ 'https://spreadsheets.google.com/feeds',
+ 'https://www.googleapis.com/auth/drive'
+ ]
+ credentials = ServiceAccountCredentials.from_json_keyfile_name(
+ json_keyfile, scope)
+ gc = gspread.authorize(credentials)
+ # go/conn-device-life-cycle
+ spreadsheet = gc.open_by_key(
+ '1DhwNaPSbdXudWIrjBJpiLViOPOjrmBGy7BpIYZr9TqQ')
+ data = []
+ for sheet in spreadsheet.worksheets():
+ if sheet.title not in SHEETS_LIST:
+ logging.info("ignoring sheet |%s|", sheet.title)
+ continue
+ logging.debug("Reading sheet %s !!!!", sheet.title)
+ values = sheet.get_all_records()
+ for i in values:
+ logging.debug(i)
+ if 'Hostname' not in i or i['Hostname'] != '':
+ data.append(i)
+ else:
+ logging.debug("ignoring %s", i)
+ return data
+
+
+def process_data(raw_data):
+ """ Get required columns"""
+
+ def _get_pool(p):
+ try:
+ for i in p.split(','):
+ if 'pool' in i:
+ return i.split(':')[1].strip(' ').lower()
+ return None
+ except:
+ logging.error("error parsing pool string %s", p)
+ return None
+
+ def _get_labels(p):
+ return [i.strip() for i in p.split(',')]
+
+ data = {}
+ for i in raw_data:
+ try:
+ logging.debug(i)
+ hostname = i['Hostname'].lower()
+ if hostname[:8] != 'chromeos':
+ logging.debug("Igorning %s", hostname)
+ continue
+ model = i['Model'].lower()
+ board = i['Board'].lower()
+ pool = _get_pool(i['Atest Labels'])
+ labels = _get_labels(i['Atest Labels'])
+
+ if 'Pi (btpeer hostname)' in i:
+ num_btpeers = i['Pi (btpeer hostname)']
+ else:
+ num_btpeers = 0
+
+ if hostname in data:
+ logging.error("Duplicate entry for %s", hostname)
+
+ data[hostname] = {}
+ if model == '' or model == 'empty cell':
+ data[hostname]['model'] = ''
+ else:
+ data[hostname]['model'] = model
+
+ if board == '' or board == 'empty cell':
+ data[hostname]['board'] = ''
+ else:
+ data[hostname]['board'] = board
+
+ data[hostname]['pool'] = pool
+
+ btpeers = []
+
+ if num_btpeers in ['', 'no']:
+ pass
+ else:
+ try:
+ btpeers.extend([
+ 'btpeer' + str(i)
+ for i in range(1,
+ int(num_btpeers) + 1)
+ ])
+ except:
+ logging.error("Exception while parsing num_btpeers %s",
+ num_btpeers)
+
+ data[hostname]['btpeers'] = btpeers
+
+ data[hostname]['labels'] = labels
+ logging.debug("Adding %s %s" % (hostname, data[hostname]))
+ except Exception as e:
+ logging.debug(sys.exc_info())
+ logging.debug("Exception %s while processing %s %s" %
+ (e, i, sys.exc_info()))
+ return data
+
+
+def get_wifisheet_data():
+ raw_data = read_wifi_device_sheet()
+ #logging.debug(raw_data)
+ data = process_data(raw_data)
+ #logging.debug(data)
+ for i in data:
+ print(i)
+ return data
+
+
+if __name__ == '__main__':
+ for i, v in get_wifisheet_data().items():
+ print(i)
+ print(v)
diff --git a/provingground/connectivity/wifidown_dashboard/populate_wificell_dashboard.py b/provingground/connectivity/wifidown_dashboard/populate_wificell_dashboard.py
new file mode 100644
index 0000000..86aeb0f
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/populate_wificell_dashboard.py
@@ -0,0 +1,1126 @@
+# Copyright 2021 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 checks WiFi/Bluetooth peer devices in the lab and creates
+# a google spreadsheet for the one which are down.
+# The google sheet is displayed at go/wifi-down
+#
+# This is used by ACS lab to detect down devices
+#
+# This script get data from 3 sources
+# 1) data from dhcp file /usr/local/google/home/<user>/chromiumos/
+# \chromeos-admin/puppet/modules/lab/files/dhcp-server/dhcpd.conf
+# 2) Swarming data of all bots with label-wificell
+# 3) data from g/cros_conn_device_lifecycle
+#
+# Once data from these three sources are combined, the script pings the devices
+# that we are interested in. Any unreachable devices is displayed in the dashboard
+# for the lab team to rectify.
+#
+# Data from all sources is collected device data which of following format
+# At each stage 'ignore' flag in send to False if the device meet the criteria to be monitored
+# Any host/peer with ignore flag set is not displayed in dashboard
+#
+# 'chromeos15-row8-rack2-host2': {'dhcp': True,
+# 'doc': True,
+# 'doc_data': {'board': 'gnawty',
+# 'btpeers': [],
+# 'model': 'gnawty',
+# 'pool': 'wificell_perbuild'},
+# 'ignore': False,
+# 'ignore_reason' : ''
+# 'peers': {'chromeos15-row8-rack2-host2-pcap': {'dhcp': True,
+# 'doc': False,
+# 'ignore': True,
+# 'ssh_status': False,
+# 'swarming': True},
+# 'chromeos15-row8-rack2-host2-router': {'dhcp': True,
+# 'doc': False,
+# 'ignore': True,
+# 'ssh_status': False,
+# 'swarming': True}},
+# 'ssh_status': False,
+# 'swarming': True,
+# 'swarming_data': {'bluetooth_label': True,
+# 'board': 'gnawty',
+# 'bt_label': False,
+# 'bt_peers': [],
+# 'conductive': True,
+# 'deleted': False,
+# 'host': 'chromeos15-row8-rack2-host2',
+# 'hw_phase': 'PHASE_PVT',
+# 'is_dead': False,
+# 'missing': False,
+# 'model': 'gnawty',
+# 'pool': 'wificell_perbuild',
+# 'servo': False,
+# 'wifichip': 'wireless_intel'}},
+#
+#
+# Note 1: Only devices is chromeos15- is checked
+# Note 2 : Currently the following peer devices are considered PCAP,ROUTER,BTPEER1-4, SERVO, ATTENUATOR
+# Note 3 : Standalone RPMS in chromeos3 are added as special cases
+# Note 4: For debugging this script, use debug_main and store intermediate results in files.
+#
+#
+#TODO
+# debug the hang
+# servo already there
+# rpm already there?
+# attentuator already there
+# separate doc issues to different sheet
+# send mail
+
+import csv
+import datetime
+import gspread
+import json
+import logging
+import os
+import pprint
+import subprocess
+import sys
+import time
+import queue
+
+from oauth2client.service_account import ServiceAccountCredentials
+from credentials import json_keyfile
+from multiprocessing import Process
+from multiprocessing import Queue
+
+import get_wificell_data
+import get_wifisheet_data
+import get_dhcp_data
+import rpm_list
+
+# Change logging level to DEBUG for more logs
+#logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
+logging.basicConfig(stream=sys.stdout, level=logging.INFO)
+
+DASHBOARD_REFRESH_INTERVAL = 1000 # Time to wait between dashboard refreshes in seconds
+CONNECTIVITY_RETEST_INTERVAL = 180 # Time to wait before rechecking connectivity to down devices in seconds
+HOST_UP = 'UP'
+HOST_DOWN = 'DOWN'
+HOST_NO_SSH = 'Online w/o SSH Con'
+
+PING_COUNT = 2
+SPREADSHEET_ALL = 'WiFi Devices DOWN'
+WORKSHEET1 = 'LAB'
+WORKSHEET2 = 'Documentation'
+
+# Mapping integers to host status strings.
+HOST_STATUS = {0: HOST_UP, 1: HOST_DOWN, 3: HOST_NO_SSH}
+
+# Ignore devices in these pools
+POOLS_TO_IGNORE = ['cross_device_multi_cb']
+
+# Names of bluetooth peers
+BT_PEERS = ['btpeer1', 'btpeer2', 'btpeer3', 'btpeer4']
+
+# Name of wifi peer devices
+WIFI_PEERS = ['router', 'pcap']
+
+#Pools with attentuator
+ATTENUATOR_POOLS = ['groamer', 'groamer_two', 'bt_groamer']
+
+
+def _pretty_print(d, msg=''):
+ print('------------------------------------------------------------')
+ if msg != '':
+ print('====== %s =========' % msg)
+ if type(d) == dict:
+ pp = pprint.PrettyPrinter(indent=1)
+ pp.pprint(d)
+ print('length is %s' % len(d))
+ elif type(d) == list:
+ for i in d:
+ print(i)
+ print('length is %s' % len(d))
+ else:
+ print(d)
+ print('------------------------------------------------------------')
+
+
+def _parse_doc_model_name(m):
+ """ parse Model name in the go/cros-conn-lifecycle sheet so it can be compared with swarming model name
+
+ It can be 'Mordin (Barla)' which be be parsed as [mordin, barla]
+ veyron_/auron_ prefixes should be removed
+ There can be WIP in the name which means that is should be ignored
+ """
+ result = []
+ m = m.lower()
+ if '[wip]' in m:
+ result.append('[wip]')
+ m = m.strip('[wip]')
+ logging.debug('WIP device found')
+ if '(' in m:
+ for i in m.split('('):
+ i = i.strip().replace(')', '').lower()
+ result.append(i)
+ else:
+ result.append(m.strip().lower())
+ logging.debug('Returning %s for %s', result, m)
+ return result
+
+
+def _make_peers(h, l):
+ if type(l) == list:
+ res = []
+ for p in l:
+ res.append(h + '-' + p)
+ return res
+ else:
+ return h + '-' + p
+
+
+def getHostStatus(q, host):
+ """ Ping the host and check if it is ssh-able"""
+ try:
+ logging.debug('Checking status of %s', host)
+ # Grab the ping exit code.
+ host_status_code = subprocess.call(['ping', '-c2', host])
+ # if the device is pingable, we check if port 22 is open to accept ssh connection.
+ if host_status_code == 0:
+ try:
+ nc_output_code = subprocess.call(
+ ['nc', '-zv', '-w3', host, '22'])
+ except:
+ logging.debug('netcat failed: %s', host)
+ if nc_output_code != 0:
+ host_status_code = 3
+ ret_status = HOST_STATUS[host_status_code]
+ except Exception as e:
+ logging.error('!!!!!!!! Exception %s while checking %s', str(e), host)
+ ret_status = HOST_DOWN
+ finally:
+ logging.debug('Host %s returning status %s', host, ret_status)
+ q.put((host, ret_status))
+
+
+def get_rpm_list():
+ """ Read the list of rpms """
+ return rpm_list.rpm_list
+
+
+def update_rpm_data(device_data, rpm_list):
+ """ Update list of rpm into device data """
+ for h in rpm_list:
+ device_data[h] = {
+ 'ignore': False,
+ 'ignore_reason': 'RPM not ignored',
+ 'dhcp': True, # Found in dhcp file
+ 'ssh_status': False,
+ 'swarming': True, # RPM wont be in swarming
+ 'doc': True, # RPM wont be in doc
+ 'pool': 'RPM', # Add a false pool
+ 'peers': {},
+ 'chromeos': False
+ }
+ logging.debug('dhcp other device added %s %s', h, device_data[h])
+
+
+def update_dhcp_data(device_data, hosts, peer_devices, other_devices):
+ """
+ Update dhcp data into device_data
+ """
+
+ for h in other_devices:
+ device_data[h] = {
+ 'ignore': True, # Ignore by default
+ 'ignore_reason': 'Other devices ignored in update_dhcp',
+ 'dhcp': True, # Found in dhcp file
+ 'ssh_status': False,
+ 'swarming': False,
+ 'doc': False,
+ 'pool': None,
+ 'peers': {},
+ 'chromeos': False
+ }
+ logging.debug('dhcp other device added %s %s', h, device_data[h])
+
+ for h in hosts:
+ device_data[h] = {
+ 'ignore': True, # ignore hosts unless it is a wificell
+ 'ignore_reason': 'host ignored in update_dhcp',
+ 'dhcp': True, # Found in dhcp file
+ 'ssh_status': False,
+ 'swarming': False,
+ 'doc': False,
+ 'pool': None,
+ 'peers': {},
+ 'chromeos': True
+ }
+ logging.debug('dhcp host added %s %s', h, device_data[h])
+ for peer in peer_devices:
+ # Do not ignore rpm or servo since these can't be detected from swarming or doc
+ if 'rpm' in peer:
+ peer_dict = {
+ 'ignore': False, # ignore it is a peer of wificell host
+ 'ignore_reason': 'peer rpm not ignored in update_dhcp',
+ 'dhcp': True, # Found in dhcp file
+ 'ssh_status': False,
+ 'swarming': True, # RPM cannot be found in swarming
+ 'doc': True, # RPM not recorded in doc
+ 'chromeos': False
+ }
+ elif 'servo' in peer:
+ peer_dict = {
+ 'ignore': False, # ignore it is a peer of wificell host
+ 'ignore_reason': 'peer servo not ignored in update_dhcp',
+ 'dhcp': True, # Found in dhcp file
+ 'ssh_status': False,
+ 'swarming':
+ True, # servo is not currently detected from swarming
+ 'doc':
+ True, # servo is not currentyl detected from the document
+ 'chromeos': False
+ }
+ else:
+ # Ignore other peer unless they can be found in swarming or doc
+ peer_dict = {
+ 'ignore': True, # ignore it is a peer of wificell host
+ 'ignore_reason': 'peer ignored in update_dhcp',
+ 'dhcp': True, # Found in dhcp file
+ 'ssh_status': False,
+ 'swarming': False,
+ 'doc': False,
+ 'chromeos': False
+ }
+ hostname = '-'.join(peer.split('-')[:4])
+ logging.debug('derived host %s from peername %s', hostname, peer)
+ # host is not in dhcp but peer is
+ if hostname not in device_data:
+ logging.debug('peer %s present in dhcp but host %s is not', peer,
+ hostname)
+ device_data[hostname] = {
+ 'ignore': True, # ignore hosts unless it is a wificell
+ 'ignore_reason':
+ 'host derived from peer ignored in update_dhcp',
+ 'dhcp': False, # Not found in dhcp file
+ 'ssh_status': False,
+ 'swarming': False,
+ 'doc': False,
+ 'pool': None,
+ 'peers': {},
+ 'chromeos': True
+ }
+
+ device_data[hostname]['peers'][peer] = peer_dict
+
+
+def update_swarming_data(device_data, swarming_data):
+ """ update device data with swarming data """
+
+ for h, v in swarming_data.items():
+ if 'chromeos3' in h:
+ logging.debug('Igonring chaos device %s in chromeos3', h)
+ continue
+
+ if v['pool'] in POOLS_TO_IGNORE:
+ logging.debug(' %s is in ignored pool %s', h, v['pool'])
+ continue
+
+ if h not in device_data:
+ logging.error(
+ 'host %s in swarming but not in dhcp. This should never happen',
+ h)
+ device_data[h] = {
+ 'ignore': False, # ignore hosts unless it is a wificell
+ 'ignore_reason':
+ 'wificell host not ignored in update_swarming',
+ 'dhcp': False, # Not Found in dhcp file
+ 'ssh_status': False,
+ 'swarming': True,
+ 'doc': False,
+ 'pool': None,
+ 'peers': {},
+ 'chromeos': True
+ }
+ else:
+ device_data[h]['ignore'] = False
+ device_data[h][
+ 'ignore_reason'] = 'wificell host not ignored in update_swarming',
+ device_data[h]['swarming'] = True
+
+ device_data[h]['pool'] = v['pool']
+ device_data[h]['swarming_data'] = v
+
+ # update status of peer devices
+ # wificell devices always have these peers
+ # except bt_grover pool
+ if v['pool'] != 'bt_groamer':
+ expected_peers = _make_peers(h, WIFI_PEERS)
+
+ # some pools have attenuator
+ if v['pool'] in ATTENUATOR_POOLS:
+ for peer in _make_peers(h, ['attenuator']):
+ expected_peers.append(peer)
+
+ # number of btpeers vary. Get the number from swarming
+ expected_peers.extend(_make_peers(h, BT_PEERS[:len(v['bt_peers'])]))
+
+ # check only servo v3
+ if v['servo']:
+ expected_peers.append(servo)
+
+ logging.debug('Expected peers for host %s is %s', h, expected_peers)
+
+ for peer in expected_peers:
+ if peer not in device_data[h]['peers']:
+ # Peer indicated in swarming data but not in dhcp
+ logging.debug('Peer %s not in dhcp but in swarming', peer)
+ device_data[h]['peers'][peer] = {
+ 'ignore': False, # ignore hosts unless it is a wificell
+ 'ignore_reason':
+ 'peer of wificell host not ignored in update_swarming',
+ 'dhcp': False, # Not found in dhcp file
+ 'ssh_status': False,
+ 'swarming': True,
+ 'doc': False
+ }
+ else:
+ device_data[h]['peers'][peer]['swarming'] = True
+ device_data[h]['peers'][peer]['ignore'] = False
+ device_data[h]['peers'][peer][
+ 'ignore_reason'] = 'peer of wificell host not ignored in update_swarming'
+
+
+def update_conn_doc_data(device_data, conn_doc_data):
+ """ update device data using go/cros_conn_device_lifecyle data"""
+ for h, v in conn_doc_data.items():
+ if h not in device_data:
+ logging.debug(
+ 'host %s not in swarming or dhcp but in go/cros_conn_device_lifecycle',
+ h)
+ device_data[h] = {
+ 'ignore': False, # All DUT in doc is important
+ 'ignore_reason': 'device found in conn_doc',
+ 'dhcp': False, # not found in dhcp file
+ 'ssh_status': False,
+ 'swarming': False, # not found in swarming
+ 'pool': None,
+ 'doc': True,
+ 'peers': {},
+ 'chromeos': True
+ }
+ else:
+ device_data[h]['doc'] = True
+ device_data[h]['ignore'] = False
+
+ device_data[h]['doc_data'] = v
+
+ # Ignore this host and peers
+ # Used for test bed until construction
+ ignore_test_bed = False
+
+ if device_data[h]['pool'] is None:
+ if v['pool'] in POOLS_TO_IGNORE:
+ logging.debug(
+ 'device %s doc data has pool %s which is to be ignored', h,
+ v['pool'])
+ device_data[h]['ignore'] = True
+ device_data[h]['ignore_reason'] = 'pool ignored'
+ ignore_test_bed = True
+ if v['model'] == '':
+ logging.debug('Empty model. Ignoring %s', h)
+ device_data[h]['ignore'] = True
+ device_data[h]['ignore_reason'] = 'empty model ignored'
+ ignore_test_bed = True
+
+ if '[wip]' in _parse_doc_model_name(v['model']):
+ logging.debug('WIP Ignoring %s', h)
+ device_data[h]['ignore'] = True
+ device_data[h]['ignore_reason'] = 'WIP device ignored'
+ ignore_test_bed = True
+
+ # update status of peers
+ documented_peers = []
+ for i in v['btpeers']:
+ documented_peers.append(h + '-' + i)
+ # bt_groamer doesn't have wifi peersx
+ if 'wificell' in v['labels'] and v['pool'] != 'bt_groamer':
+ for i in WIFI_PEERS:
+ documented_peers.append(h + '-' + i)
+ logging.debug('documented peers for %s is %s', h, documented_peers)
+ if v['pool'] in ATTENUATOR_POOLS:
+ documented_peers.extend(_make_peers(h, ['attenuator']))
+
+ for peer in documented_peers:
+ if peer not in device_data[h]['peers']:
+ logging.debug('%s in doc data but not swarming', peer)
+ device_data[h]['peers'][peer] = {
+ 'ignore':
+ ignore_test_bed, # ignore hosts unless it is a wificell
+ 'ignore_reason':
+ 'peer if dut with ignore_test_bed %s ' % ignore_test_bed,
+ 'dhcp': False, # Not found in dhcp file
+ 'ssh_status': False,
+ 'swarming': False, # Found in swarming
+ 'doc': True,
+ 'chromeos': False
+ }
+ else:
+ device_data[h]['peers'][peer]['doc'] = True
+ device_data[h]['peers'][peer]['ignore'] = ignore_test_bed
+
+
+def check_connectivity(device_data, recheck=False):
+ """ check if device is pingable and sshable"""
+
+ def _add_to_result(result_dict, rhost, result):
+ logging.debug('Adding to result %s %s', rhost, result)
+ if rhost in result_dict:
+ logging.error('rhost %s already present in result', rhost)
+ logging.error('This should not happen###')
+ raise ValueError
+
+ result_dict[rhost] = result
+
+ devices_to_check = {
+ 'hosts': [],
+ 'peers': {},
+ }
+ # Only check devices which are present in DHCP data
+ for host, host_value in device_data.items():
+ if not host_value['ignore'] and host_value['dhcp']:
+ # On recheck, check devices which is not up
+ if not recheck or host_value['ssh_status'] != HOST_UP:
+ devices_to_check['hosts'].append(host)
+ for peer, peer_value in host_value['peers'].items():
+ if not peer_value['ignore'] and peer_value['dhcp']:
+ if not recheck or peer_value['ssh_status'] != HOST_UP:
+ devices_to_check['peers'][peer] = host
+
+ device_list = devices_to_check['hosts'][:]
+ device_list.extend(list(devices_to_check['peers'].keys()))
+
+ #
+ # GetHostStatus function is called in separate process for each dut
+ # Each of these process put the result in a queue
+ # THe main process get results from queue and joins the processes
+ # The processes was getting hung probably since the queue was growing large
+ # Adding code to remove items from the queue resolved the issue
+ #
+
+ q = Queue(32000)
+ result_dict = {}
+ process_list = []
+ count = 0
+ for host in device_list:
+ p = Process(target=getHostStatus, args=(q, host))
+ p.start()
+ process_list.append((p, host))
+ logging.debug('starting check %s %s', host, count)
+ count += 1
+
+ try:
+ (rhost, result) = q.get(block=False)
+ _add_to_result(result_dict, rhost, result)
+ except queue.Empty:
+ pass
+
+ while process_list != []:
+ logging.info('{} processes remaining '.format(len(process_list)))
+ logging.debug(' process list %s result %s queue size %s ',
+ len(process_list), len(result_dict), q.qsize())
+ for (p, host) in process_list:
+ # empty queue to prevent the proceess from hanging
+ try:
+ (rhost, result) = q.get(block=False)
+ _add_to_result(result_dict, rhost, result)
+ except queue.Empty:
+ pass
+
+ if not p.is_alive():
+ logging.info('{} process has ended'.format(host))
+ p.join()
+ process_list.remove((p, host))
+ else:
+ logging.info('{} process pending'.format(host))
+ logging.debug('sleeping for 3 seconds')
+ time.sleep(3)
+
+ while not q.empty():
+ (rhost, result) = q.get(timeout=2)
+ _add_to_result(result_dict, rhost, result)
+
+ if len(result_dict) != len(device_list):
+ logging.error(
+ 'Length of result %s is not equal to length'
+ 'of device list %s', len(result_dict), len(device_list))
+ for h in result_dict:
+ if h not in device_list:
+ logging.error('%s not in device_list', h)
+ for h in device_list:
+ if h not in result_dict:
+ logging.error('%s not in result', h)
+
+ raise ValueError
+
+ _pretty_print(result_dict, 'result_dict')
+
+ for h in devices_to_check['hosts']:
+ device_data[h]['ssh_status'] = result_dict[h]
+
+ for p, h in devices_to_check['peers'].items():
+ device_data[h]['peers'][p]['ssh_status'] = result_dict[p]
+
+
+# error conditions
+IGNORED = 'IGNORED'
+IMPOSSIBLE = 'ERROR' # Impossible combination like device not in DHCP but ssh-able
+NOT_DOCUMENTED = 'NOT DOCUMENTED'
+NOT_IN_DHCP = 'NOT IN DHCP BUT DOCUMENTED'
+NOT_IN_SWARMING = 'NOT IN SWARMING BUT IN DHCP'
+NOT_REACHABLE = 'NOT PINGABLE OR SSH-ABLE'
+IN_SWARMING_NOT_IN_DHCP = 'IN SWARMING BUT NOT IN DHCP FILE '
+ONLINE_BUT_NOT_IN_DHCP = 'DEVICE IS UP BUT NOT IN DHCP FILE!'
+ALL_OK = 'UP'
+
+BAD_STATES = [
+ NOT_REACHABLE, NOT_IN_SWARMING, NOT_DOCUMENTED, IMPOSSIBLE, NOT_IN_DHCP,
+ IN_SWARMING_NOT_IN_DHCP, ONLINE_BUT_NOT_IN_DHCP
+]
+
+# ignore, dhcp, ssh swarming, doc : result
+error_dict = {
+ (False, False, False, False, False): IMPOSSIBLE,
+ (False, False, False, False, True): NOT_IN_DHCP,
+ (False, False, False, True, False): IN_SWARMING_NOT_IN_DHCP,
+ (False, False, False, True, True): IN_SWARMING_NOT_IN_DHCP,
+ (False, False, True, False, False): ONLINE_BUT_NOT_IN_DHCP,
+ (False, False, True, False, True): ONLINE_BUT_NOT_IN_DHCP,
+ (False, False, True, True, False): IN_SWARMING_NOT_IN_DHCP,
+ (False, False, True, True, True): IN_SWARMING_NOT_IN_DHCP,
+ (False, True, False, False, False): NOT_REACHABLE,
+ (False, True, False, False, True): NOT_REACHABLE,
+ (False, True, False, True, False): NOT_REACHABLE,
+ (False, True, False, True, True): NOT_REACHABLE,
+ (False, True, True, False, False): NOT_IN_SWARMING,
+ (False, True, True, False, True): NOT_IN_SWARMING,
+ (False, True, True, True, False): NOT_DOCUMENTED,
+ (False, True, True, True, True): ALL_OK,
+ (True, False, False, False, False): IGNORED,
+ (True, False, False, False, True): IGNORED,
+ (True, False, False, True, False): IGNORED,
+ (True, False, False, True, True): IGNORED,
+ (True, False, True, False, False): IGNORED,
+ (True, False, True, False, True): IGNORED,
+ (True, False, True, True, False): IGNORED,
+ (True, False, True, True, True): IGNORED,
+ (True, True, False, False, False): IGNORED,
+ (True, True, False, False, True): IGNORED,
+ (True, True, False, True, False): IGNORED,
+ (True, True, False, True, True): IGNORED,
+ (True, True, True, False, False): IGNORED,
+ (True, True, True, False, True): IGNORED,
+ (True, True, True, True, False): IGNORED,
+ (True, True, True, True, True): IGNORED,
+}
+
+
+def generate_dashboard(device_data):
+ """ Analyses device_data and prepare result to be populated in dashboard"""
+
+ for host, hv in device_data.items():
+ logging.debug(host)
+ _pretty_print(hv)
+
+ peer_error_found = False # Unreachable peer which should be flagged in main dashboard
+ issue_found = False # Any other issue which is displayed in secondary dashboard
+
+ hv['device_status'] = error_dict[(hv['ignore'], hv['dhcp'],
+ not (hv['ssh_status'] == HOST_DOWN),
+ hv['swarming'], hv['doc'])]
+ logging.debug(
+ 'ignore %s dhcp %s swarming %s ssh_status %s not ssh_status == HOST_DOWN %s doc %s status %s',
+ hv['ignore'], hv['dhcp'], hv['swarming'], hv['ssh_status'],
+ not (hv['ssh_status'] == HOST_DOWN), hv['doc'],
+ hv['device_status'])
+ logging.debug(error_dict[(False, True, True, False, True)])
+
+ # main dashboard need not show status of DUT since there is a separate dashboard for that.
+ if hv['device_status'] != IGNORED and hv['device_status'] in BAD_STATES:
+ issue_found = True
+
+ logging.debug('device status is %s', hv['device_status'])
+
+ if 'peers' in hv.keys():
+ for peer, pv in hv['peers'].items():
+ logging.debug(peer)
+ logging.debug(pv)
+ pv['device_status'] = error_dict[(
+ pv['ignore'], pv['dhcp'],
+ not (pv['ssh_status'] == HOST_DOWN), pv['swarming'],
+ pv['doc'])]
+ logging.debug(
+ 'ignore %s dhcp %s swarming %s ssh_status %s not(ssh_status == HOST_DOWN) %s doc %s',
+ pv['ignore'], pv['dhcp'], pv['swarming'], pv['ssh_status'],
+ not (pv['ssh_status'] == HOST_DOWN), pv['doc'])
+
+ # If the host is ignored then do not show it in the dashboard
+ if hv['device_status'] == IGNORED:
+ logging.debug('device status is %s ignoring %s',
+ hv['device_status'], host)
+ issue_found = issue_found or pv[
+ 'device_status'] in BAD_STATES
+ else:
+ peer_error_found = peer_error_found or pv[
+ 'device_status'] == NOT_REACHABLE
+ logging.debug('device status is %s', pv['device_status'])
+
+ # Documentation errors
+ hv['documentation_errors'] = []
+ # check only chromeos devices and avoid ignored devices
+ if hv['device_status'] != IGNORED and hv['chromeos']:
+ # model/boards of host in go/conn-device-lifecycle is different from swarming
+ if hv['swarming'] and hv['doc']:
+ if hv['swarming_data']['model'] not in _parse_doc_model_name(
+ hv['doc_data']['model']):
+ hv['documentation_errors'].append(
+ 'model in swarming "%s" differs from model in doc "%s"'
+ % (hv['swarming_data']['model'],
+ hv['doc_data']['model']))
+ if hv['swarming_data']['board'] != hv['doc_data'][
+ 'board'].strip():
+ hv['documentation_errors'].append(
+ 'board in swarming "%s" differs from board in doc "%s"'
+ % (hv['swarming_data']['board'],
+ hv['doc_data']['board']))
+ # Pool differs
+ if hv['swarming'] and hv['doc']:
+ if hv['swarming_data']['pool'] != hv['doc_data']['pool']:
+ hv['documentation_errors'].append(
+ 'pool in swarming "%s" differs from pool in doc "%s"' %
+ (hv['swarming_data']['pool'], hv['doc_data']['pool']))
+ # wificell / conductive label differ
+ if hv['swarming'] and hv['doc']:
+ if hv['swarming_data']['wificell'] != (
+ 'wificell' in hv['doc_data']['labels']):
+ hv['documentation_errors'].append(
+ 'label:wificell differs between doc and swarming')
+ _pretty_print(hv)
+ logging.debug('label wificell discrepencise %s %s',
+ hv['swarming_data']['wificell'],
+ 'wificell' in hv['doc_data']['labels'])
+ # bluetooth label not found
+ if hv['swarming'] and not hv['swarming_data']['bluetooth_label']:
+ hv['documentation_errors'].append('Bluetooth label not found')
+
+ if hv['documentation_errors'] != []:
+ logging.debug(hv['documentation_errors'])
+
+ hv['peer_error_found'] = peer_error_found
+ hv['issue_found'] = issue_found
+ _pretty_print(device_data)
+
+ logging.debug('## IGNORED devices')
+ for host, hv in device_data.items():
+ if hv['device_status'] == IGNORED:
+ logging.debug('IGNORED DEVICE %s', (host))
+ _pretty_print(hv)
+ logging.debug('## IGNORED devices END')
+
+ logging.debug('## IMPOSSIBLE devices')
+ for host, hv in device_data.items():
+ if hv['device_status'] == IMPOSSIBLE:
+ logging.debug('IMPOSSIBLE DEVICE %s', (host))
+ _pretty_print(hv)
+ logging.debug('## IMPOSSIBLE devices END')
+
+
+def populate_dashboard(spreadsheet_name, device_data):
+ def _find_header(d):
+ """ given list of dicts,find all keys"""
+ header = [
+ 'pool',
+ 'host',
+ 'model',
+ 'host_status',
+ ]
+ peer_header = []
+ for _, v in d.items():
+ if 'peers' in v:
+ for p, pv in v['peers'].items():
+ if pv['ignore']:
+ continue
+ logging.debug(p)
+ peer_suffix = p.split('-')[4]
+ if peer_suffix not in peer_header:
+ peer_header.append(peer_suffix)
+ peer_header.sort()
+ header.extend(peer_header)
+ logging.debug('header is %s', header)
+ return header
+
+ def _populate_document_sheet(wsheet, msgs, header, data):
+ row_count = 1
+ for i, m in enumerate(msgs):
+ wsheet.insert_row(m.split(' '), i + row_count)
+ logging.debug('Writing %s at %s', m, i + row_count)
+
+ row_count += len(msgs)
+ wsheet.insert_row([h.upper() for h in header], row_count)
+ logging.debug('writing header at %s', row_count)
+ wsheet.format(
+ 'A%s:S%s' % (row_count, row_count),
+ {'backgroundColor': {
+ 'red': 0.0,
+ 'green': 0.5,
+ 'blue': 0.5
+ }})
+
+ row_count += 1
+
+ row_length = 12
+
+ cell_start_index = row_count
+ cell_end_index = cell_start_index + len(data)
+ range_label = 'A%s:%s%s' % (cell_start_index,
+ '-ABCDEFGHIJKLMNOPQR' [row_length],
+ cell_end_index)
+ logging.debug('range_label %s', range_label)
+ cell_list = wsheet.range(range_label)
+ logging.debug('cell_list Info: %s', (cell_list))
+ cell_list_index = 0
+
+ host_list = list(data.keys())
+ host_list.sort()
+ for host in host_list:
+ hv = data[host]
+ if hv['documentation_errors'] == []:
+ continue
+ logging.debug('%s %s', host, hv['documentation_errors'])
+ _pretty_print(hv)
+ cell_list[cell_list_index].value = hv['pool']
+ cell_list_index += 1
+ cell_list[cell_list_index].value = host
+ cell_list_index += 1
+ cell_list[cell_list_index].value = hv['swarming_data'][
+ 'model'] if hv['swarming'] else '--'
+ cell_list_index += 1
+
+ logging.debug(
+ '%s %s %s %s', hv['pool'], host,
+ hv['swarming_data']['model'] if hv['swarming'] else '--',
+ hv['documentation_errors'])
+ for e in hv['documentation_errors']:
+ cell_list[cell_list_index].value = e
+ cell_list_index += 1
+ for i in range(3 + len(hv['documentation_errors']), row_length):
+ cell_list[cell_list_index].value = ''
+ cell_list_index += 1
+ wsheet.update_cells(cell_list)
+
+ def _populate_lab_sheet(wsheet,
+ msgs,
+ header,
+ data,
+ error_field='peer_error_found'):
+ row_count = 1
+ for i, m in enumerate(msgs):
+ wsheet.insert_row(m.split(' '), i + row_count)
+ logging.debug('Writing %s at %s', m, i + row_count)
+
+ row_count += len(msgs)
+
+ wsheet.insert_row([h.upper() for h in header], row_count)
+ logging.debug('writing header at %s', row_count)
+ wsheet.format(
+ 'A%s:S%s' % (row_count, row_count),
+ {'backgroundColor': {
+ 'red': 0.0,
+ 'green': 0.5,
+ 'blue': 0.5
+ }})
+ row_count += 1
+
+ cell_start_index = row_count
+ cell_end_index = cell_start_index + len(data)
+ range_label = 'A%s:%s%s' % (cell_start_index,
+ '-ABCDEFGHIJKLMNOPQR' [len(header)],
+ cell_end_index)
+ logging.debug('range_label %s', range_label)
+ cell_list = wsheet.range(range_label)
+ logging.debug('cell_list Info: %s', (cell_list))
+ cell_list_index = 0
+
+ host_list = list(data.keys())
+ host_list.sort()
+ for host in host_list:
+ hv = data[host]
+ if not hv[error_field]:
+ continue
+ if 'rpm' in host:
+ print('error found')
+ logging.debug('%s %s', host, hv['device_status'])
+ logging.debug('%s %s', host, hv['device_status'])
+ _pretty_print(hv)
+ cell_list[cell_list_index].value = hv['pool']
+ cell_list_index += 1
+ cell_list[cell_list_index].value = host
+ cell_list_index += 1
+ cell_list[cell_list_index].value = hv['swarming_data'][
+ 'model'] if hv['swarming'] else '--'
+ cell_list_index += 1
+ cell_list[cell_list_index].value = hv['ssh_status'] if hv[
+ 'device_status'] == NOT_REACHABLE else hv['device_status']
+ cell_list_index += 1
+ logging.debug('%s %s %s', hv['pool'], host, hv['device_status'])
+
+ for suffix in header[4:]:
+ peername = host + '-' + suffix
+ if 'peers' in hv and peername in hv['peers']:
+ cell_list[cell_list_index].value = hv['peers'][peername][
+ 'ssh_status'] if hv['peers'][peername][
+ 'device_status'] == NOT_REACHABLE else hv['peers'][
+ peername]['device_status']
+ logging.debug('%s %s', peername,
+ hv['peers'][peername]['device_status'])
+ else:
+ cell_list[cell_list_index].value = '--'
+ logging.debug('peername not found %s', peername)
+ cell_list_index += 1
+
+ wsheet.update_cells(cell_list)
+
+ """ Display the data in the dashboard"""
+ scope = [
+ 'https://spreadsheets.google.com/feeds',
+ 'https://www.googleapis.com/auth/drive'
+ ]
+ credentials = ServiceAccountCredentials.from_json_keyfile_name(
+ json_keyfile, scope)
+ gc = gspread.authorize(credentials)
+ spreadsheet = gc.open(spreadsheet_name)
+
+ worksheet = 'DOWN PEERS'
+ wsheet1 = spreadsheet.worksheet(worksheet)
+ wsheet1.clear()
+ wsheet1.format(
+ 'A1:S1000',
+ {'backgroundColor': {
+ 'red': 1.0,
+ 'green': 1.0,
+ 'blue': 1.0
+ }})
+
+ worksheet = 'DOCUMENTATION ERRORS'
+ wsheet2 = spreadsheet.worksheet(worksheet)
+ wsheet2.clear()
+ wsheet2.format(
+ 'A1:S1000',
+ {'backgroundColor': {
+ 'red': 1.0,
+ 'green': 1.0,
+ 'blue': 1.0
+ }})
+
+ worksheet = 'OTHER ERRORS'
+ wsheet3 = spreadsheet.worksheet(worksheet)
+ wsheet3.clear()
+ wsheet3.format(
+ 'A1:S1000',
+ {'backgroundColor': {
+ 'red': 1.0,
+ 'green': 1.0,
+ 'blue': 1.0
+ }})
+
+ lab_issues, documentation_issues, other_issues = False, False, False
+ lab_messages = []
+ doc_messages = []
+ other_messages = []
+
+ for k, v in device_data.items():
+ if v['peer_error_found']:
+ lab_issues = True
+ if v['documentation_errors'] != []:
+ documentation_issues = True
+ if v['issue_found']:
+ other_issues = True
+ if lab_issues and documentation_issues and other_issues:
+ break
+
+ if not lab_issues:
+ lab_messages = ['No Issues Found. Check other tabs']
+ if not documentation_issues:
+ doc_messages = ['No Issues Found. Check other tabs']
+ if not other_issues:
+ other_messages = ['No Issues Found. Check other tabs']
+
+ messages = [
+ 'LAST_UPDATED_AT %s' % str(datetime.datetime.now()),
+ 'NEXT_UPDATE_WILL_BE_AT %s' %
+ (str(datetime.datetime.now() +
+ datetime.timedelta(seconds=DASHBOARD_REFRESH_INTERVAL)))
+ ]
+
+ lab_messages.extend(messages)
+ _pretty_print(lab_messages)
+ doc_messages.extend(messages)
+ _pretty_print(doc_messages)
+ other_messages.extend(messages)
+ _pretty_print(other_messages)
+
+ logging.debug('writing the lab sheet')
+ header = _find_header(device_data)
+ _populate_lab_sheet(wsheet1, lab_messages, header, device_data)
+
+ logging.debug('writing the other sheet')
+ header = _find_header(device_data)
+ _populate_lab_sheet(wsheet3,
+ lab_messages,
+ header,
+ device_data,
+ error_field='issue_found')
+
+ logging.debug('writing the document sheet')
+ header = ['pool', 'host', 'model', 'Documentation errors']
+ _populate_document_sheet(wsheet2, doc_messages, header, device_data)
+ logging.debug('populate_dashboard_complete')
+
+
+def dict_diff(s1, s2):
+ if type(s1) == dict and type(s2) == dict:
+ for k in s1:
+ if k not in s2:
+ print('key %s missing in second' % k)
+ for k in s2:
+ if k not in s1:
+ print('key %s missing in first' % k)
+
+ for k, v1 in s1.items():
+ if k in s2:
+ if type(v1) == dict:
+ dict_diff(v1, s2[k])
+ elif v1 != s2[k]:
+ logging.debug('value %s %s differs', v1, s2[k])
+
+
+def debug_main():
+ """ Debug version of Main function """
+
+ device_data = {}
+
+ #Read the list of rpms to check
+ rpm_list = get_rpm_list()
+ update_rpm_data(device_data, rpm_list)
+
+ # Get dhcp data and update device data
+ (hosts, peer_devices, other_devices) = get_dhcp_data.get_data()
+ update_dhcp_data(device_data, hosts, peer_devices, other_devices)
+ logging.debug("After update_dhcp_data")
+ _pretty_print(device_data)
+ input()
+
+ # use this to debug the script without getting swarming data everytime
+ with open('/tmp/skylab_hosts.json') as json_file:
+ swarming_data = json.load(json_file)
+ _pretty_print(swarming_data)
+
+ # Get swarming data and update device_data
+ #swarming_data = get_wificell_data.get_data()
+ #_pretty_print(swarming_data)
+
+ update_swarming_data(device_data, swarming_data)
+ _pretty_print(device_data)
+ logging.debug("After update_swarming_data")
+ input()
+
+ # Get data from g/cros_conn_device_lifecycle and updat device data
+ conn_doc_data = get_wifisheet_data.get_wifisheet_data()
+ update_conn_doc_data(device_data, conn_doc_data)
+ _pretty_print(device_data)
+ logging.debug("After update_conn_data")
+ input()
+
+ with open('data.txt', 'w') as outfile:
+ json.dump(device_data, outfile)
+
+ with open('data.txt') as json_file:
+ device_data = json.load(json_file)
+
+ check_connectivity(device_data)
+ logging.debug("After check_connectivity")
+ input()
+ logging.info('Waiting for 2 seconds before checking connectivity again')
+ time.sleep(2)
+ check_connectivity(device_data, recheck=True)
+ logging.debug("After check_connectivity recheck")
+ input()
+
+ with open('data2.txt', 'w') as outfile:
+ json.dump(device_data, outfile)
+
+ with open('data2.txt') as json_file:
+ device_data = json.load(json_file)
+
+ _pretty_print(device_data)
+ generate_dashboard(device_data)
+ logging.debug("After generate_dashboard")
+ input()
+ populate_dashboard(SPREADSHEET_ALL, device_data)
+ logging.debug("After populate_dashboard")
+ input()
+
+
+def main():
+ """ Main function """
+
+ device_data = {}
+
+ #Read the list of rpms to check
+ rpm_list = get_rpm_list()
+ update_rpm_data(device_data, rpm_list)
+
+ # Get dhcp data and update device data
+ (hosts, peer_devices, other_devices) = get_dhcp_data.get_data()
+ update_dhcp_data(device_data, hosts, peer_devices, other_devices)
+
+ # Get swarming data and update device_data
+ swarming_data = get_wificell_data.get_data()
+ update_swarming_data(device_data, swarming_data)
+
+ # Get data from g/cros_conn_device_lifecycle and updat device data
+ conn_doc_data = get_wifisheet_data.get_wifisheet_data()
+ update_conn_doc_data(device_data, conn_doc_data)
+
+ # Check connectivity of devices
+ check_connectivity(device_data)
+ logging.info('Waiting for %s seconds before checking connectivity again',
+ CONNECTIVITY_RETEST_INTERVAL)
+ time.sleep(CONNECTIVITY_RETEST_INTERVAL)
+ check_connectivity(device_data, recheck=True)
+
+ generate_dashboard(device_data)
+ populate_dashboard(SPREADSHEET_ALL, device_data)
+
+ _pretty_print(device_data)
+
+
+if __name__ == '__main__':
+ if int(sys.version.split(' ')[0].split('.')[0]) != 3:
+ print('Please invoke with python3')
+ sys.exit()
+ while True:
+ try:
+ logging.debug('Ctrl-C to stop')
+ main()
+ #debug_main()
+ logging.debug('Sleeping for %s seconds',
+ DASHBOARD_REFRESH_INTERVAL)
+ time.sleep(DASHBOARD_REFRESH_INTERVAL)
+ except KeyboardInterrupt:
+ sys.exit()
+ except Exception as e:
+ logging.error(
+ 'Exception %s while running script. Press any key to continue',
+ str(e))
+ input()
+ logging.debug('Sleeping for %s seconds',
+ DASHBOARD_REFRESH_INTERVAL)
+ time.sleep(DASHBOARD_REFRESH_INTERVAL)
diff --git a/provingground/connectivity/wifidown_dashboard/rpm_list.py b/provingground/connectivity/wifidown_dashboard/rpm_list.py
new file mode 100644
index 0000000..88041f5
--- /dev/null
+++ b/provingground/connectivity/wifidown_dashboard/rpm_list.py
@@ -0,0 +1,14 @@
+# Copyright 2021 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.
+
+# List of rpms used in chaos lab
+# This list is hardcoded since these are the only
+# rpms that we care about
+
+rpm_list = [
+ 'chromeos3-row2-rack1-rpm1', 'chromeos3-row2-rack2-rpm1',
+ 'chromeos3-row2-rack3-rpm1', 'chromeos3-row2-rack4-rpm1',
+ 'chromeos3-row3-rack1-rpm1', 'chromeos3-row3-rack2-rpm1',
+ 'chromeos3-row3-rack3-rpm1', 'chromeos3-row3-rack4-rpm1'
+]