blob: 8edd3221db55230e20f2a5db2bc8b26e99a9a0bc [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
Matthew Hart4a4f1202020-06-12 15:52:46 +010027import requests
28import shutil
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010029
30class LAVA_RPC_connector(xmlrpc.client.ServerProxy, object):
31
32 def __init__(self,
33 username,
34 token,
35 hostname,
36 rest_prefix="RPC2",
37 https=False):
38
39 # If user provides hostname with http/s prefix
40 if "://" in hostname:
41 htp_pre, hostname = hostname.split("://")
42 server_addr = "%s://%s:%s@%s/%s" % (htp_pre,
43 username,
44 token,
45 hostname,
46 rest_prefix)
47 self.server_url = "%s://%s" % (htp_pre, hostname)
48 else:
49 server_addr = "%s://%s:%s@%s/%s" % ("https" if https else "http",
50 username,
51 token,
52 hostname,
53 rest_prefix)
54 self.server_url = "%s://%s" % ("https" if https else "http",
55 hostname)
56
57 self.server_job_prefix = "%s/scheduler/job/%%s" % self.server_url
Matthew Hart4a4f1202020-06-12 15:52:46 +010058 self.server_results_prefix = "%s/results/%%s" % self.server_url
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010059 super(LAVA_RPC_connector, self).__init__(server_addr)
60
61 def _rpc_cmd_raw(self, cmd, params=None):
62 """ Run a remote comand and return the result. There is no constrain
63 check on the syntax of the command. """
64
65 cmd = "self.%s(%s)" % (cmd, params if params else "")
66 return eval(cmd)
67
68 def ls_cmd(self):
69 """ Return a list of supported commands """
70
71 print("\n".join(self.system.listMethods()))
72
Matthew Hart4a4f1202020-06-12 15:52:46 +010073 def fetch_file(self, url, out_file):
74 try:
75 with requests.get(url, stream=True) as r:
76 with open(out_file, 'wb') as f:
77 shutil.copyfileobj(r.raw, f)
78 return(out_file)
79 except:
80 return(False)
81
82 def get_job_results(self, job_id, yaml_out_file):
83 results_url = "{}/yaml".format(self.server_results_prefix % job_id)
84 return(self.fetch_file(results_url, yaml_out_file))
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +010085
Matthew Hartfb6fd362020-03-04 21:03:59 +000086 def get_job_definition(self, job_id, yaml_out_file=None):
87 job_def = self.scheduler.jobs.definition(job_id)
88 if yaml_out_file:
89 with open(yaml_out_file, "w") as F:
90 F.write(str(job_def))
91 def_o = yaml.load(job_def)
92 return job_def, def_o.get('metadata', [])
93
Matthew Hart4a4f1202020-06-12 15:52:46 +010094 def get_job_log(self, job_id, target_out_file):
95 log_url = "{}/log_file/plain".format(self.server_job_prefix % job_id)
96 r = requests.get(log_url, stream=True)
97 if not r:
98 return
99 with open(target_out_file, "w") as target_out:
100 try:
101 for line in r.iter_lines():
102 line = line.decode('utf-8')
103 try:
104 if ('target' in line) or ('feedback' in line):
105 line_yaml = yaml.load(line)[0]
106 if line_yaml['lvl'] in ['target', 'feedback']:
107 target_out.write("{}\n".format(line_yaml['msg']))
108 except yaml.parser.ParserError as e:
109 continue
110 except yaml.scanner.ScannerError as e:
111 continue
112 except Exception as e:
113 pass
Matthew Hartfb6fd362020-03-04 21:03:59 +0000114
Matthew Hart4a4f1202020-06-12 15:52:46 +0100115 def get_job_config(self, job_id, config_out_file):
116 config_url = "{}/configuration".format(self.server_job_prefix % job_id)
117 self.fetch_file(config_url, config_out_file)
Matthew Hartfb6fd362020-03-04 21:03:59 +0000118
119 def get_job_info(self, job_id, yaml_out_file=None):
120 job_info = self.scheduler.jobs.show(job_id)
121 if yaml_out_file:
122 with open(yaml_out_file, "w") as F:
123 F.write(str(job_info))
124 return job_info
125
126 def get_error_reason(self, job_id):
Matthew Hart2c2688f2020-05-26 13:09:20 +0100127 try:
128 lava_res = self.results.get_testsuite_results_yaml(job_id, 'lava')
129 results = yaml.load(lava_res)
130 for test in results:
131 if test['name'] == 'job':
132 return(test.get('metadata', {}).get('error_type', ''))
133 except Exception:
134 return("Unknown")
Matthew Hartfb6fd362020-03-04 21:03:59 +0000135
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100136 def get_job_state(self, job_id):
137 return self.scheduler.job_state(job_id)["job_state"]
138
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100139 def cancel_job(self, job_id):
140 """ Cancell job with id=job_id. Returns True if successfull """
141
142 return self.scheduler.jobs.cancel(job_id)
143
144 def validate_job_yaml(self, job_definition, print_err=False):
145 """ Validate a job definition syntax. Returns true is server considers
146 the syntax valid """
147
148 try:
149 with open(job_definition) as F:
150 input_yaml = F.read()
151 self.scheduler.validate_yaml(input_yaml)
152 return True
153 except Exception as E:
154 if print_err:
155 print(E)
156 return False
157
Matthew Hart110e1dc2020-05-27 17:18:55 +0100158 def device_type_from_def(self, job_data):
159 def_yaml = yaml.load(job_data)
160 return(def_yaml['device_type'])
161
162 def has_device_type(self, job_data):
163 d_type = self.device_type_from_def(job_data)
164 all_d = self.scheduler.devices.list()
165 for device in all_d:
166 if device['type'] == d_type:
167 if device['health'] in ['Good', 'Unknown']:
168 return(True)
169 return(False)
170
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100171 def submit_job(self, job_definition):
172 """ Will submit a yaml definition pointed by job_definition after
173 validating it againist the remote backend. Returns resulting job id,
174 and server url for job"""
175
176 try:
177 if not self.validate_job_yaml(job_definition):
178 print("Served rejected job's syntax")
179 raise Exception("Invalid job")
180 with open(job_definition, "r") as F:
181 job_data = F.read()
182 except Exception as e:
183 print("Cannot submit invalid job. Check %s's content" %
184 job_definition)
185 print(e)
186 return None, None
Dean Bircha6ede7e2020-03-13 14:00:33 +0000187 try:
Dean Birch1d545c02020-05-29 14:09:21 +0100188 if self.has_device_type(job_data):
189 job_id = self.scheduler.submit_job(job_data)
190 job_url = self.server_job_prefix % job_id
191 return(job_id, job_url)
192 else:
193 raise Exception("No devices online with required device_type")
Dean Bircha6ede7e2020-03-13 14:00:33 +0000194 except Exception as e:
195 print(e)
196 return(None, None)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100197
198 def resubmit_job(self, job_id):
199 """ Re-submit job with provided id. Returns resulting job id,
200 and server url for job"""
201
202 job_id = self.scheduler.resubmit_job(job_id)
203 job_url = self.server_job_prefix % job_id
204 return(job_id, job_url)
205
206 def block_wait_for_job(self, job_id, timeout, poll_freq=1):
207 """ Will block code execution and wait for the job to submit.
208 Returns job status on completion """
209
210 start_t = int(time.time())
211 while(True):
212 cur_t = int(time.time())
213 if cur_t - start_t >= timeout:
214 print("Breaking because of timeout")
215 break
216 # Check if the job is not running
Dean Arnoldf1169b92020-03-11 10:14:14 +0000217 cur_status = self.get_job_state(job_id)
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100218 # If in queue or running wait
Dean Arnoldc1d81b42020-03-11 15:56:36 +0000219 if cur_status not in ["Canceling","Finished"]:
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100220 time.sleep(poll_freq)
221 else:
222 break
Dean Arnoldc1d81b42020-03-11 15:56:36 +0000223 return self.scheduler.job_health(job_id)["job_health"]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100224
Matthew Hartfb6fd362020-03-04 21:03:59 +0000225 def block_wait_for_jobs(self, job_ids, timeout, poll_freq=10):
226 """ Wait for multiple LAVA job ids to finish and return finished list """
227
228 start_t = int(time.time())
229 finished_jobs = {}
230 while(True):
231 cur_t = int(time.time())
232 if cur_t - start_t >= timeout:
233 print("Breaking because of timeout")
234 break
235 for job_id in job_ids:
236 # Check if the job is not running
237 cur_status = self.get_job_info(job_id)
238 # If in queue or running wait
239 if cur_status['state'] in ["Canceling","Finished"]:
240 cur_status['error_reason'] = self.get_error_reason(job_id)
241 finished_jobs[job_id] = cur_status
242 if len(job_ids) == len(finished_jobs):
243 break
244 else:
245 time.sleep(poll_freq)
246 if len(job_ids) == len(finished_jobs):
247 break
248 return finished_jobs
249
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100250 def test_credentials(self):
251 """ Attempt to querry the back-end and verify that the user provided
252 authentication is valid """
253
254 try:
255 self._rpc_cmd_raw("system.listMethods")
256 return True
257 except Exception as e:
258 print(e)
259 print("Credential validation failed")
260 return False
261
262
263if __name__ == "__main__":
264 pass