line |
stmt |
code |
1
|
|
# Copyright 2015-2021 SUSE LLC |
2
|
|
# SPDX-License-Identifier: GPL-2.0-or-later |
3
|
|
|
4
|
|
## Multi-Machine API |
5
|
|
|
6
|
|
use Mojo::Base 'Exporter', -signatures; |
7
|
1
|
our @EXPORT = qw(get_children_by_state get_children get_parents |
|
1
|
|
|
1
|
|
8
|
|
get_job_info get_job_autoinst_url get_job_autoinst_vars |
9
|
|
wait_for_children wait_for_children_to_start api_call |
10
|
|
api_call_2 handle_api_error get_current_job_id |
11
|
|
); |
12
|
|
|
13
|
|
require bmwqemu; |
14
|
|
|
15
|
|
use Mojo::UserAgent; |
16
|
1
|
use Mojo::URL; |
|
1
|
|
|
1
|
|
17
|
1
|
|
|
1
|
|
|
1
|
|
18
|
|
our $retry_count = $ENV{OS_AUTOINST_MMAPI_RETRY_COUNT} // 30; |
19
|
|
our $retry_interval = $ENV{OS_AUTOINST_MMAPI_RETRY_INTERVAL} // 10; |
20
|
|
our $poll_interval = $ENV{OS_AUTOINST_MMAPI_POLL_INTERVAL} // 1; |
21
|
|
|
22
|
|
our $url; |
23
|
|
|
24
|
|
# private ua |
25
|
|
my $ua; |
26
|
|
my $app; |
27
|
|
|
28
|
|
# define HTTP return codes which are not treated as errors by api_call/api_call_2/handle_api_error |
29
|
|
my $CODES_EXPECTED_BY_DEFAULT = {200 => 1, 409 => 1}; |
30
|
|
|
31
|
|
# define HTTP return codes which are not treated as errors by functions of mmapi itself |
32
|
|
my $CODES_EXPECTED_BY_MMAPI = {200 => 1}; |
33
|
|
|
34
|
|
# init $ua and $url |
35
|
2
|
my $host = $bmwqemu::vars{OPENQA_URL}; |
|
2
|
|
36
|
|
my $secret = $bmwqemu::vars{JOBTOKEN}; |
37
|
2
|
return unless $host && $secret; |
38
|
2
|
$url = Mojo::URL->new($host =~ '/' ? $host : "http://$host"); |
39
|
2
|
|
40
|
2
|
# Relative paths are appended to the existing one |
41
|
|
$url->path('/api/v1/'); |
42
|
|
|
43
|
2
|
$ua = Mojo::UserAgent->new; |
44
|
|
|
45
|
2
|
# add JOBTOKEN header secret |
46
|
|
$ua->on(start => sub ($ua, $tx) { $tx->req->headers->add('X-API-JobToken' => $secret) }); |
47
|
|
} |
48
|
2
|
|
|
39
|
|
|
39
|
|
|
39
|
|
|
39
|
|
|
39
|
|
49
|
|
_init; |
50
|
|
$ua->server->app($app = $app_arg); |
51
|
1
|
} |
|
1
|
|
|
1
|
|
52
|
1
|
|
53
|
1
|
=head2 api_call_2 |
54
|
|
|
55
|
|
Queries openQA's multi-machine API and returns the resulting Mojo::Transaction::HTTP object. |
56
|
|
|
57
|
|
=cut |
58
|
|
|
59
|
|
_init unless $ua; |
60
|
|
bmwqemu::mydie('Missing mandatory options') unless $method && $action && $ua; |
61
|
|
|
62
|
39
|
my $ua_url = $url->clone; |
|
39
|
|
|
39
|
|
|
39
|
|
|
39
|
|
|
39
|
|
63
|
39
|
$ua_url->path($action); |
64
|
39
|
$ua_url->query($params) if $params; |
65
|
|
|
66
|
39
|
my $tries = $retry_count; |
67
|
39
|
my ($tx, $res); |
68
|
39
|
while ($tries--) { |
69
|
|
$tx = $ua->$method($ua_url); |
70
|
39
|
$res = $tx->res; |
71
|
39
|
last if $res->code && ($expected_codes // $CODES_EXPECTED_BY_DEFAULT)->{$res->code}; |
72
|
39
|
bmwqemu::diag("api_call_2 failed, retries left: $tries of $retry_count"); |
73
|
39
|
sleep $retry_interval; |
74
|
39
|
} |
75
|
39
|
return $tx; |
76
|
14
|
} |
77
|
14
|
|
78
|
|
=head2 api_call |
79
|
39
|
|
80
|
|
Queries openQA's multi-machine API and returns the result as Mojo::Message::Response object. |
81
|
|
|
82
|
|
=cut |
83
|
|
|
84
|
|
|
85
|
|
=head2 handle_api_error |
86
|
|
|
87
|
|
Returns a truthy value if the specified Mojo::Transaction::HTTP object has an error. |
88
|
1
|
Logs the errors if a log context is specified. |
|
1
|
|
|
1
|
|
|
1
|
|
89
|
|
|
90
|
|
=cut |
91
|
|
|
92
|
|
my $err = $tx->error; |
93
|
|
return 0 unless $err; |
94
|
|
|
95
|
|
my $url = $tx->req->url; |
96
|
|
my $code = $err->{code}; |
97
|
39
|
return 0 if $code && ($expected_codes // $CODES_EXPECTED_BY_DEFAULT)->{$code}; |
|
39
|
|
|
39
|
|
|
39
|
|
|
39
|
|
98
|
39
|
$err->{message} .= "; URL was $url" if $url; |
99
|
39
|
bmwqemu::diag($code |
100
|
|
? "$log_ctx: $code response: $err->{message}" |
101
|
18
|
: "$log_ctx: Connection error: $err->{message}") if $log_ctx; |
102
|
18
|
return 1; |
103
|
18
|
} |
104
|
14
|
|
105
|
14
|
=head2 get_children_by_state |
106
|
|
|
107
|
|
my $children = get_children_by_state('done'); |
108
|
14
|
print $children->[0] |
109
|
|
|
110
|
|
Returns an array ref containing ids of children in given state. |
111
|
|
|
112
|
|
=cut |
113
|
|
|
114
|
|
my $tx = api_call_2(get => "mm/children/$state", undef, $CODES_EXPECTED_BY_MMAPI); |
115
|
|
return undef if handle_api_error($tx, 'get_children_by_state', $CODES_EXPECTED_BY_MMAPI); |
116
|
|
return $tx->res->json('/jobs'); |
117
|
|
} |
118
|
|
|
119
|
|
=head2 get_children |
120
|
2
|
|
|
2
|
|
|
2
|
|
121
|
2
|
my $children = get_children(); |
122
|
2
|
print keys %$children; |
123
|
1
|
|
124
|
|
Returns a hash ref containing { id => state } pair for each child job. |
125
|
|
|
126
|
|
=cut |
127
|
|
|
128
|
|
my $tx = api_call_2(get => 'mm/children', undef, $CODES_EXPECTED_BY_MMAPI); |
129
|
|
return undef if handle_api_error($tx, 'get_children', $CODES_EXPECTED_BY_MMAPI); |
130
|
|
return $tx->res->json('/jobs'); |
131
|
|
} |
132
|
|
|
133
|
|
=head2 get_parents |
134
|
|
|
135
|
6
|
my $parents = get_parents |
|
6
|
|
136
|
6
|
print $parents->[0] |
137
|
6
|
|
138
|
5
|
Returns an array ref containing ids of parent jobs. |
139
|
|
|
140
|
|
=cut |
141
|
|
|
142
|
|
my $tx = api_call_2(get => 'mm/parents', undef, $CODES_EXPECTED_BY_MMAPI); |
143
|
|
return undef if handle_api_error($tx, 'get_parents', $CODES_EXPECTED_BY_MMAPI); |
144
|
|
return $tx->res->json('/jobs'); |
145
|
|
} |
146
|
|
|
147
|
|
=head2 get_job_info |
148
|
|
|
149
|
|
my $info = get_job_info($target_id); |
150
|
1
|
print $info->{settings}->{DESKTOP} |
|
1
|
|
151
|
1
|
|
152
|
1
|
Returns a hash containin job information provided by openQA server. |
153
|
1
|
|
154
|
|
=cut |
155
|
|
|
156
|
|
my $tx = api_call_2(get => "jobs/$target_id", undef, $CODES_EXPECTED_BY_MMAPI); |
157
|
|
return undef if handle_api_error($tx, 'get_job_info', $CODES_EXPECTED_BY_MMAPI); |
158
|
|
return $tx->res->json('/job'); |
159
|
|
} |
160
|
|
|
161
|
|
=head2 get_job_autoinst_url |
162
|
|
|
163
|
|
my $url = get_job_autoinst_url($target_id); |
164
|
|
|
165
|
2
|
Returns url of os-autoinst webserver for job $target_id or C<undef> on failure. |
|
2
|
|
|
2
|
|
166
|
2
|
|
167
|
2
|
=cut |
168
|
1
|
|
169
|
|
my $tx = api_call_2(get => 'workers', undef, $CODES_EXPECTED_BY_MMAPI); |
170
|
|
return undef if handle_api_error($tx, 'get_job_autoinst_url', $CODES_EXPECTED_BY_MMAPI); |
171
|
|
|
172
|
|
my $workers = $tx->res->json('/workers') // []; |
173
|
|
for my $worker (@$workers) { |
174
|
|
if ($worker->{jobid} && $target_id == $worker->{jobid} && $worker->{host} && $worker->{instance} && $worker->{properties}{JOBTOKEN}) { |
175
|
|
my $hostname = $worker->{host}; |
176
|
|
my $token = $worker->{properties}{JOBTOKEN}; |
177
|
|
my $workerport = $worker->{instance} * 10 + 20002 + 1; # the same as in openqa/script/worker |
178
|
|
my $url = "http://$hostname:$workerport/$token"; |
179
|
2
|
return $url; |
|
2
|
|
|
2
|
|
180
|
2
|
} |
181
|
2
|
} |
182
|
|
bmwqemu::diag("get_job_autoinst_url: No worker info for job $target_id available."); |
183
|
2
|
return undef; |
184
|
2
|
} |
185
|
2
|
|
186
|
1
|
=head2 get_job_autoinst_vars |
187
|
1
|
|
188
|
1
|
my $vars = get_job_autoinst_vars($target_id); |
189
|
1
|
print $vars->{WORKER_ID}; |
190
|
1
|
|
191
|
|
Returns hash reference containing variables of job $target_id or C<undef> on failure. The variables |
192
|
|
are taken from vars.json file of the corresponding worker. |
193
|
1
|
|
194
|
1
|
=cut |
195
|
|
|
196
|
|
my $url = get_job_autoinst_url($target_id); |
197
|
|
return undef unless $url; |
198
|
|
|
199
|
|
# query the os-autoinst webserver of the job specified by $target_id |
200
|
|
$url .= '/vars'; |
201
|
|
|
202
|
|
my $ua = Mojo::UserAgent->new; |
203
|
|
$ua->server->app($app) if $app; |
204
|
|
my $tx = $ua->get($url); |
205
|
|
return undef if handle_api_error($tx, 'get_job_autoinst_vars', $CODES_EXPECTED_BY_MMAPI); |
206
|
|
return $tx->res->json('/vars'); |
207
|
2
|
} |
|
2
|
|
|
2
|
|
208
|
2
|
|
209
|
2
|
=head2 wait_for_children |
210
|
|
|
211
|
|
wait_for_children(); |
212
|
1
|
|
213
|
|
Wait while any running or scheduled children exist. |
214
|
1
|
|
215
|
1
|
=cut |
216
|
1
|
|
217
|
1
|
while (1) { |
218
|
1
|
my $children = get_children() // die 'Failed to wait for children'; |
219
|
|
my $n = 0; |
220
|
|
for my $state (values %$children) { |
221
|
|
next if $state eq 'done' or $state eq 'cancelled'; |
222
|
|
$n++; |
223
|
|
} |
224
|
|
|
225
|
|
bmwqemu::diag("Waiting for $n jobs to finish"); |
226
|
|
last unless $n; |
227
|
|
sleep $poll_interval; |
228
|
|
} |
229
|
2
|
} |
|
2
|
|
230
|
2
|
|
231
|
3
|
=head2 wait_for_children_to_start |
232
|
2
|
|
233
|
2
|
wait_for_children_to_start(); |
234
|
2
|
|
235
|
1
|
Wait while any scheduled children exist. |
236
|
|
|
237
|
|
=cut |
238
|
2
|
while (1) { |
239
|
2
|
my $children = get_children() // die 'Failed to wait for children to start'; |
240
|
1
|
my $n = 0; |
241
|
|
for my $state (values %$children) { |
242
|
|
next if $state eq 'done' or $state eq 'cancelled' or $state eq 'running'; |
243
|
|
$n++; |
244
|
|
} |
245
|
|
|
246
|
|
bmwqemu::diag("Waiting for $n jobs to start"); |
247
|
|
last unless $n; |
248
|
|
sleep $poll_interval; |
249
|
|
} |
250
|
|
} |
251
|
1
|
|
|
1
|
|
252
|
1
|
=head2 get_current_job_id |
253
|
2
|
|
254
|
2
|
get_current_job_id(); |
255
|
2
|
|
256
|
2
|
Query openQA's API to retrieve the current job ID |
257
|
1
|
=cut |
258
|
|
my $tx = api_call_2(get => 'whoami', undef, $CODES_EXPECTED_BY_MMAPI); |
259
|
|
return undef if handle_api_error($tx, 'whoami', $CODES_EXPECTED_BY_MMAPI); |
260
|
2
|
return $tx->res->json('/id'); |
261
|
2
|
} |
262
|
1
|
|
263
|
|
1; |