blob: f1767fe78cc681e4ef60299b4e95dca4afeccd92 [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 """
Karl Zhang08681e62020-10-30 13:56:03 +080018
19__author__ = "tf-m@lists.trustedfirmware.org"
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010020__project__ = "Trusted Firmware-M Open CI"
Karl Zhang08681e62020-10-30 13:56:03 +080021__version__ = "1.2.0"
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010022
23import xmlrpc.client
24import time
Matthew Hartfb6fd362020-03-04 21:03:59 +000025import yaml
Matthew Hart4a4f1202020-06-12 15:52:46 +010026import requests
27import shutil
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010028
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
Matthew Hart4a4f1202020-06-12 15:52:46 +010057 self.server_results_prefix = "%s/results/%%s" % self.server_url
Matthew Hartc6bbbf92020-08-19 14:12:07 +010058 self.token = token
59 self.username = username
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010060 super(LAVA_RPC_connector, self).__init__(server_addr)
61
62 def _rpc_cmd_raw(self, cmd, params=None):
63 """ Run a remote comand and return the result. There is no constrain
64 check on the syntax of the command. """
65
66 cmd = "self.%s(%s)" % (cmd, params if params else "")
67 return eval(cmd)
68
69 def ls_cmd(self):
70 """ Return a list of supported commands """
71
72 print("\n".join(self.system.listMethods()))
73
Matthew Hart4a4f1202020-06-12 15:52:46 +010074 def fetch_file(self, url, out_file):
Matthew Hartc6bbbf92020-08-19 14:12:07 +010075 auth_params = {
76 'user': self.username,
77 'token': self.token
78 }
Matthew Hart4a4f1202020-06-12 15:52:46 +010079 try:
Matthew Hartc6bbbf92020-08-19 14:12:07 +010080 with requests.get(url, stream=True, params=auth_params) as r:
Matthew Hart4a4f1202020-06-12 15:52:46 +010081 with open(out_file, 'wb') as f:
82 shutil.copyfileobj(r.raw, f)
83 return(out_file)
84 except:
85 return(False)
86
87 def get_job_results(self, job_id, yaml_out_file):
88 results_url = "{}/yaml".format(self.server_results_prefix % job_id)
89 return(self.fetch_file(results_url, yaml_out_file))
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010090
Matthew Hartfb6fd362020-03-04 21:03:59 +000091 def get_job_definition(self, job_id, yaml_out_file=None):
92 job_def = self.scheduler.jobs.definition(job_id)
93 if yaml_out_file:
94 with open(yaml_out_file, "w") as F:
95 F.write(str(job_def))
96 def_o = yaml.load(job_def)
97 return job_def, def_o.get('metadata', [])
98
Matthew Hart4a4f1202020-06-12 15:52:46 +010099 def get_job_log(self, job_id, target_out_file):
100 log_url = "{}/log_file/plain".format(self.server_job_prefix % job_id)
101 r = requests.get(log_url, stream=True)
102 if not r:
103 return
104 with open(target_out_file, "w") as target_out:
105 try:
106 for line in r.iter_lines():
107 line = line.decode('utf-8')
108 try:
109 if ('target' in line) or ('feedback' in line):
110 line_yaml = yaml.load(line)[0]
111 if line_yaml['lvl'] in ['target', 'feedback']:
112 target_out.write("{}\n".format(line_yaml['msg']))
113 except yaml.parser.ParserError as e:
114 continue
115 except yaml.scanner.ScannerError as e:
116 continue
117 except Exception as e:
118 pass
Matthew Hartfb6fd362020-03-04 21:03:59 +0000119
Matthew Hart4a4f1202020-06-12 15:52:46 +0100120 def get_job_config(self, job_id, config_out_file):
121 config_url = "{}/configuration".format(self.server_job_prefix % job_id)
122 self.fetch_file(config_url, config_out_file)
Matthew Hartfb6fd362020-03-04 21:03:59 +0000123
124 def get_job_info(self, job_id, yaml_out_file=None):
125 job_info = self.scheduler.jobs.show(job_id)
126 if yaml_out_file:
127 with open(yaml_out_file, "w") as F:
128 F.write(str(job_info))
129 return job_info
130
131 def get_error_reason(self, job_id):
Matthew Hart2c2688f2020-05-26 13:09:20 +0100132 try:
133 lava_res = self.results.get_testsuite_results_yaml(job_id, 'lava')
134 results = yaml.load(lava_res)
135 for test in results:
136 if test['name'] == 'job':
137 return(test.get('metadata', {}).get('error_type', ''))
138 except Exception:
139 return("Unknown")
Matthew Hartfb6fd362020-03-04 21:03:59 +0000140
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100141 def get_job_state(self, job_id):
142 return self.scheduler.job_state(job_id)["job_state"]
143
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100144 def cancel_job(self, job_id):
145 """ Cancell job with id=job_id. Returns True if successfull """
146
147 return self.scheduler.jobs.cancel(job_id)
148
149 def validate_job_yaml(self, job_definition, print_err=False):
150 """ Validate a job definition syntax. Returns true is server considers
151 the syntax valid """
152
153 try:
154 with open(job_definition) as F:
155 input_yaml = F.read()
156 self.scheduler.validate_yaml(input_yaml)
157 return True
158 except Exception as E:
159 if print_err:
160 print(E)
161 return False
162
Matthew Hart110e1dc2020-05-27 17:18:55 +0100163 def device_type_from_def(self, job_data):
164 def_yaml = yaml.load(job_data)
165 return(def_yaml['device_type'])
166
167 def has_device_type(self, job_data):
168 d_type = self.device_type_from_def(job_data)
169 all_d = self.scheduler.devices.list()
170 for device in all_d:
171 if device['type'] == d_type:
172 if device['health'] in ['Good', 'Unknown']:
173 return(True)
174 return(False)
175
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100176 def submit_job(self, job_definition):
177 """ Will submit a yaml definition pointed by job_definition after
178 validating it againist the remote backend. Returns resulting job id,
179 and server url for job"""
180
181 try:
182 if not self.validate_job_yaml(job_definition):
183 print("Served rejected job's syntax")
184 raise Exception("Invalid job")
185 with open(job_definition, "r") as F:
186 job_data = F.read()
187 except Exception as e:
188 print("Cannot submit invalid job. Check %s's content" %
189 job_definition)
190 print(e)
191 return None, None
Dean Bircha6ede7e2020-03-13 14:00:33 +0000192 try:
Dean Birch1d545c02020-05-29 14:09:21 +0100193 if self.has_device_type(job_data):
194 job_id = self.scheduler.submit_job(job_data)
195 job_url = self.server_job_prefix % job_id
196 return(job_id, job_url)
197 else:
198 raise Exception("No devices online with required device_type")
Dean Bircha6ede7e2020-03-13 14:00:33 +0000199 except Exception as e:
200 print(e)
201 return(None, None)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100202
203 def resubmit_job(self, job_id):
204 """ Re-submit job with provided id. Returns resulting job id,
205 and server url for job"""
206
207 job_id = self.scheduler.resubmit_job(job_id)
208 job_url = self.server_job_prefix % job_id
209 return(job_id, job_url)
210
211 def block_wait_for_job(self, job_id, timeout, poll_freq=1):
212 """ Will block code execution and wait for the job to submit.
213 Returns job status on completion """
214
215 start_t = int(time.time())
216 while(True):
217 cur_t = int(time.time())
218 if cur_t - start_t >= timeout:
219 print("Breaking because of timeout")
220 break
221 # Check if the job is not running
Dean Arnoldf1169b92020-03-11 10:14:14 +0000222 cur_status = self.get_job_state(job_id)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100223 # If in queue or running wait
Dean Arnoldc1d81b42020-03-11 15:56:36 +0000224 if cur_status not in ["Canceling","Finished"]:
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100225 time.sleep(poll_freq)
226 else:
227 break
Dean Arnoldc1d81b42020-03-11 15:56:36 +0000228 return self.scheduler.job_health(job_id)["job_health"]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100229
Matthew Hartfb6fd362020-03-04 21:03:59 +0000230 def block_wait_for_jobs(self, job_ids, timeout, poll_freq=10):
231 """ Wait for multiple LAVA job ids to finish and return finished list """
232
233 start_t = int(time.time())
234 finished_jobs = {}
235 while(True):
236 cur_t = int(time.time())
237 if cur_t - start_t >= timeout:
238 print("Breaking because of timeout")
239 break
240 for job_id in job_ids:
241 # Check if the job is not running
242 cur_status = self.get_job_info(job_id)
243 # If in queue or running wait
244 if cur_status['state'] in ["Canceling","Finished"]:
245 cur_status['error_reason'] = self.get_error_reason(job_id)
246 finished_jobs[job_id] = cur_status
247 if len(job_ids) == len(finished_jobs):
248 break
249 else:
250 time.sleep(poll_freq)
251 if len(job_ids) == len(finished_jobs):
252 break
253 return finished_jobs
254
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100255 def test_credentials(self):
256 """ Attempt to querry the back-end and verify that the user provided
257 authentication is valid """
258
259 try:
260 self._rpc_cmd_raw("system.listMethods")
261 return True
262 except Exception as e:
263 print(e)
264 print("Credential validation failed")
265 return False
266
267
268if __name__ == "__main__":
269 pass