1*800a58d9SAndroid Build Coastguard Worker#!/usr/bin/env python 2*800a58d9SAndroid Build Coastguard Worker# 3*800a58d9SAndroid Build Coastguard Worker# Copyright 2016 - The Android Open Source Project 4*800a58d9SAndroid Build Coastguard Worker# 5*800a58d9SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*800a58d9SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*800a58d9SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*800a58d9SAndroid Build Coastguard Worker# 9*800a58d9SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*800a58d9SAndroid Build Coastguard Worker# 11*800a58d9SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*800a58d9SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*800a58d9SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*800a58d9SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*800a58d9SAndroid Build Coastguard Worker# limitations under the License. 16*800a58d9SAndroid Build Coastguard Worker 17*800a58d9SAndroid Build Coastguard Worker"""Command report. 18*800a58d9SAndroid Build Coastguard Worker 19*800a58d9SAndroid Build Coastguard WorkerReport class holds the results of a command execution. 20*800a58d9SAndroid Build Coastguard WorkerEach driver API call will generate a report instance. 21*800a58d9SAndroid Build Coastguard Worker 22*800a58d9SAndroid Build Coastguard WorkerIf running the CLI of the driver, a report will 23*800a58d9SAndroid Build Coastguard Workerbe printed as logs. And it will also be dumped to a json file 24*800a58d9SAndroid Build Coastguard Workerif requested via command line option. 25*800a58d9SAndroid Build Coastguard Worker 26*800a58d9SAndroid Build Coastguard WorkerThe json format of a report dump looks like: 27*800a58d9SAndroid Build Coastguard Worker 28*800a58d9SAndroid Build Coastguard Worker - A failed "delete" command: 29*800a58d9SAndroid Build Coastguard Worker { 30*800a58d9SAndroid Build Coastguard Worker "command": "delete", 31*800a58d9SAndroid Build Coastguard Worker "data": {}, 32*800a58d9SAndroid Build Coastguard Worker "errors": [ 33*800a58d9SAndroid Build Coastguard Worker "Can't find instances: ['104.197.110.255']" 34*800a58d9SAndroid Build Coastguard Worker ], 35*800a58d9SAndroid Build Coastguard Worker "error_type": "error_type_1", 36*800a58d9SAndroid Build Coastguard Worker "status": "FAIL" 37*800a58d9SAndroid Build Coastguard Worker } 38*800a58d9SAndroid Build Coastguard Worker 39*800a58d9SAndroid Build Coastguard Worker - A successful "create" command: 40*800a58d9SAndroid Build Coastguard Worker { 41*800a58d9SAndroid Build Coastguard Worker "command": "create", 42*800a58d9SAndroid Build Coastguard Worker "data": { 43*800a58d9SAndroid Build Coastguard Worker "devices": [ 44*800a58d9SAndroid Build Coastguard Worker { 45*800a58d9SAndroid Build Coastguard Worker "instance_name": "instance_1", 46*800a58d9SAndroid Build Coastguard Worker "ip": "104.197.62.36" 47*800a58d9SAndroid Build Coastguard Worker }, 48*800a58d9SAndroid Build Coastguard Worker { 49*800a58d9SAndroid Build Coastguard Worker "instance_name": "instance_2", 50*800a58d9SAndroid Build Coastguard Worker "ip": "104.197.62.37" 51*800a58d9SAndroid Build Coastguard Worker } 52*800a58d9SAndroid Build Coastguard Worker ] 53*800a58d9SAndroid Build Coastguard Worker }, 54*800a58d9SAndroid Build Coastguard Worker "errors": [], 55*800a58d9SAndroid Build Coastguard Worker "status": "SUCCESS" 56*800a58d9SAndroid Build Coastguard Worker } 57*800a58d9SAndroid Build Coastguard Worker""" 58*800a58d9SAndroid Build Coastguard Worker 59*800a58d9SAndroid Build Coastguard Workerimport json 60*800a58d9SAndroid Build Coastguard Workerimport logging 61*800a58d9SAndroid Build Coastguard Workerimport os 62*800a58d9SAndroid Build Coastguard Worker 63*800a58d9SAndroid Build Coastguard Workerfrom acloud.internal import constants 64*800a58d9SAndroid Build Coastguard Worker 65*800a58d9SAndroid Build Coastguard Worker 66*800a58d9SAndroid Build Coastguard Workerlogger = logging.getLogger(__name__) 67*800a58d9SAndroid Build Coastguard Worker 68*800a58d9SAndroid Build Coastguard Worker 69*800a58d9SAndroid Build Coastguard Workerclass Status(): 70*800a58d9SAndroid Build Coastguard Worker """Status of acloud command.""" 71*800a58d9SAndroid Build Coastguard Worker 72*800a58d9SAndroid Build Coastguard Worker SUCCESS = "SUCCESS" 73*800a58d9SAndroid Build Coastguard Worker FAIL = "FAIL" 74*800a58d9SAndroid Build Coastguard Worker BOOT_FAIL = "BOOT_FAIL" 75*800a58d9SAndroid Build Coastguard Worker UNKNOWN = "UNKNOWN" 76*800a58d9SAndroid Build Coastguard Worker 77*800a58d9SAndroid Build Coastguard Worker SEVERITY_ORDER = {UNKNOWN: 0, SUCCESS: 1, FAIL: 2, BOOT_FAIL: 3} 78*800a58d9SAndroid Build Coastguard Worker 79*800a58d9SAndroid Build Coastguard Worker @classmethod 80*800a58d9SAndroid Build Coastguard Worker def IsMoreSevere(cls, candidate, reference): 81*800a58d9SAndroid Build Coastguard Worker """Compare the severity of two statuses. 82*800a58d9SAndroid Build Coastguard Worker 83*800a58d9SAndroid Build Coastguard Worker Args: 84*800a58d9SAndroid Build Coastguard Worker candidate: One of the statuses. 85*800a58d9SAndroid Build Coastguard Worker reference: One of the statuses. 86*800a58d9SAndroid Build Coastguard Worker 87*800a58d9SAndroid Build Coastguard Worker Returns: 88*800a58d9SAndroid Build Coastguard Worker True if candidate is more severe than reference, 89*800a58d9SAndroid Build Coastguard Worker False otherwise. 90*800a58d9SAndroid Build Coastguard Worker 91*800a58d9SAndroid Build Coastguard Worker Raises: 92*800a58d9SAndroid Build Coastguard Worker ValueError: if candidate or reference is not a known state. 93*800a58d9SAndroid Build Coastguard Worker """ 94*800a58d9SAndroid Build Coastguard Worker if (candidate not in cls.SEVERITY_ORDER or 95*800a58d9SAndroid Build Coastguard Worker reference not in cls.SEVERITY_ORDER): 96*800a58d9SAndroid Build Coastguard Worker raise ValueError( 97*800a58d9SAndroid Build Coastguard Worker "%s or %s is not recognized." % (candidate, reference)) 98*800a58d9SAndroid Build Coastguard Worker return cls.SEVERITY_ORDER[candidate] > cls.SEVERITY_ORDER[reference] 99*800a58d9SAndroid Build Coastguard Worker 100*800a58d9SAndroid Build Coastguard Worker 101*800a58d9SAndroid Build Coastguard Workerdef LogFile(path, log_type, name=None): 102*800a58d9SAndroid Build Coastguard Worker """Create a log entry that can be added to the report. 103*800a58d9SAndroid Build Coastguard Worker 104*800a58d9SAndroid Build Coastguard Worker Args: 105*800a58d9SAndroid Build Coastguard Worker path: A string, the local or remote path to the log file. 106*800a58d9SAndroid Build Coastguard Worker log_type: A string, the type of the log file. 107*800a58d9SAndroid Build Coastguard Worker name: A string, the optional entry name. 108*800a58d9SAndroid Build Coastguard Worker 109*800a58d9SAndroid Build Coastguard Worker Returns: 110*800a58d9SAndroid Build Coastguard Worker The log entry as a dictionary. 111*800a58d9SAndroid Build Coastguard Worker """ 112*800a58d9SAndroid Build Coastguard Worker log = {"path": path, "type": log_type} 113*800a58d9SAndroid Build Coastguard Worker if name: 114*800a58d9SAndroid Build Coastguard Worker log["name"] = name 115*800a58d9SAndroid Build Coastguard Worker return log 116*800a58d9SAndroid Build Coastguard Worker 117*800a58d9SAndroid Build Coastguard Worker 118*800a58d9SAndroid Build Coastguard Workerclass Report(): 119*800a58d9SAndroid Build Coastguard Worker """A class that stores and generates report.""" 120*800a58d9SAndroid Build Coastguard Worker 121*800a58d9SAndroid Build Coastguard Worker def __init__(self, command): 122*800a58d9SAndroid Build Coastguard Worker """Initialize. 123*800a58d9SAndroid Build Coastguard Worker 124*800a58d9SAndroid Build Coastguard Worker Args: 125*800a58d9SAndroid Build Coastguard Worker command: A string, name of the command. 126*800a58d9SAndroid Build Coastguard Worker """ 127*800a58d9SAndroid Build Coastguard Worker self.command = command 128*800a58d9SAndroid Build Coastguard Worker self.status = Status.UNKNOWN 129*800a58d9SAndroid Build Coastguard Worker self.errors = [] 130*800a58d9SAndroid Build Coastguard Worker self.error_type = "" 131*800a58d9SAndroid Build Coastguard Worker self.data = {} 132*800a58d9SAndroid Build Coastguard Worker 133*800a58d9SAndroid Build Coastguard Worker def AddData(self, key, value): 134*800a58d9SAndroid Build Coastguard Worker """Add a key-val to the report. 135*800a58d9SAndroid Build Coastguard Worker 136*800a58d9SAndroid Build Coastguard Worker Args: 137*800a58d9SAndroid Build Coastguard Worker key: A key of basic type. 138*800a58d9SAndroid Build Coastguard Worker value: A value of any json compatible type. 139*800a58d9SAndroid Build Coastguard Worker """ 140*800a58d9SAndroid Build Coastguard Worker self.data.setdefault(key, []).append(value) 141*800a58d9SAndroid Build Coastguard Worker 142*800a58d9SAndroid Build Coastguard Worker def UpdateData(self, dict_data): 143*800a58d9SAndroid Build Coastguard Worker """Update a dict data to the report. 144*800a58d9SAndroid Build Coastguard Worker 145*800a58d9SAndroid Build Coastguard Worker Args: 146*800a58d9SAndroid Build Coastguard Worker dict_data: A dict of report data. 147*800a58d9SAndroid Build Coastguard Worker """ 148*800a58d9SAndroid Build Coastguard Worker self.data.update(dict_data) 149*800a58d9SAndroid Build Coastguard Worker 150*800a58d9SAndroid Build Coastguard Worker def AddError(self, error): 151*800a58d9SAndroid Build Coastguard Worker """Add error message. 152*800a58d9SAndroid Build Coastguard Worker 153*800a58d9SAndroid Build Coastguard Worker Args: 154*800a58d9SAndroid Build Coastguard Worker error: A string. 155*800a58d9SAndroid Build Coastguard Worker """ 156*800a58d9SAndroid Build Coastguard Worker self.errors.append(error) 157*800a58d9SAndroid Build Coastguard Worker 158*800a58d9SAndroid Build Coastguard Worker def AddErrors(self, errors): 159*800a58d9SAndroid Build Coastguard Worker """Add a list of error messages. 160*800a58d9SAndroid Build Coastguard Worker 161*800a58d9SAndroid Build Coastguard Worker Args: 162*800a58d9SAndroid Build Coastguard Worker errors: A list of string. 163*800a58d9SAndroid Build Coastguard Worker """ 164*800a58d9SAndroid Build Coastguard Worker self.errors.extend(errors) 165*800a58d9SAndroid Build Coastguard Worker 166*800a58d9SAndroid Build Coastguard Worker def SetErrorType(self, error_type): 167*800a58d9SAndroid Build Coastguard Worker """Set error type. 168*800a58d9SAndroid Build Coastguard Worker 169*800a58d9SAndroid Build Coastguard Worker Args: 170*800a58d9SAndroid Build Coastguard Worker error_type: String of error type. 171*800a58d9SAndroid Build Coastguard Worker """ 172*800a58d9SAndroid Build Coastguard Worker self.error_type = error_type 173*800a58d9SAndroid Build Coastguard Worker 174*800a58d9SAndroid Build Coastguard Worker def SetStatus(self, status): 175*800a58d9SAndroid Build Coastguard Worker """Set status. 176*800a58d9SAndroid Build Coastguard Worker 177*800a58d9SAndroid Build Coastguard Worker Args: 178*800a58d9SAndroid Build Coastguard Worker status: One of the status in Status. 179*800a58d9SAndroid Build Coastguard Worker """ 180*800a58d9SAndroid Build Coastguard Worker if Status.IsMoreSevere(status, self.status): 181*800a58d9SAndroid Build Coastguard Worker self.status = status 182*800a58d9SAndroid Build Coastguard Worker else: 183*800a58d9SAndroid Build Coastguard Worker logger.debug( 184*800a58d9SAndroid Build Coastguard Worker "report: Current status is %s, " 185*800a58d9SAndroid Build Coastguard Worker "requested to update to a status with lower severity %s, ignored.", 186*800a58d9SAndroid Build Coastguard Worker self.status, status) 187*800a58d9SAndroid Build Coastguard Worker 188*800a58d9SAndroid Build Coastguard Worker def AddDevice(self, instance_name, ip_address, adb_port, vnc_port, 189*800a58d9SAndroid Build Coastguard Worker webrtc_port=None, device_serial=None, logs=None, 190*800a58d9SAndroid Build Coastguard Worker key="devices", update_data=None): 191*800a58d9SAndroid Build Coastguard Worker """Add a record of a device. 192*800a58d9SAndroid Build Coastguard Worker 193*800a58d9SAndroid Build Coastguard Worker Args: 194*800a58d9SAndroid Build Coastguard Worker instance_name: A string. 195*800a58d9SAndroid Build Coastguard Worker ip_address: A string. 196*800a58d9SAndroid Build Coastguard Worker adb_port: An integer. 197*800a58d9SAndroid Build Coastguard Worker vnc_port: An integer. 198*800a58d9SAndroid Build Coastguard Worker webrtc_port: An integer, the port to display device screen. 199*800a58d9SAndroid Build Coastguard Worker device_serial: String of device serial. 200*800a58d9SAndroid Build Coastguard Worker logs: A list of LogFile. 201*800a58d9SAndroid Build Coastguard Worker key: A string, the data entry where the record is added. 202*800a58d9SAndroid Build Coastguard Worker update_data: A dict to update device data. 203*800a58d9SAndroid Build Coastguard Worker """ 204*800a58d9SAndroid Build Coastguard Worker device = {constants.INSTANCE_NAME: instance_name} 205*800a58d9SAndroid Build Coastguard Worker if adb_port: 206*800a58d9SAndroid Build Coastguard Worker device[constants.ADB_PORT] = adb_port 207*800a58d9SAndroid Build Coastguard Worker device[constants.IP] = "%s:%d" % (ip_address, adb_port) 208*800a58d9SAndroid Build Coastguard Worker else: 209*800a58d9SAndroid Build Coastguard Worker device[constants.IP] = ip_address 210*800a58d9SAndroid Build Coastguard Worker 211*800a58d9SAndroid Build Coastguard Worker if device_serial: 212*800a58d9SAndroid Build Coastguard Worker device[constants.DEVICE_SERIAL] = device_serial 213*800a58d9SAndroid Build Coastguard Worker 214*800a58d9SAndroid Build Coastguard Worker if vnc_port: 215*800a58d9SAndroid Build Coastguard Worker device[constants.VNC_PORT] = vnc_port 216*800a58d9SAndroid Build Coastguard Worker 217*800a58d9SAndroid Build Coastguard Worker if webrtc_port: 218*800a58d9SAndroid Build Coastguard Worker device[constants.WEBRTC_PORT] = webrtc_port 219*800a58d9SAndroid Build Coastguard Worker 220*800a58d9SAndroid Build Coastguard Worker if logs: 221*800a58d9SAndroid Build Coastguard Worker device[constants.LOGS] = logs 222*800a58d9SAndroid Build Coastguard Worker 223*800a58d9SAndroid Build Coastguard Worker if update_data: 224*800a58d9SAndroid Build Coastguard Worker device.update(update_data) 225*800a58d9SAndroid Build Coastguard Worker self.AddData(key=key, value=device) 226*800a58d9SAndroid Build Coastguard Worker 227*800a58d9SAndroid Build Coastguard Worker def AddDeviceBootFailure(self, instance_name, ip_address, adb_port, 228*800a58d9SAndroid Build Coastguard Worker vnc_port, error, device_serial=None, 229*800a58d9SAndroid Build Coastguard Worker webrtc_port=None, logs=None): 230*800a58d9SAndroid Build Coastguard Worker """Add a record of device boot failure. 231*800a58d9SAndroid Build Coastguard Worker 232*800a58d9SAndroid Build Coastguard Worker Args: 233*800a58d9SAndroid Build Coastguard Worker instance_name: A string. 234*800a58d9SAndroid Build Coastguard Worker ip_address: A string. 235*800a58d9SAndroid Build Coastguard Worker adb_port: An integer. 236*800a58d9SAndroid Build Coastguard Worker vnc_port: An integer. Can be None if the device doesn't support it. 237*800a58d9SAndroid Build Coastguard Worker error: A string, the error message. 238*800a58d9SAndroid Build Coastguard Worker device_serial: String of device serial. 239*800a58d9SAndroid Build Coastguard Worker webrtc_port: An integer. 240*800a58d9SAndroid Build Coastguard Worker logs: A list of LogFile. 241*800a58d9SAndroid Build Coastguard Worker """ 242*800a58d9SAndroid Build Coastguard Worker self.AddDevice(instance_name, ip_address, adb_port, vnc_port, 243*800a58d9SAndroid Build Coastguard Worker webrtc_port, device_serial, logs, 244*800a58d9SAndroid Build Coastguard Worker "devices_failing_boot") 245*800a58d9SAndroid Build Coastguard Worker self.AddError(error) 246*800a58d9SAndroid Build Coastguard Worker 247*800a58d9SAndroid Build Coastguard Worker def UpdateFailure(self, error, error_type=None): 248*800a58d9SAndroid Build Coastguard Worker """Update the falure information of report. 249*800a58d9SAndroid Build Coastguard Worker 250*800a58d9SAndroid Build Coastguard Worker Args: 251*800a58d9SAndroid Build Coastguard Worker error: String, the error message. 252*800a58d9SAndroid Build Coastguard Worker error_type: String, the error type. 253*800a58d9SAndroid Build Coastguard Worker """ 254*800a58d9SAndroid Build Coastguard Worker self.AddError(error) 255*800a58d9SAndroid Build Coastguard Worker self.SetStatus(Status.FAIL) 256*800a58d9SAndroid Build Coastguard Worker if error_type: 257*800a58d9SAndroid Build Coastguard Worker self.SetErrorType(error_type) 258*800a58d9SAndroid Build Coastguard Worker 259*800a58d9SAndroid Build Coastguard Worker def Dump(self, report_file): 260*800a58d9SAndroid Build Coastguard Worker """Dump report content to a file. 261*800a58d9SAndroid Build Coastguard Worker 262*800a58d9SAndroid Build Coastguard Worker Args: 263*800a58d9SAndroid Build Coastguard Worker report_file: A path to a file where result will be dumped to. 264*800a58d9SAndroid Build Coastguard Worker If None, will only output result as logs. 265*800a58d9SAndroid Build Coastguard Worker """ 266*800a58d9SAndroid Build Coastguard Worker result = dict( 267*800a58d9SAndroid Build Coastguard Worker command=self.command, 268*800a58d9SAndroid Build Coastguard Worker status=self.status, 269*800a58d9SAndroid Build Coastguard Worker errors=self.errors, 270*800a58d9SAndroid Build Coastguard Worker error_type=self.error_type, 271*800a58d9SAndroid Build Coastguard Worker data=self.data) 272*800a58d9SAndroid Build Coastguard Worker logger.info("Report: %s", json.dumps(result, indent=2, sort_keys=True)) 273*800a58d9SAndroid Build Coastguard Worker if not report_file: 274*800a58d9SAndroid Build Coastguard Worker return 275*800a58d9SAndroid Build Coastguard Worker try: 276*800a58d9SAndroid Build Coastguard Worker with open(report_file, "w") as f: 277*800a58d9SAndroid Build Coastguard Worker json.dump(result, f, indent=2, sort_keys=True) 278*800a58d9SAndroid Build Coastguard Worker logger.info("Report file generated at %s", 279*800a58d9SAndroid Build Coastguard Worker os.path.abspath(report_file)) 280*800a58d9SAndroid Build Coastguard Worker except OSError as e: 281*800a58d9SAndroid Build Coastguard Worker logger.error("Failed to dump report to file: %s", str(e)) 282