• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SpiNNakerManchester / JavaSpiNNaker / 13070643287

31 Jan 2025 10:04AM UTC coverage: 38.643% (+0.01%) from 38.633%
13070643287

push

github

rowleya
Fix line length

0 of 1 new or added line in 1 file covered. (0.0%)

281 existing lines in 9 files now uncovered.

9175 of 23743 relevant lines covered (38.64%)

1.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.84
/SpiNNaker-allocserv/src/main/java/uk/ac/manchester/spinnaker/alloc/compat/V1CompatTask.java
1
/*
2
 * Copyright (c) 2021 The University of Manchester
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package uk.ac.manchester.spinnaker.alloc.compat;
17

18
import static java.lang.Thread.currentThread;
19
import static java.lang.Thread.interrupted;
20
import static java.nio.charset.StandardCharsets.UTF_8;
21
import static java.util.Objects.isNull;
22
import static java.util.Objects.nonNull;
23
import static java.util.Objects.requireNonNull;
24
import static org.apache.commons.io.IOUtils.buffer;
25
import static org.slf4j.LoggerFactory.getLogger;
26
import static uk.ac.manchester.spinnaker.alloc.compat.Utils.parseDec;
27
import static uk.ac.manchester.spinnaker.alloc.model.PowerState.OFF;
28
import static uk.ac.manchester.spinnaker.alloc.model.PowerState.ON;
29

30
import java.io.BufferedReader;
31
import java.io.IOException;
32
import java.io.InputStreamReader;
33
import java.io.InterruptedIOException;
34
import java.io.OutputStreamWriter;
35
import java.io.Reader;
36
import java.io.Writer;
37
import java.net.InetSocketAddress;
38
import java.net.Socket;
39
import java.net.SocketException;
40
import java.net.SocketTimeoutException;
41
import java.util.List;
42
import java.util.Map;
43
import java.util.Objects;
44
import java.util.Optional;
45

46
import javax.validation.Valid;
47
import javax.validation.constraints.NotBlank;
48
import javax.validation.constraints.NotNull;
49
import javax.validation.constraints.Positive;
50

51
import org.slf4j.Logger;
52

53
import com.fasterxml.jackson.core.JsonParseException;
54
import com.fasterxml.jackson.databind.JsonMappingException;
55
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
56
import com.google.errorprone.annotations.concurrent.GuardedBy;
57

58
import uk.ac.manchester.spinnaker.alloc.model.PowerState;
59
import uk.ac.manchester.spinnaker.machine.ValidP;
60
import uk.ac.manchester.spinnaker.machine.ValidX;
61
import uk.ac.manchester.spinnaker.machine.ValidY;
62
import uk.ac.manchester.spinnaker.machine.board.TriadCoords;
63
import uk.ac.manchester.spinnaker.machine.board.ValidBoardNumber;
64
import uk.ac.manchester.spinnaker.machine.board.ValidCabinetNumber;
65
import uk.ac.manchester.spinnaker.machine.board.ValidFrameNumber;
66
import uk.ac.manchester.spinnaker.machine.board.ValidTriadHeight;
67
import uk.ac.manchester.spinnaker.machine.board.ValidTriadWidth;
68
import uk.ac.manchester.spinnaker.machine.board.ValidTriadX;
69
import uk.ac.manchester.spinnaker.machine.board.ValidTriadY;
70
import uk.ac.manchester.spinnaker.machine.board.ValidTriadZ;
71
import uk.ac.manchester.spinnaker.spalloc.messages.BoardCoordinates;
72
import uk.ac.manchester.spinnaker.spalloc.messages.BoardPhysicalCoordinates;
73
import uk.ac.manchester.spinnaker.spalloc.messages.JobDescription;
74
import uk.ac.manchester.spinnaker.spalloc.messages.JobMachineInfo;
75
import uk.ac.manchester.spinnaker.spalloc.messages.JobState;
76
import uk.ac.manchester.spinnaker.spalloc.messages.Machine;
77
import uk.ac.manchester.spinnaker.spalloc.messages.WhereIs;
78
import uk.ac.manchester.spinnaker.utils.validation.IPAddress;
79

80
/**
81
 * The core of tasks that handle connections by clients.
82
 *
83
 * @author Donal Fellows
84
 */
85
public abstract class V1CompatTask extends V1CompatService.Aware {
86
        private static final Logger log = getLogger(V1CompatTask.class);
3✔
87

88
        private static final int TRIAD_COORD_COUNT = 3;
89

90
        /**
91
         * The socket that this task is handling.
92
         */
93
        private final Socket sock;
94

95
        /**
96
         * How to read from the socket. The protocol expects messages to be UTF-8
97
         * lines, with each line being a JSON document.
98
         */
99
        private final BufferedReader in;
100

101
        /**
102
         * How to write to the socket. The protocol expects messages to be UTF-8
103
         * lines, with each line being a JSON document.
104
         * <p>
105
         * Note that synchronisation will be performed on this object.
106
         */
107
        @GuardedBy("itself")
108
        private final Writer out;
109

110
        /**
111
         * Make an instance that wraps a socket.
112
         *
113
         * @param srv
114
         *            The overall service, used for looking up shared resources that
115
         *            are uncomfortable as beans.
116
         * @param sock
117
         *            The socket that talks to the client.
118
         * @throws IOException
119
         *             If access to the socket fails.
120
         */
121
        protected V1CompatTask(V1CompatService srv, Socket sock)
122
                        throws IOException {
123
                super(srv);
×
124
                this.sock = sock;
×
125
                sock.setTcpNoDelay(true);
×
126
                sock.setSoTimeout((int) getProperties().getReceiveTimeout().toMillis());
×
127

128
                in = buffer(new InputStreamReader(sock.getInputStream(), UTF_8));
×
129
                out = new OutputStreamWriter(sock.getOutputStream(), UTF_8);
×
130
        }
×
131

132
        /**
133
         * Constructor for testing. Makes a task that isn't connected to a socket.
134
         *
135
         * @param srv
136
         *            The overall service, used for looking up shared resources that
137
         *            are uncomfortable as beans.
138
         * @param in
139
         *            Input to the task.
140
         * @param out
141
         *            Output to the task.
142
         */
143
        protected V1CompatTask(V1CompatService srv, Reader in, Writer out) {
144
                super(srv);
3✔
145
                this.sock = null;
3✔
146
                this.in = buffer(in);
3✔
147
                this.out = out;
3✔
148
        }
3✔
149

150
        final void handleConnection() {
151
                log.debug("waiting for commands from {}", sock);
3✔
152
                try {
153
                        while (!interrupted()) {
3✔
154
                                if (!communicate()) {
3✔
155
                                        log.debug("Communcation break");
3✔
156
                                        break;
3✔
157
                                }
158
                        }
159
                        if (interrupted()) {
3✔
160
                                log.debug("Shutdown on interrupt");
×
161
                        }
162
                } catch (InterruptedException | InterruptedIOException interrupted) {
×
163
                        log.debug("interrupted", interrupted);
×
164
                } catch (IOException e) {
×
165
                        log.error("problem with socket {}", sock, e);
×
166
                } finally {
167
                        log.debug("closing down connection from {}", sock);
3✔
168
                        closeNotifiers();
3✔
169
                        try {
170
                                if (nonNull(sock)) {
3✔
171
                                        sock.close();
×
172
                                } else {
173
                                        in.close();
3✔
174
                                        synchronized (out) {
3✔
175
                                                out.close();
3✔
176
                                        }
3✔
177
                                }
178
                        } catch (IOException e) {
×
179
                                log.error("problem closing socket {}", sock, e);
×
180
                        }
3✔
181
                }
182
        }
3✔
183

184
        /**
185
         * Stop any current running notifiers.
186
         */
187
        protected abstract void closeNotifiers();
188

189
        /**
190
         * What host is connected to this service instance?
191
         *
192
         * @return The remote host that this task is serving.
193
         */
194
        public final String host() {
195
                if (isNull(sock)) {
3✔
196
                        return "<NOWHERE>";
3✔
197
                }
198
                return ((InetSocketAddress) sock.getRemoteSocketAddress()).getAddress()
×
199
                                .getHostAddress();
×
200
        }
201

202
        /**
203
         * Parse a command from a message.
204
         *
205
         * @param msg
206
         *            The message to parse.
207
         * @return The command.
208
         * @throws IOException
209
         *             If the message doesn't contain a valid command.
210
         */
211
        protected Command parseCommand(String msg) throws IOException {
212
                return getJsonMapper().readValue(msg, Command.class);
3✔
213
        }
214

215
        /**
216
         * Parse a command that was saved in the DB.
217
         *
218
         * @param msg
219
         *            The saved command to parse.
220
         * @return The command, or {@code null} if the message can't be parsed.
221
         */
222
        protected Command parseCommand(byte[] msg) {
223
                if (isNull(msg)) {
3✔
224
                        return null;
×
225
                }
226
                try {
227
                        return getJsonMapper().readValue(msg, Command.class);
3✔
228
                } catch (IOException e) {
×
229
                        log.error("unexpected failure parsing JSON", e);
×
230
                        return null;
×
231
                }
232
        }
233

234
        /**
235
         * Read a command message from the client. The message will have occupied
236
         * one line of text on the input stream from the socket.
237
         *
238
         * @return The parsed command message, or {@code empty} on end-of-stream.
239
         * @throws IOException
240
         *             If things go wrong, such as a bad or incomplete message.
241
         * @throws InterruptedException
242
         *             If interrupted.
243
         */
244
        private Optional<Command> readMessage()
245
                        throws IOException, InterruptedException {
246
                String line;
247
                try {
248
                        line = in.readLine();
3✔
249
                        log.debug("Incoming message: {}", line);
3✔
250
                } catch (SocketException e) {
×
251
                        /*
252
                         * Don't know why we get a generic socket exception for some of
253
                         * these, but it happens when there's been some sort of network drop
254
                         * or if the connection close happens in a weird order. Treating as
255
                         * EOF is the right thing.
256
                         *
257
                         * I also don't know why there is no nicer way of detecting this
258
                         * than matching the exception message. You'd think that you'd get
259
                         * something better, but no...
260
                         */
261
                        switch (e.getMessage()) {
×
262
                        case "Connection reset":
263
                        case "Connection timed out (Read failed)":
264
                                return Optional.empty();
×
265
                        default:
266
                                throw e;
×
267
                        }
268
                }
3✔
269
                if (currentThread().isInterrupted()) {
3✔
270
                        throw new InterruptedException();
×
271
                }
272
                if (isNull(line) || line.isBlank()) {
3✔
273
                        return Optional.empty();
3✔
274
                }
275
                var c = parseCommand(line);
3✔
276
                if (isNull(c) || isNull(c.getCommand())) {
3✔
277
                        throw new IOException("message did not specify a command");
×
278
                }
279
                log.debug("Command: {}", c);
3✔
280
                return Optional.of(c);
3✔
281
        }
282

283
        /**
284
         * Basic message send. Synchronised so that only full messages are written.
285
         *
286
         * @param msg
287
         *            The message to send. Must serializable to JSON.
288
         * @throws IOException
289
         *             If the message can't be written.
290
         */
291
        private void sendMessage(Object msg) throws IOException {
292
                // We go via a string to avoid early closing issues
293
                var data = getJsonMapper().writeValueAsString(msg);
3✔
294
                log.debug("about to send message: {}", data);
3✔
295
                // Synch so we definitely don't interleave bits of messages
296
                synchronized (out) {
3✔
297
                        out.write(data + "\r\n");
3✔
298
                        out.flush();
3✔
299
                }
3✔
300
        }
3✔
301

302
        private boolean mayWrite() {
303
                if (isNull(sock)) {
3✔
304
                        return true;
3✔
305
                }
306
                return !sock.isClosed();
×
307
        }
308

309
        /**
310
         * Send a response message.
311
         *
312
         * @param response
313
         *            The body object of the response.
314
         * @throws IOException
315
         *             If network access fails, or the object isn't serializable as
316
         *             JSON or a suitable primitive.
317
         */
318
        protected final void writeResponse(Object response) throws IOException {
319
                if (mayWrite()) {
3✔
320
                        sendMessage(new ReturnResponse(response));
3✔
321
                }
322
        }
3✔
323

324
        /**
325
         * Send an exception message.
326
         *
327
         * @param exn
328
         *            A description of the exception.
329
         * @throws IOException
330
         *             If network access fails.
331
         */
332
        protected final void writeException(Throwable exn) throws IOException {
333
                if (mayWrite()) {
3✔
334
                        if (nonNull(exn.getMessage())) {
3✔
335
                                sendMessage(new ExceptionResponse(exn.getMessage()));
3✔
336
                        } else {
337
                                sendMessage(new ExceptionResponse(exn.toString()));
×
338
                        }
339
                }
340
        }
3✔
341

342
        /**
343
         * Send a notification about a collection of jobs changing.
344
         *
345
         * @param jobIds
346
         *            The jobs that have changed. (Usually <em>all</em> jobs.)
347
         * @throws IOException
348
         *             If network access fails.
349
         */
350
        protected final void writeJobNotification(List<Integer> jobIds)
351
                        throws IOException {
352
                if (!jobIds.isEmpty() && mayWrite()) {
×
353
                        sendMessage(new JobNotifyMessage(jobIds));
×
354
                }
355
        }
×
356

357
        /**
358
         * Send a notification about a collection of machines changing.
359
         *
360
         * @param machineNames
361
         *            The machines that have changed. (Usually <em>all</em>
362
         *            machines.)
363
         * @throws IOException
364
         *             If network access fails.
365
         */
366
        protected final void writeMachineNotification(
367
                        List<String> machineNames)
368
                        throws IOException {
369
                if (!machineNames.isEmpty() && mayWrite()) {
×
370
                        sendMessage(new MachineNotifyMessage(machineNames));
×
371
                }
372
        }
×
373

374
        /**
375
         * Read a message from the client and send a response.
376
         *
377
         * @return {@code true} if further messages should be processed,
378
         *         {@code false} if the connection should be closed.
379
         * @throws IOException
380
         *             If network access fails.
381
         * @throws InterruptedException
382
         *             If interrupted (happens on service shutdown).
383
         */
384
        public final boolean communicate()
385
                        throws IOException, InterruptedException {
386
                Command cmd;
387
                try {
388
                        var c = readMessage();
3✔
389
                        if (!c.isPresent()) {
3✔
390
                                log.debug("null message");
3✔
391
                                return false;
3✔
392
                        }
393
                        cmd = c.orElseThrow();
3✔
394
                } catch (SocketTimeoutException e) {
×
395
                        log.trace("timeout");
×
396
                        // Message was not read by time timeout expired
397
                        return !currentThread().isInterrupted();
×
398
                } catch (MismatchedInputException e) {
3✔
399
                        log.error("Error on message reception: {}", e.getMessage());
3✔
400
                        writeException(e);
3✔
401
                        return true;
3✔
402
                } catch (JsonMappingException | JsonParseException e) {
×
403
                        log.error("Error on message reception", e);
×
404
                        writeException(e);
×
405
                        return true;
×
406
                }
3✔
407

408
                Object r;
409
                try {
410
                        r = callOperation(cmd);
3✔
411
                } catch (Oops | TaskException | IllegalArgumentException e) {
3✔
412
                        // Expected exceptions; don't log
413
                        writeException(e);
3✔
414
                        return true;
3✔
415
                } catch (Exception e) {
×
416
                        log.warn("unexpected exception from {} operation",
×
417
                                        cmd.getCommand(), e);
×
418
                        writeException(e);
×
419
                        return true;
×
420
                }
3✔
421

422
                log.debug("responded with {}", r);
3✔
423
                writeResponse(r);
3✔
424
                return true;
3✔
425
        }
426

427
        /**
428
         * Decode the command to convert into a method to call.
429
         *
430
         * @param cmd
431
         *            The command.
432
         * @return The result of the command. Can be anything (<em>including</em>
433
         *         {@code null}) as long as it can be serialised.
434
         * @throws Exception
435
         *             If things go wrong
436
         */
437
        private Object callOperation(Command cmd) throws Exception {
438
                log.debug("calling operation '{}'", cmd.getCommand());
3✔
439
                var args = cmd.getArgs();
3✔
440
                var kwargs = cmd.getKwargs();
3✔
441
                switch (cmd.getCommand()) {
3✔
442
                case "create_job":
443
                        // This is three operations really, and an optional parameter.
444
                        byte[] serialCmd = getJsonMapper().writeValueAsBytes(cmd);
3✔
445
                        switch (args.size()) {
3✔
446
                        case 0:
447
                                return createJobNumBoards(1, kwargs, serialCmd);
3✔
448
                        case 1:
449
                                return createJobNumBoards(parseDec(args, 0), kwargs, serialCmd);
3✔
450
                        case 2:
451
                                return createJobRectangle(parseDec(args, 0), parseDec(args, 1),
3✔
452
                                                kwargs, serialCmd);
453
                        case TRIAD_COORD_COUNT:
454
                                return createJobSpecificBoard(new TriadCoords(parseDec(args, 0),
3✔
455
                                                parseDec(args, 1), parseDec(args, 2)), kwargs,
3✔
456
                                                serialCmd);
457
                        default:
458
                                throw new Oops(
3✔
459
                                                "unsupported number of arguments: " + args.size());
3✔
460
                        }
461
                case "destroy_job":
462
                        destroyJob(parseDec(args, 0), (String) kwargs.get("reason"));
3✔
463
                        break;
3✔
464
                case "get_board_at_position":
465
                        return requireNonNull(getBoardAtPhysicalPosition(
3✔
466
                                        (String) kwargs.get("machine_name"), parseDec(kwargs, "x"),
3✔
467
                                        parseDec(kwargs, "y"), parseDec(kwargs, "z")));
3✔
468
                case "get_board_position":
469
                        return requireNonNull(getBoardAtLogicalPosition(
3✔
470
                                        (String) kwargs.get("machine_name"), parseDec(kwargs, "x"),
3✔
471
                                        parseDec(kwargs, "y"), parseDec(kwargs, "z")));
3✔
472
                case "get_job_machine_info":
473
                        return requireNonNull(getJobMachineInfo(parseDec(args, 0)));
3✔
474
                case "get_job_state":
475
                        return requireNonNull(getJobState(parseDec(args, 0)));
3✔
476
                case "job_keepalive":
477
                        jobKeepalive(parseDec(args, 0));
3✔
478
                        break;
3✔
479
                case "list_jobs":
480
                        return requireNonNull(listJobs());
3✔
481
                case "list_machines":
482
                        return requireNonNull(listMachines());
3✔
483
                case "no_notify_job":
484
                        notifyJob(optInt(args), false);
3✔
485
                        break;
3✔
486
                case "no_notify_machine":
487
                        notifyMachine(optStr(args), false);
3✔
488
                        break;
3✔
489
                case "notify_job":
490
                        notifyJob(optInt(args), true);
3✔
491
                        break;
3✔
492
                case "notify_machine":
493
                        notifyMachine(optStr(args), true);
3✔
494
                        break;
3✔
495
                case "power_off_job_boards":
496
                        powerJobBoards(parseDec(args, 0), OFF);
3✔
497
                        break;
3✔
498
                case "power_on_job_boards":
UNCOV
499
                        powerJobBoards(parseDec(args, 0), ON);
×
500
                        break;
×
501
                case "version":
502
                        return requireNonNull(version());
3✔
503
                case "where_is":
504
                        // This is four operations in a trench coat
505
                        if (kwargs.containsKey("job_id")) {
3✔
506
                                return requireNonNull(whereIsJobChip(parseDec(kwargs, "job_id"),
3✔
507
                                                parseDec(kwargs, "chip_x"),
3✔
508
                                                parseDec(kwargs, "chip_y")));
3✔
509
                        } else if (!kwargs.containsKey("machine")) {
3✔
UNCOV
510
                                throw new Oops("missing parameter: machine");
×
511
                        }
512
                        var m = (String) kwargs.get("machine");
3✔
513
                        if (kwargs.containsKey("chip_x")) {
3✔
514
                                return requireNonNull(
3✔
515
                                                whereIsMachineChip(m, parseDec(kwargs, "chip_x"),
3✔
516
                                                                parseDec(kwargs, "chip_y")));
3✔
517
                        } else if (kwargs.containsKey("x")) {
3✔
518
                                return requireNonNull(
3✔
519
                                                whereIsMachineLogicalBoard(m, parseDec(kwargs, "x"),
3✔
520
                                                                parseDec(kwargs, "y"), parseDec(kwargs, "z")));
3✔
521
                        } else if (kwargs.containsKey("cabinet")) {
3✔
522
                                return requireNonNull(whereIsMachinePhysicalBoard(m,
3✔
523
                                                parseDec(kwargs, "cabinet"), parseDec(kwargs, "frame"),
3✔
524
                                                parseDec(kwargs, "board")));
3✔
525
                        } else {
UNCOV
526
                                throw new Oops("missing parameter: chip_x, x, or cabinet");
×
527
                        }
528
                case "report_problem":
UNCOV
529
                        var ip = args.get(0).toString();
×
530
                        Integer x = null, y = null, p = null;
×
531
                        var desc = "It doesn't work and I don't know why.";
×
532
                        if (kwargs.containsKey("x")) {
×
533
                                x = parseDec(kwargs, "x");
×
534
                                y = parseDec(kwargs, "y");
×
535
                                if (kwargs.containsKey("p")) {
×
536
                                        p = parseDec(kwargs, "p");
×
537
                                }
538
                        }
UNCOV
539
                        if (kwargs.containsKey("description")) {
×
540
                                desc = Objects.toString(kwargs.get("description"));
×
541
                        }
UNCOV
542
                        reportProblem(ip, x, y, p, desc);
×
543
                        break;
×
544
                case "login":
UNCOV
545
                        throw new Oops("upgrading security is not supported");
×
546
                default:
UNCOV
547
                        throw new Oops("unknown command: " + cmd.getCommand());
×
548
                }
549
                return null;
3✔
550
        }
551

552
        /**
553
         * Create a job asking for a number of boards.
554
         *
555
         * @param numBoards
556
         *            Number of boards.
557
         * @param kwargs
558
         *            Keyword argument map.
559
         * @param cmd
560
         *            The actual command, as serialised JSON.
561
         * @return Job identifier.
562
         * @throws TaskException
563
         *             If anything goes wrong.
564
         */
565
        protected abstract int createJobNumBoards(
566
                        @Positive int numBoards,
567
                        Map<@NotBlank String, @NotNull Object> kwargs, byte[] cmd)
568
                        throws TaskException;
569

570
        /**
571
         * Create a job asking for a rectangle of boards.
572
         *
573
         * @param width
574
         *            Width of rectangle in triads.
575
         * @param height
576
         *            Height of rectangle in triads.
577
         * @param kwargs
578
         *            Keyword argument map.
579
         * @param cmd
580
         *            The actual command, as serialised JSON.
581
         * @return Job identifier.
582
         * @throws TaskException
583
         *             If anything goes wrong.
584
         */
585
        protected abstract int createJobRectangle(
586
                        @ValidTriadWidth int width, @ValidTriadHeight int height,
587
                        Map<@NotBlank String, @NotNull Object> kwargs, byte[] cmd)
588
                        throws TaskException;
589

590
        /**
591
         * Create a job asking for a specific board.
592
         *
593
         * @param coords
594
         *            Which board, by its logical coordinates.
595
         * @param kwargs
596
         *            Keyword argument map.
597
         * @param cmd
598
         *            The actual command, as serialised JSON.
599
         * @return Job identifier. Never {@code null}.
600
         * @throws TaskException
601
         *             If anything goes wrong.
602
         */
603
        protected abstract int createJobSpecificBoard(
604
                        @Valid TriadCoords coords,
605
                        Map<@NotBlank String, @NotNull Object> kwargs, byte[] cmd)
606
                        throws TaskException;
607

608
        /**
609
         * Destroy a job.
610
         *
611
         * @param jobId
612
         *            Job identifier.
613
         * @param reason
614
         *            Why the machine is being destroyed.
615
         * @throws TaskException
616
         *             If anything goes wrong.
617
         */
618
        protected abstract void destroyJob(int jobId, String reason)
619
                        throws TaskException;
620

621
        /**
622
         * Get the coordinates of a board at a physical location.
623
         *
624
         * @param machineName
625
         *            Name of the machine.
626
         * @param cabinet
627
         *            Cabinet number.
628
         * @param frame
629
         *            Frame number.
630
         * @param board
631
         *            Board number.
632
         * @return Logical location. Never {@code null}.
633
         * @throws TaskException
634
         *             If anything goes wrong.
635
         */
636
        protected abstract BoardCoordinates getBoardAtPhysicalPosition(
637
                        @NotBlank String machineName, @ValidCabinetNumber int cabinet,
638
                        @ValidFrameNumber int frame, @ValidBoardNumber int board)
639
                        throws TaskException;
640

641
        /**
642
         * Get the physical location of a board at given coordinates.
643
         *
644
         * @param machineName
645
         *            Name of the machine.
646
         * @param x
647
         *            Triad X coordinate.
648
         * @param y
649
         *            Triad Y coordinate.
650
         * @param z
651
         *            Triad Z coordinate.
652
         * @return Physical location. Never {@code null}.
653
         * @throws TaskException
654
         *             If anything goes wrong.
655
         */
656
        protected abstract BoardPhysicalCoordinates getBoardAtLogicalPosition(
657
                        @NotBlank String machineName, @ValidTriadX int x,
658
                        @ValidTriadY int y, @ValidTriadZ int z) throws TaskException;
659

660
        /**
661
         * Get information about the machine allocated to a job.
662
         *
663
         * @param jobId
664
         *            Job identifier.
665
         * @return Description of job's (sub)machine. Never {@code null}.
666
         * @throws TaskException
667
         *             If anything goes wrong.
668
         */
669
        protected abstract JobMachineInfo getJobMachineInfo(int jobId)
670
                        throws TaskException;
671

672
        /**
673
         * Get the state of a job.
674
         *
675
         * @param jobId
676
         *            Job identifier.
677
         * @return State description. Never {@code null}.
678
         * @throws TaskException
679
         *             If anything goes wrong.
680
         */
681
        protected abstract JobState getJobState(int jobId) throws TaskException;
682

683
        /**
684
         * Mark a job as still being kept alive.
685
         *
686
         * @param jobId
687
         *            Job identifier.
688
         * @throws TaskException
689
         *             If anything goes wrong.
690
         */
691
        protected abstract void jobKeepalive(int jobId) throws TaskException;
692

693
        /**
694
         * List the jobs.
695
         *
696
         * @return Descriptions of jobs on all machines. Never {@code null}.
697
         * @throws TaskException
698
         *             If anything goes wrong.
699
         */
700
        protected abstract JobDescription[] listJobs() throws TaskException;
701

702
        /**
703
         * List the machines.
704
         *
705
         * @return Descriptions of all machines. Never {@code null}.
706
         * @throws TaskException
707
         *             If anything goes wrong.
708
         */
709
        protected abstract Machine[] listMachines() throws TaskException;
710

711
        /**
712
         * Request notification of job status changes. Best effort only.
713
         *
714
         * @param jobId
715
         *            Job identifier. May be {@code null} to talk about any job.
716
         * @param wantNotify
717
         *            Whether to enable or disable these notifications.
718
         * @throws TaskException
719
         *             If anything goes wrong.
720
         */
721
        protected abstract void notifyJob(Integer jobId, boolean wantNotify)
722
                        throws TaskException;
723

724
        /**
725
         * Request notification of machine status changes. Best effort only.
726
         *
727
         * @param machineName
728
         *            Name of the machine. May be {@code null} to talk about any
729
         *            machine.
730
         * @param wantNotify
731
         *            Whether to enable or disable these notifications.
732
         * @throws TaskException
733
         *             If anything goes wrong.
734
         */
735
        protected abstract void notifyMachine(String machineName,
736
                        boolean wantNotify) throws TaskException;
737

738
        /**
739
         * Switch on or off a job's boards.
740
         *
741
         * @param jobId
742
         *            Job identifier.
743
         * @param switchOn
744
         *            Whether to switch on.
745
         * @throws TaskException
746
         *             If anything goes wrong.
747
         */
748
        protected abstract void powerJobBoards(int jobId, PowerState switchOn)
749
                        throws TaskException;
750

751
        /**
752
         * Report a problem with a board, chip or core. If a whole chip has a
753
         * problem, {@code p} will be {@code null}. If a whole board has a problem,
754
         * {@code x,y} will be {@code null,null}.
755
         *
756
         * @param address
757
         *            The board's IP address.
758
         * @param x
759
         *            The chip's X coordinate.
760
         * @param y
761
         *            The chip's Y coordinate.
762
         * @param p
763
         *            The core's P coordinate.
764
         * @param description
765
         *            Optional descriptive text about the problem.
766
         */
767
        protected abstract void reportProblem(@IPAddress String address,
768
                        @ValidX Integer x, @ValidY Integer y, @ValidP Integer p,
769
                        String description);
770

771
        /**
772
         * Get the service version.
773
         *
774
         * @return The service version. Never {@code null}.
775
         * @throws TaskException
776
         *             If anything goes wrong.
777
         */
778
        protected abstract String version() throws TaskException;
779

780
        /**
781
         * Describe where a chip is within a job.
782
         *
783
         * @param jobId
784
         *            Job identifier.
785
         * @param x
786
         *            Chip X coordinate.
787
         * @param y
788
         *            Chip Y coordinate.
789
         * @return Descriptor. Never {@code null}.
790
         * @throws TaskException
791
         *             If anything goes wrong.
792
         */
793
        protected abstract WhereIs whereIsJobChip(int jobId, @ValidX int x,
794
                        @ValidY int y) throws TaskException;
795

796
        /**
797
         * Describe where a chip is within a machine.
798
         *
799
         * @param machineName
800
         *            Name of the machine.
801
         * @param x
802
         *            Chip X coordinate.
803
         * @param y
804
         *            Chip Y coordinate.
805
         * @return Descriptor. Never {@code null}.
806
         * @throws TaskException
807
         *             If anything goes wrong.
808
         */
809
        protected abstract WhereIs whereIsMachineChip(@NotBlank String machineName,
810
                        @ValidX int x, @ValidY int y) throws TaskException;
811

812
        /**
813
         * Describe where a board is within a machine.
814
         *
815
         * @param machineName
816
         *            Name of the machine.
817
         * @param x
818
         *            Triad X coordinate.
819
         * @param y
820
         *            Triad Y coordinate.
821
         * @param z
822
         *            Triad Z coordinate.
823
         * @return Descriptor. Never {@code null}.
824
         * @throws TaskException
825
         *             If anything goes wrong.
826
         */
827
        protected abstract WhereIs whereIsMachineLogicalBoard(
828
                        @NotBlank String machineName, @ValidTriadX int x,
829
                        @ValidTriadY int y, @ValidTriadZ int z) throws TaskException;
830

831
        /**
832
         * Describe where a board is within a machine.
833
         *
834
         * @param machineName
835
         *            Name of the machine.
836
         * @param cabinet
837
         *            Cabinet number.
838
         * @param frame
839
         *            Frame number.
840
         * @param board
841
         *            Board number.
842
         * @return Descriptor. Never {@code null}.
843
         * @throws TaskException
844
         *             If anything goes wrong.
845
         */
846
        protected abstract WhereIs whereIsMachinePhysicalBoard(String machineName,
847
                        @ValidCabinetNumber int cabinet, @ValidFrameNumber int frame,
848
                        @ValidBoardNumber int board) throws TaskException;
849

850
        private static Integer optInt(List<?> args) {
851
                return args.isEmpty() ? null : parseDec(args, 0);
3✔
852
        }
853

854
        private static String optStr(List<?> args) {
855
                return args.isEmpty() ? null : Objects.toString(args.get(0), null);
3✔
856
        }
857
}
858

859
/** Indicates a failure to parse a command. */
860
final class Oops extends RuntimeException {
861
        private static final long serialVersionUID = 1L;
862

863
        Oops(String msg) {
864
                super(msg);
3✔
865
        }
3✔
866
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc