xref: /aosp_15_r20/tools/acloud/public/report.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
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