blob: 2048329869f41593612dd6ad67887aa7fa9ad4f4 [file] [log] [blame]
Keun Soo Yimb293fdb2016-09-21 16:03:44 -07001#!/usr/bin/env python
2#
3# Copyright 2016 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Command report.
18
19Report class holds the results of a command execution.
20Each driver API call will generate a report instance.
21
22If running the CLI of the driver, a report will
23be printed as logs. And it will also be dumped to a json file
24if requested via command line option.
25
26The json format of a report dump looks like:
27
28 - A failed "delete" command:
29 {
30 "command": "delete",
31 "data": {},
32 "errors": [
33 "Can't find instances: ['104.197.110.255']"
34 ],
35 "status": "FAIL"
36 }
37
38 - A successful "create" command:
39 {
40 "command": "create",
41 "data": {
42 "devices": [
43 {
44 "instance_name": "instance_1",
45 "ip": "104.197.62.36"
46 },
47 {
48 "instance_name": "instance_2",
49 "ip": "104.197.62.37"
50 }
51 ]
52 },
53 "errors": [],
54 "status": "SUCCESS"
55 }
56"""
57
58import json
59import logging
60import os
61
62logger = logging.getLogger(__name__)
63
64
65class Status(object):
66 """Status of acloud command."""
67
68 SUCCESS = "SUCCESS"
69 FAIL = "FAIL"
70 BOOT_FAIL = "BOOT_FAIL"
71 UNKNOWN = "UNKNOWN"
72
73 SEVERITY_ORDER = {UNKNOWN: 0, SUCCESS: 1, FAIL: 2, BOOT_FAIL: 3}
74
75 @classmethod
76 def IsMoreSevere(cls, candidate, reference):
77 """Compare the severity of two statuses.
78
79 Args:
80 candidate: One of the statuses.
81 reference: One of the statuses.
82
83 Returns:
84 True if candidate is more severe than reference,
85 False otherwise.
86
87 Raises:
88 ValueError: if candidate or reference is not a known state.
89 """
90 if (candidate not in cls.SEVERITY_ORDER or
91 reference not in cls.SEVERITY_ORDER):
92 raise ValueError("%s or %s is not recognized." %
93 (candidate, reference))
94 return cls.SEVERITY_ORDER[candidate] > cls.SEVERITY_ORDER[reference]
95
96
97class Report(object):
98 """A class that stores and generates report."""
99
100 def __init__(self, command):
101 """Initialize.
102
103 Args:
104 command: A string, name of the command.
105 """
106 self.command = command
107 self.status = Status.UNKNOWN
108 self.errors = []
109 self.data = {}
110
111 def AddData(self, key, value):
112 """Add a key-val to the report.
113
114 Args:
115 key: A key of basic type.
116 value: A value of any json compatible type.
117 """
118 self.data.setdefault(key, []).append(value)
119
120 def AddError(self, error):
121 """Add error message.
122
123 Args:
124 error: A string.
125 """
126 self.errors.append(error)
127
128 def AddErrors(self, errors):
129 """Add a list of error messages.
130
131 Args:
132 errors: A list of string.
133 """
134 self.errors.extend(errors)
135
136 def SetStatus(self, status):
137 """Set status.
138
139 Args:
140 status: One of the status in Status.
141 """
142 if Status.IsMoreSevere(status, self.status):
143 self.status = status
144 else:
145 logger.debug(
146 "report: Current status is %s, "
147 "requested to update to a status with lower severity %s, ignored.",
148 self.status, status)
149
150 def Dump(self, report_file):
151 """Dump report content to a file.
152
153 Args:
154 report_file: A path to a file where result will be dumped to.
155 If None, will only output result as logs.
156 """
157 result = dict(command=self.command,
158 status=self.status,
159 errors=self.errors,
160 data=self.data)
161 logger.info("Report: %s", json.dumps(result, indent=2))
162 if not report_file:
163 return
164 try:
165 with open(report_file, "w") as f:
166 json.dump(result, f, indent=2)
167 logger.info("Report file generated at %s",
168 os.path.abspath(report_file))
169 except OSError as e:
170 logger.error("Failed to dump report to file: %s", str(e))