blob: ac227741ed6403e680d1043b5cf4b9fcbacf0c02 [file] [log] [blame]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +01001#!/usr/bin/env python3
2
3""" lava_rpc_connector.py:
4
5 class that extends xmlrpc in order to add LAVA specific functionality.
6 Used in managing communication with the back-end. """
7
8from __future__ import print_function
9
10__copyright__ = """
11/*
Dean Arnoldf1169b92020-03-11 10:14:14 +000012 * Copyright (c) 2018-2020, Arm Limited. All rights reserved.
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010013 *
14 * SPDX-License-Identifier: BSD-3-Clause
15 *
16 */
17 """
18__author__ = "Minos Galanakis"
19__email__ = "minos.galanakis@linaro.org"
20__project__ = "Trusted Firmware-M Open CI"
21__status__ = "stable"
Minos Galanakisea421232019-06-20 17:11:28 +010022__version__ = "1.1"
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010023
24import xmlrpc.client
25import time
Matthew Hartfb6fd362020-03-04 21:03:59 +000026import yaml
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010027
28
29class LAVA_RPC_connector(xmlrpc.client.ServerProxy, object):
30
31 def __init__(self,
32 username,
33 token,
34 hostname,
35 rest_prefix="RPC2",
36 https=False):
37
38 # If user provides hostname with http/s prefix
39 if "://" in hostname:
40 htp_pre, hostname = hostname.split("://")
41 server_addr = "%s://%s:%s@%s/%s" % (htp_pre,
42 username,
43 token,
44 hostname,
45 rest_prefix)
46 self.server_url = "%s://%s" % (htp_pre, hostname)
47 else:
48 server_addr = "%s://%s:%s@%s/%s" % ("https" if https else "http",
49 username,
50 token,
51 hostname,
52 rest_prefix)
53 self.server_url = "%s://%s" % ("https" if https else "http",
54 hostname)
55
56 self.server_job_prefix = "%s/scheduler/job/%%s" % self.server_url
57 super(LAVA_RPC_connector, self).__init__(server_addr)
58
59 def _rpc_cmd_raw(self, cmd, params=None):
60 """ Run a remote comand and return the result. There is no constrain
61 check on the syntax of the command. """
62
63 cmd = "self.%s(%s)" % (cmd, params if params else "")
64 return eval(cmd)
65
66 def ls_cmd(self):
67 """ Return a list of supported commands """
68
69 print("\n".join(self.system.listMethods()))
70
71 def get_job_results(self, job_id, yaml_out_file=None):
72 results = self.results.get_testjob_results_yaml(job_id)
73 if yaml_out_file:
74 with open(yaml_out_file, "w") as F:
75 F.write(results)
76 return results
77
Matthew Hartfb6fd362020-03-04 21:03:59 +000078 def get_job_definition(self, job_id, yaml_out_file=None):
79 job_def = self.scheduler.jobs.definition(job_id)
80 if yaml_out_file:
81 with open(yaml_out_file, "w") as F:
82 F.write(str(job_def))
83 def_o = yaml.load(job_def)
84 return job_def, def_o.get('metadata', [])
85
86 def write_target_lines(self, target_out_file, log):
Matthew Hart83366052020-05-15 12:48:36 +010087 log = yaml.load(log)
Matthew Hartfb6fd362020-03-04 21:03:59 +000088 with open(target_out_file, "w+") as F:
89 for line in log:
90 if line['lvl'] in ['target', 'feedback']:
91 F.write("{}\n".format(line['msg']))
92
93 def get_job_log(self, job_id, yaml_out_file=None, target_out_file=None):
94 job_res, job_log = self.scheduler.jobs.logs(job_id)
Matthew Hart83366052020-05-15 12:48:36 +010095 job_log = job_log.data.decode('utf-8')
Matthew Hartfb6fd362020-03-04 21:03:59 +000096 if yaml_out_file:
97 with open(yaml_out_file, "w") as F:
Matthew Hart83366052020-05-15 12:48:36 +010098 F.write(job_log)
Matthew Hartfb6fd362020-03-04 21:03:59 +000099 if target_out_file:
100 self.write_target_lines(target_out_file, job_log)
101 return job_log
102
103 def get_job_config(self, job_id, yaml_out_file=None):
104 job_config = self.scheduler.jobs.configuration(job_id)
105 if yaml_out_file:
106 with open(yaml_out_file, "w") as F:
107 for data in job_config:
108 if data:
Matthew Hart83366052020-05-15 12:48:36 +0100109 line = data.data.decode('utf-8')
110 F.write(line)
Matthew Hartfb6fd362020-03-04 21:03:59 +0000111 return job_config
112
113 def get_job_info(self, job_id, yaml_out_file=None):
114 job_info = self.scheduler.jobs.show(job_id)
115 if yaml_out_file:
116 with open(yaml_out_file, "w") as F:
117 F.write(str(job_info))
118 return job_info
119
120 def get_error_reason(self, job_id):
Matthew Hart2c2688f2020-05-26 13:09:20 +0100121 try:
122 lava_res = self.results.get_testsuite_results_yaml(job_id, 'lava')
123 results = yaml.load(lava_res)
124 for test in results:
125 if test['name'] == 'job':
126 return(test.get('metadata', {}).get('error_type', ''))
127 except Exception:
128 return("Unknown")
Matthew Hartfb6fd362020-03-04 21:03:59 +0000129
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100130 def get_job_state(self, job_id):
131 return self.scheduler.job_state(job_id)["job_state"]
132
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100133 def cancel_job(self, job_id):
134 """ Cancell job with id=job_id. Returns True if successfull """
135
136 return self.scheduler.jobs.cancel(job_id)
137
138 def validate_job_yaml(self, job_definition, print_err=False):
139 """ Validate a job definition syntax. Returns true is server considers
140 the syntax valid """
141
142 try:
143 with open(job_definition) as F:
144 input_yaml = F.read()
145 self.scheduler.validate_yaml(input_yaml)
146 return True
147 except Exception as E:
148 if print_err:
149 print(E)
150 return False
151
Matthew Hart110e1dc2020-05-27 17:18:55 +0100152 def device_type_from_def(self, job_data):
153 def_yaml = yaml.load(job_data)
154 return(def_yaml['device_type'])
155
156 def has_device_type(self, job_data):
157 d_type = self.device_type_from_def(job_data)
158 all_d = self.scheduler.devices.list()
159 for device in all_d:
160 if device['type'] == d_type:
161 if device['health'] in ['Good', 'Unknown']:
162 return(True)
163 return(False)
164
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100165 def submit_job(self, job_definition):
166 """ Will submit a yaml definition pointed by job_definition after
167 validating it againist the remote backend. Returns resulting job id,
168 and server url for job"""
169
170 try:
171 if not self.validate_job_yaml(job_definition):
172 print("Served rejected job's syntax")
173 raise Exception("Invalid job")
174 with open(job_definition, "r") as F:
175 job_data = F.read()
176 except Exception as e:
177 print("Cannot submit invalid job. Check %s's content" %
178 job_definition)
179 print(e)
180 return None, None
Dean Bircha6ede7e2020-03-13 14:00:33 +0000181 try:
Dean Birch1d545c02020-05-29 14:09:21 +0100182 if self.has_device_type(job_data):
183 job_id = self.scheduler.submit_job(job_data)
184 job_url = self.server_job_prefix % job_id
185 return(job_id, job_url)
186 else:
187 raise Exception("No devices online with required device_type")
Dean Bircha6ede7e2020-03-13 14:00:33 +0000188 except Exception as e:
189 print(e)
190 return(None, None)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100191
192 def resubmit_job(self, job_id):
193 """ Re-submit job with provided id. Returns resulting job id,
194 and server url for job"""
195
196 job_id = self.scheduler.resubmit_job(job_id)
197 job_url = self.server_job_prefix % job_id
198 return(job_id, job_url)
199
200 def block_wait_for_job(self, job_id, timeout, poll_freq=1):
201 """ Will block code execution and wait for the job to submit.
202 Returns job status on completion """
203
204 start_t = int(time.time())
205 while(True):
206 cur_t = int(time.time())
207 if cur_t - start_t >= timeout:
208 print("Breaking because of timeout")
209 break
210 # Check if the job is not running
Dean Arnoldf1169b92020-03-11 10:14:14 +0000211 cur_status = self.get_job_state(job_id)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100212 # If in queue or running wait
Dean Arnoldc1d81b42020-03-11 15:56:36 +0000213 if cur_status not in ["Canceling","Finished"]:
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100214 time.sleep(poll_freq)
215 else:
216 break
Dean Arnoldc1d81b42020-03-11 15:56:36 +0000217 return self.scheduler.job_health(job_id)["job_health"]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100218
Matthew Hartfb6fd362020-03-04 21:03:59 +0000219 def block_wait_for_jobs(self, job_ids, timeout, poll_freq=10):
220 """ Wait for multiple LAVA job ids to finish and return finished list """
221
222 start_t = int(time.time())
223 finished_jobs = {}
224 while(True):
225 cur_t = int(time.time())
226 if cur_t - start_t >= timeout:
227 print("Breaking because of timeout")
228 break
229 for job_id in job_ids:
230 # Check if the job is not running
231 cur_status = self.get_job_info(job_id)
232 # If in queue or running wait
233 if cur_status['state'] in ["Canceling","Finished"]:
234 cur_status['error_reason'] = self.get_error_reason(job_id)
235 finished_jobs[job_id] = cur_status
236 if len(job_ids) == len(finished_jobs):
237 break
238 else:
239 time.sleep(poll_freq)
240 if len(job_ids) == len(finished_jobs):
241 break
242 return finished_jobs
243
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100244 def test_credentials(self):
245 """ Attempt to querry the back-end and verify that the user provided
246 authentication is valid """
247
248 try:
249 self._rpc_cmd_raw("system.listMethods")
250 return True
251 except Exception as e:
252 print(e)
253 print("Credential validation failed")
254 return False
255
256
257if __name__ == "__main__":
258 pass