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

SpiNNakerManchester / JavaSpiNNaker / 6310285782

26 Sep 2023 08:47AM UTC coverage: 36.367% (-0.5%) from 36.866%
6310285782

Pull #658

github

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

1675 of 1675 new or added lines in 266 files covered. (100.0%)

8368 of 23010 relevant lines covered (36.37%)

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 org.slf4j.Logger;
51
import org.springframework.beans.factory.annotation.Autowired;
52
import org.springframework.dao.DataAccessException;
53
import org.springframework.stereotype.Component;
54

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

83
/**
84
 * Concrete implementation of a task handling a single client connection. This
85
 * is a <em>prototype bean</em>; Spring will instantiate it whenever needed.
86
 * It's consumed by {@link V1CompatService}, which uses it to implement the
87
 * handler for a particular connection.
88
 *
89
 * @author Donal Fellows
90
 */
91
@Component
92
@Prototype
93
class V1TaskImpl extends V1CompatTask {
94
        /**
95
         * We are compatible with spalloc-server release version 5.0.0.
96
         */
97
        public static final String VERSION = "5.0.0";
98

99
        private static final int LOTS = 10000;
100

101
        private static final Logger log = getLogger(V1CompatService.class);
1✔
102

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

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

107
        /** The overall service properties. */
108
        @Autowired
109
        private SpallocProperties mainProps;
110

111
        /** The core spalloc service. */
112
        @Autowired
113
        private SpallocAPI spalloc;
114

115
        @Autowired
116
        private CompatHelper helper;
117

118
        @Autowired
119
        private Epochs epochs;
120

121
        /** Encoded form of our special permissions token. */
122
        private Permit permit;
123

124
        /** Name of the group for accounting purposes. */
125
        private String groupName;
126

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

564
        // instance-aware utilities
565

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

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

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