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

SpiNNakerManchester / JavaSpiNNaker / 6233274834

19 Sep 2023 08:46AM UTC coverage: 36.409% (-0.6%) from 36.982%
6233274834

Pull #658

github

dkfellows
Merge branch 'master' into java-17
Pull Request #658: Update Java version to 17

1656 of 1656 new or added lines in 260 files covered. (100.0%)

8373 of 22997 relevant lines covered (36.41%)

0.36 hits per line

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

78.95
/SpiNNaker-allocserv/src/main/java/uk/ac/manchester/spinnaker/alloc/compat/V1TaskImpl.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.util.Objects.isNull;
19
import static java.util.Objects.nonNull;
20
import static java.util.stream.Collectors.toList;
21
import static java.util.stream.Collectors.toMap;
22
import static org.apache.commons.lang3.StringUtils.isAsciiPrintable;
23
import static org.slf4j.LoggerFactory.getLogger;
24
import static uk.ac.manchester.spinnaker.alloc.allocator.SpallocAPI.CreateBoard.triad;
25
import static uk.ac.manchester.spinnaker.alloc.compat.Utils.parseDec;
26
import static uk.ac.manchester.spinnaker.alloc.compat.Utils.state;
27
import static uk.ac.manchester.spinnaker.alloc.compat.Utils.timestamp;
28
import static uk.ac.manchester.spinnaker.alloc.model.PowerState.ON;
29
import static uk.ac.manchester.spinnaker.machine.MachineDefaults.HALF_SIZE;
30
import static uk.ac.manchester.spinnaker.machine.MachineDefaults.SIZE_X_OF_ONE_BOARD;
31
import static uk.ac.manchester.spinnaker.machine.MachineDefaults.SIZE_Y_OF_ONE_BOARD;
32
import static uk.ac.manchester.spinnaker.machine.MachineDefaults.TRIAD_HEIGHT;
33
import static uk.ac.manchester.spinnaker.machine.MachineDefaults.TRIAD_WIDTH;
34
import static uk.ac.manchester.spinnaker.utils.CollectionUtils.collectToArray;
35
import static uk.ac.manchester.spinnaker.utils.UnitConstants.NSEC_PER_SEC;
36

37
import java.io.IOException;
38
import java.io.Reader;
39
import java.io.Writer;
40
import java.net.Socket;
41
import java.time.Duration;
42
import java.util.ArrayList;
43
import java.util.HashMap;
44
import java.util.List;
45
import java.util.Map;
46
import java.util.Objects;
47
import java.util.Optional;
48
import java.util.concurrent.Future;
49

50
import javax.annotation.PostConstruct;
51

52
import org.slf4j.Logger;
53
import org.springframework.beans.factory.annotation.Autowired;
54
import org.springframework.dao.DataAccessException;
55
import org.springframework.stereotype.Component;
56

57
import uk.ac.manchester.spinnaker.alloc.SpallocProperties;
58
import uk.ac.manchester.spinnaker.alloc.allocator.Epochs;
59
import uk.ac.manchester.spinnaker.alloc.allocator.SpallocAPI;
60
import uk.ac.manchester.spinnaker.alloc.allocator.SpallocAPI.BoardLocation;
61
import uk.ac.manchester.spinnaker.alloc.allocator.SpallocAPI.CreateDimensions;
62
import uk.ac.manchester.spinnaker.alloc.allocator.SpallocAPI.CreateNumBoards;
63
import uk.ac.manchester.spinnaker.alloc.allocator.SpallocAPI.Job;
64
import uk.ac.manchester.spinnaker.alloc.compat.Utils.Notifier;
65
import uk.ac.manchester.spinnaker.alloc.db.DatabaseAwareBean;
66
import uk.ac.manchester.spinnaker.alloc.model.JobListEntryRecord;
67
import uk.ac.manchester.spinnaker.alloc.model.PowerState;
68
import uk.ac.manchester.spinnaker.alloc.model.Prototype;
69
import uk.ac.manchester.spinnaker.alloc.security.Permit;
70
import uk.ac.manchester.spinnaker.machine.ChipLocation;
71
import uk.ac.manchester.spinnaker.machine.CoreLocation;
72
import uk.ac.manchester.spinnaker.machine.HasChipLocation;
73
import uk.ac.manchester.spinnaker.machine.board.PhysicalCoords;
74
import uk.ac.manchester.spinnaker.machine.board.TriadCoords;
75
import uk.ac.manchester.spinnaker.spalloc.messages.BoardCoordinates;
76
import uk.ac.manchester.spinnaker.spalloc.messages.BoardPhysicalCoordinates;
77
import uk.ac.manchester.spinnaker.spalloc.messages.Connection;
78
import uk.ac.manchester.spinnaker.spalloc.messages.JobDescription;
79
import uk.ac.manchester.spinnaker.spalloc.messages.JobMachineInfo;
80
import uk.ac.manchester.spinnaker.spalloc.messages.JobState;
81
import uk.ac.manchester.spinnaker.spalloc.messages.Machine;
82
import uk.ac.manchester.spinnaker.spalloc.messages.WhereIs;
83

84
/**
85
 * Concrete implementation of a task handling a single client connection. This
86
 * is a <em>prototype bean</em>; Spring will instantiate it whenever needed.
87
 * It's consumed by {@link V1CompatService}, which uses it to implement the
88
 * handler for a particular connection.
89
 *
90
 * @author Donal Fellows
91
 */
92
@Component
93
@Prototype
94
class V1TaskImpl extends V1CompatTask {
95

96
        /**
97
         * We are compatible with spalloc-server release version 5.0.0.
98
         */
99
        public static final String VERSION = "5.0.0";
100

101
        private static final int LOTS = 10000;
102

103
        private static final Logger log = getLogger(V1CompatService.class);
1✔
104

105
        private final Map<Integer, Future<Void>> jobNotifiers = new HashMap<>();
1✔
106

107
        private final Map<String, Future<Void>> machNotifiers = new HashMap<>();
1✔
108

109
        /** The overall service properties. */
110
        @Autowired
111
        private SpallocProperties mainProps;
112

113
        /** The core spalloc service. */
114
        @Autowired
115
        private SpallocAPI spalloc;
116

117
        @Autowired
118
        private CompatHelper helper;
119

120
        @Autowired
121
        private Epochs epochs;
122

123
        /** Encoded form of our special permissions token. */
124
        private Permit permit;
125

126
        /** Name of the group for accounting purposes. */
127
        private String groupName;
128

129
        V1TaskImpl(V1CompatService srv, Socket sock) throws IOException {
130
                super(srv, sock);
×
131
        }
×
132

133
        V1TaskImpl(V1CompatService srv, Reader in, Writer out) {
134
                super(srv, in, out);
1✔
135
        }
1✔
136

137
        @PostConstruct
138
        void initUser() {
139
                var props = mainProps.getCompat();
1✔
140
                permit = new Permit(props.getServiceUser());
1✔
141
                groupName = props.getServiceGroup();
1✔
142
        }
1✔
143

144
        @Override
145
        protected final void closeNotifiers() {
146
                for (var n : jobNotifiers.values()) {
1✔
147
                        n.cancel(true);
×
148
                }
×
149
                for (var n : machNotifiers.values()) {
1✔
150
                        n.cancel(true);
×
151
                }
×
152
        }
1✔
153

154
        @Override
155
        protected final String version() {
156
                return VERSION;
1✔
157
        }
158

159
        @Override
160
        protected final JobMachineInfo getJobMachineInfo(int jobId)
161
                        throws TaskException {
162
                var job = getJob(jobId);
1✔
163
                job.access(host());
1✔
164
                var machine = job.getMachine()
1✔
165
                                .orElseThrow(() -> new TaskException("boards not allocated"));
1✔
166
                int w, h;
167
                if (machine.getDepth() == 1) {
1✔
168
                        // Special case: single board alloc
169
                        w = SIZE_X_OF_ONE_BOARD;
1✔
170
                        h = SIZE_Y_OF_ONE_BOARD;
1✔
171
                } else {
172
                        // Everything else is in terms of triads; careful of wraparounds
173
                        var full = machine.getMachine();
×
174
                        int linearTriadSize = job.getWidth().orElse(0);
×
175
                        w = linearTriadSize * TRIAD_WIDTH;
×
176
                        if (linearTriadSize != full.getWidth()
×
177
                                        || !full.isHorizonallyWrapped()) {
×
178
                                w += HALF_SIZE;
×
179
                        }
180
                        linearTriadSize = job.getHeight().orElse(0);
×
181
                        h = linearTriadSize * TRIAD_HEIGHT;
×
182
                        if (linearTriadSize != full.getHeight()
×
183
                                        || !full.isVerticallyWrapped()) {
×
184
                                h += HALF_SIZE;
×
185
                        }
186
                }
187
                return new JobMachineInfo(w, h, machine.getConnections().stream()
1✔
188
                                .map(ci -> new Connection(ci.chip(), ci.hostname()))
1✔
189
                                .collect(toList()), machine.getMachine().getName(),
1✔
190
                                machine.getBoards());
1✔
191
        }
192

193
        @Override
194
        protected final JobState getJobState(int jobId) throws TaskException {
195
                var job = getJob(jobId);
1✔
196
                job.access(host());
1✔
197
                var js = new JobState.Builder();
1✔
198
                js.setKeepalive(timestamp(job.getKeepaliveTimestamp()));
1✔
199
                js.setKeepalivehost(job.getKeepaliveHost().orElse(""));
1✔
200
                js.setPower(job.getMachine().map(m -> {
1✔
201
                        try {
202
                                return m.getPower() == ON;
1✔
203
                        } catch (DataAccessException e) {
×
204
                                log.warn("problem getting job power state", e);
×
205
                                return false;
×
206
                        }
207
                }).orElse(false));
1✔
208
                js.setReason(job.getReason().orElse(""));
1✔
209
                js.setStartTime(timestamp(job.getStartTime()));
1✔
210
                js.setState(state(job.getState()));
1✔
211
                return js.build();
1✔
212
        }
213

214
        @Override
215
        protected final void jobKeepalive(int jobId) throws TaskException {
216
                getJob(jobId).access(host());
1✔
217
        }
1✔
218

219
        /**
220
         * Parse a value to get a keepalive duration.
221
         *
222
         * @param keepalive
223
         *            The number to parse. May be {@code null} to get a default.
224
         * @return The duration. Never {@code null}.
225
         */
226
        private Duration parseKeepalive(Number keepalive) {
227
                if (isNull(keepalive)) {
1✔
228
                        return mainProps.getCompat().getDefaultKeepalive();
1✔
229
                }
230
                var d = Duration.ofSeconds(keepalive.longValue());
×
231
                if (!(keepalive instanceof Double || keepalive instanceof Float)) {
×
232
                        return d;
×
233
                }
234
                double fractionalPart = keepalive.doubleValue() - keepalive.longValue();
×
235
                return d.plusNanos((long) (fractionalPart * NSEC_PER_SEC));
×
236
        }
237

238
        /**
239
         * Parse a value to get a list of machine tags to match.
240
         *
241
         * @param src
242
         *            The value to parse.
243
         * @param mayForceDefault
244
         *            Whether we want to force a default value into the tags.
245
         * @return The list of tags. Never {@code null}.
246
         */
247
        private static List<String> tags(Object src, boolean mayForceDefault) {
248
                var vals = new ArrayList<String>();
1✔
249
                if (src instanceof List<?> lst) {
1✔
250
                        for (var o : lst) {
×
251
                                vals.add(Objects.toString(o));
×
252
                        }
×
253
                } else if (src instanceof String s) {
1✔
254
                        vals.add(s);
×
255
                }
256
                if (vals.isEmpty() && mayForceDefault) {
1✔
257
                        return List.of("default");
×
258
                }
259
                return vals;
1✔
260
        }
261

262
        @Override
263
        protected final Optional<Integer> createJobNumBoards(int numBoards,
264
                        Map<String, Object> kwargs, byte[] cmd) throws TaskException {
265
                var maxDead = parseDec(kwargs.get("max_dead_boards"));
1✔
266
                return createJob(new CreateNumBoards(numBoards, maxDead), kwargs, cmd);
1✔
267
        }
268

269
        @Override
270
        protected final Optional<Integer> createJobRectangle(int width, int height,
271
                        Map<String, Object> kwargs, byte[] cmd) throws TaskException {
272
                var maxDead = parseDec(kwargs.get("max_dead_boards"));
1✔
273
                return createJob(new CreateDimensions(width, height, maxDead), kwargs,
1✔
274
                                cmd);
275
        }
276

277
        @Override
278
        protected final Optional<Integer> createJobSpecificBoard(TriadCoords coords,
279
                        Map<String, Object> kwargs, byte[] cmd) throws TaskException {
280
                return createJob(triad(coords.x(), coords.y(), coords.z()), kwargs,
1✔
281
                                cmd);
282
        }
283

284
        private static String getOwner(Map<String, Object> kwargs)
285
                        throws TaskException {
286
                var owner = Objects.toString(kwargs.get("owner"), "").strip();
1✔
287
                if (owner.isBlank()) {
1✔
288
                        throw new TaskException(
×
289
                                        "invalid owner identifier; must be non-empty");
290
                }
291
                if (!isAsciiPrintable(owner)) {
1✔
292
                        throw new TaskException(
×
293
                                        "invalid owner identifier; must be printable ASCII");
294
                }
295
                return owner;
1✔
296
        }
297

298
        private Optional<Integer> createJob(SpallocAPI.CreateDescriptor create,
299
                        Map<String, Object> kwargs, byte[] cmd) throws TaskException {
300
                var owner = getOwner(kwargs);
1✔
301
                var keepalive = parseKeepalive((Number) kwargs.get("keepalive"));
1✔
302
                var machineName = (String) kwargs.get("machine");
1✔
303
                var ts = tags(kwargs.get("tags"), isNull(machineName));
1✔
304
                var result = permit.authorize(() -> spalloc.createJobInGroup(
1✔
305
                                permit.name, groupName, create, machineName, ts, keepalive,
306
                                cmd));
307
                result.ifPresent(
1✔
308
                                j -> log.info(
1✔
309
                                                "made compatibility-mode job {} "
310
                                                                + "on behalf of claimed user {}",
311
                                                j.getId(), owner));
1✔
312
                return result.map(Job::getId);
1✔
313
        }
314

315
        @Override
316
        protected final void destroyJob(int jobId, String reason)
317
                        throws TaskException {
318
                getJob(jobId).destroy(reason);
1✔
319
        }
1✔
320

321
        @Override
322
        protected final BoardCoordinates getBoardAtPhysicalPosition(
323
                        String machineName, int cabinet, int frame, int board)
324
                        throws TaskException {
325
                return getMachine(machineName)
1✔
326
                                .getBoardByPhysicalCoords(
1✔
327
                                                new PhysicalCoords(cabinet, frame, board))
328
                                .orElseThrow(() -> new TaskException("no such board"))
1✔
329
                                .getLogical();
1✔
330
        }
331

332
        @Override
333
        protected final BoardPhysicalCoordinates getBoardAtLogicalPosition(
334
                        String machineName, int x, int y, int z) throws TaskException {
335
                return getMachine(machineName)
1✔
336
                                .getBoardByLogicalCoords(new TriadCoords(x, y, z))
1✔
337
                                .orElseThrow(() -> new TaskException("no such board"))
1✔
338
                                .getPhysical();
1✔
339
        }
340

341
        @Component
342
        @SuppressWarnings("checkstyle:finalclass")
343
        private static class CompatHelper extends DatabaseAwareBean {
344
                /** The core spalloc service. */
345
                @Autowired
346
                private SpallocAPI spalloc;
347

348
                private JobDescription[] listJobs(V1TaskImpl task) {
349
                        // Messy; hits the database many times
350
                        return spalloc.listJobs(task.permit).stream()
1✔
351
                                        .map(job -> buildJobDescription(task, job))
1✔
352
                                        .collect(collectToArray(JobDescription[]::new));
1✔
353
                }
354

355
                private static JobDescription buildJobDescription(V1TaskImpl task,
356
                                JobListEntryRecord job) {
357
                        var jd = new JobDescription.Builder();
1✔
358
                        jd.setJobID(job.getId());
1✔
359
                        jd.setOwner(job.getOwner().orElse("<Hidden>"));
1✔
360
                        jd.setKeepAlive(job.getKeepaliveInterval().getSeconds());
1✔
361
                        jd.setKeepAliveHost(job.getHost().orElse("<Hidden>"));
1✔
362
                        jd.setReason("");
1✔
363
                        jd.setStartTime(
1✔
364
                                        (double) job.getCreationTimestamp().getEpochSecond());
1✔
365
                        jd.setState(state(job.getState()));
1✔
366
                        job.getOriginalRequest().map(task::parseCommand).ifPresent(cmd -> {
1✔
367
                                // In order to get here, this must be safe
368
                                // Validation was when job was created
369
                                @SuppressWarnings({ "unchecked", "rawtypes" })
370
                                List<Integer> args = (List) cmd.getArgs();
1✔
371
                                jd.setArgs(args);
1✔
372
                                jd.setKwargs(cmd.getKwargs());
1✔
373
                                // Override shrouded owner from above
374
                                var owner = cmd.getKwargs().get("owner");
1✔
375
                                if (owner != null) {
1✔
376
                                        jd.setOwner(owner.toString());
1✔
377
                                }
378
                        });
1✔
379
                        jd.setMachine(job.getMachineName());
1✔
380
                        jd.setPower(job.isPowered());
1✔
381
                        jd.setBoards(job.getBoards().stream().map(
1✔
382
                                        b -> new BoardCoordinates(b.x(), b.y(), b.z()))
×
383
                                        .collect(toList()));
1✔
384
                        return jd.build();
1✔
385
                }
386

387
                private Machine[] listMachines() {
388
                        // Messy; hits the database many times
389
                        return executeRead(c -> spalloc.getMachines(false).values().stream()
1✔
390
                                        .map(CompatHelper::buildMachineDescription)
1✔
391
                                        .collect(collectToArray(Machine[]::new)));
1✔
392
                }
393

394
                private static Machine buildMachineDescription(SpallocAPI.Machine m) {
395
                        return new Machine(m.getName(), List.copyOf(m.getTags()),
1✔
396
                                        m.getWidth(), m.getHeight(),
1✔
397
                                        m.getDeadBoards().stream().map(Utils::board)
1✔
398
                                                        .collect(toList()),
1✔
399
                                        m.getDownLinks().stream().flatMap(Utils::boardLinks)
1✔
400
                                                        .collect(toList()));
1✔
401
                }
402
        }
403

404
        @Override
405
        protected final JobDescription[] listJobs() {
406
                return permit.authorize(() -> helper.listJobs(this));
1✔
407
        }
408

409
        @Override
410
        protected final Machine[] listMachines() {
411
                return permit.authorize(helper::listMachines);
1✔
412
        }
413

414
        @Override
415
        protected final void notifyJob(Integer jobId, boolean wantNotify)
416
                        throws TaskException {
417
                if (nonNull(jobId)) {
1✔
418
                        var job = getJob(jobId);
1✔
419
                        job.access(host());
1✔
420
                        manageNotifier(jobNotifiers, jobId, wantNotify, () -> {
1✔
421
                                log.debug("Looking for changes to {}", job.getId());
1✔
422
                                if (job.waitForChange(
1✔
423
                                                mainProps.getCompat().getNotifyWaitTime())) {
1✔
424
                                        log.debug("Job {} changed!", jobId);
×
425
                                        writeJobNotification(List.of(jobId));
×
426
                                }
427
                                log.debug("Stopped looking for changes to {}", jobId);
1✔
428
                        });
1✔
429
                } else {
1✔
430
                        manageNotifier(jobNotifiers, jobId, wantNotify, () -> {
1✔
431
                                var actual = permit.authorize(() -> {
1✔
432
                                        log.debug("Looking for changes to all jobs");
1✔
433
                                        return spalloc.getJobs(false, LOTS, 0).getChanged(
1✔
434
                                                        mainProps.getCompat().getNotifyWaitTime());
1✔
435
                                });
436
                                if (actual.size() > 0) {
1✔
437
                                        log.debug("Jobs {} changed!", actual);
×
438
                                        writeJobNotification(
×
439
                                                        actual.stream().collect(toList()));
×
440
                                }
441
                                log.debug("Stopped looking for changes to all jobs");
1✔
442
                        });
1✔
443
                }
444
        }
1✔
445

446
        @Override
447
        protected final void notifyMachine(String machineName, boolean wantNotify)
448
                        throws TaskException {
449
                if (nonNull(machineName)) {
1✔
450
                        var machine = getMachine(machineName);
1✔
451
                        manageNotifier(machNotifiers, machineName, wantNotify, () -> {
1✔
452
                                if (machine.waitForChange(
1✔
453
                                                mainProps.getCompat().getNotifyWaitTime())) {
1✔
454
                                        writeMachineNotification(List.of(machineName));
×
455
                                }
456
                        });
1✔
457
                } else {
1✔
458
                        manageNotifier(machNotifiers, machineName, wantNotify, () -> {
1✔
459
                                List<String> actual = permit.authorize(() -> {
1✔
460
                                        var machines = spalloc.getMachines(false);
1✔
461
                                        if (machines.isEmpty()) {
1✔
462
                                                // No machines, so don't wait
463
                                                return List.of();
×
464
                                        }
465
                                        var invMap = machines.values().stream()
1✔
466
                                                        .collect(toMap(SpallocAPI.Machine::getId,
1✔
467
                                                                        SpallocAPI.Machine::getName));
468
                                        var mIds = machines.values().stream().map(m -> m.getId())
1✔
469
                                                        .collect(toList());
1✔
470
                                        var epoch = epochs.getMachinesEpoch(mIds);
1✔
471
                                        try {
472
                                                var changed = epoch.getChanged(
1✔
473
                                                                mainProps.getCompat().getNotifyWaitTime());
1✔
474
                                                return changed.stream().map(invMap::get)
1✔
475
                                                                .collect(toList());
1✔
476
                                        } catch (InterruptedException e) {
1✔
477
                                                return List.of();
1✔
478
                                        }
479
                                });
480
                                if (actual.size() > 0) {
1✔
481
                                        writeMachineNotification(actual.stream().collect(toList()));
×
482
                                }
483
                        });
1✔
484
                }
485
        }
1✔
486

487
        @Override
488
        protected final void powerJobBoards(int jobId, PowerState switchOn)
489
                        throws TaskException {
490
                var job = getJob(jobId);
1✔
491
                job.access(host());
1✔
492
                job.getMachine().orElseThrow(
1✔
493
                                () -> new TaskException("no boards currently allocated"))
×
494
                                .setPower(switchOn);
1✔
495
        }
1✔
496

497
        @Override
498
        protected void reportProblem(String address, Integer x, Integer y,
499
                        Integer p, String description) {
500
                HasChipLocation locus;
501
                if (nonNull(x) && nonNull(y)) {
×
502
                        if (nonNull(p)) {
×
503
                                locus = new CoreLocation(x, y, p);
×
504
                        } else {
505
                                locus = new ChipLocation(x, y);
×
506
                        }
507
                } else {
508
                        locus = null;
×
509
                }
510
                permit.authorize(() -> {
×
511
                        spalloc.reportProblem(address, locus, description, permit);
×
512
                        return this; // Ignored value
×
513
                });
514
        }
×
515

516
        private static WhereIs makeWhereIs(BoardLocation bl) {
517
                var wi = new WhereIs.Builder();
1✔
518
                wi.setMachine(bl.getMachine());
1✔
519
                wi.setLogical(bl.getLogical());
1✔
520
                wi.setPhysical(bl.getPhysical());
1✔
521
                wi.setChip(bl.getChip());
1✔
522
                wi.setBoardChip(bl.getBoardChip());
1✔
523
                var j = bl.getJob();
1✔
524
                if (nonNull(j)) {
1✔
525
                        wi.setJobId(j.getId());
1✔
526
                        wi.setJobChip(bl.getChipRelativeTo(j.getRootChip().orElseThrow()));
1✔
527
                }
528
                return wi.build();
1✔
529
        }
530

531
        @Override
532
        protected final WhereIs whereIsJobChip(int jobId, int x, int y)
533
                        throws TaskException {
534
                var job = getJob(jobId);
1✔
535
                job.access(host());
1✔
536
                return job.whereIs(x, y).map(V1TaskImpl::makeWhereIs).orElseThrow(
1✔
537
                                () -> new TaskException("no boards currently allocated"));
×
538
        }
539

540
        @Override
541
        protected final WhereIs whereIsMachineChip(String machineName, int x, int y)
542
                        throws TaskException {
543
                return getMachine(machineName).getBoardByChip(new ChipLocation(x, y))
1✔
544
                                .map(V1TaskImpl::makeWhereIs)
1✔
545
                                .orElseThrow(() -> new TaskException("no such board"));
1✔
546
        }
547

548
        @Override
549
        protected final WhereIs whereIsMachineLogicalBoard(String machineName,
550
                        int x, int y, int z) throws TaskException {
551
                return getMachine(machineName)
1✔
552
                                .getBoardByLogicalCoords(new TriadCoords(x, y, z))
1✔
553
                                .map(V1TaskImpl::makeWhereIs)
1✔
554
                                .orElseThrow(() -> new TaskException("no such board"));
1✔
555
        }
556

557
        @Override
558
        protected final WhereIs whereIsMachinePhysicalBoard(String machineName,
559
                        int c, int f, int b) throws TaskException {
560
                return getMachine(machineName)
1✔
561
                                .getBoardByPhysicalCoords(new PhysicalCoords(c, f, b))
1✔
562
                                .map(V1TaskImpl::makeWhereIs)
1✔
563
                                .orElseThrow(() -> new TaskException("no such board"));
1✔
564
        }
565

566
        // instance-aware utilities
567

568
        private Job getJob(int jobId) throws TaskException {
569
                return permit.authorize(() -> spalloc.getJob(permit, jobId))
1✔
570
                                .orElseThrow(() -> new TaskException("no such job"));
1✔
571
        }
572

573
        private SpallocAPI.Machine getMachine(String machineName)
574
                        throws TaskException {
575
                return permit.authorize(() -> spalloc.getMachine(machineName, false))
1✔
576
                                .orElseThrow(() -> new TaskException("no such machine"));
1✔
577
        }
578

579
        private <T> void manageNotifier(Map<T, Future<Void>> notifiers, T key,
580
                        boolean wantNotify, Notifier notifier) {
581
                if (wantNotify) {
1✔
582
                        if (!notifiers.containsKey(key)) {
1✔
583
                                notifiers.put(key,
1✔
584
                                                getExecutor().submit(Notifier.toCallable(notifier)));
1✔
585
                        }
586
                } else {
587
                        var n = notifiers.remove(key);
1✔
588
                        if (nonNull(n)) {
1✔
589
                                n.cancel(true);
1✔
590
                        }
591
                }
592
        }
1✔
593
}
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