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

SpiNNakerManchester / JavaSpiNNaker / 6194687960

15 Sep 2023 06:35AM UTC coverage: 36.946% (-0.06%) from 37.001%
6194687960

push

github

web-flow
Merge pull request #1059 from SpiNNakerManchester/make_full_proxy_jar

Make jar with dependencies

8675 of 23480 relevant lines covered (36.95%)

0.74 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);
3✔
104

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

107
        private final Map<String, Future<Void>> machNotifiers = new HashMap<>();
3✔
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);
3✔
135
        }
3✔
136

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

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

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

159
        @Override
160
        protected final JobMachineInfo getJobMachineInfo(int jobId)
161
                        throws TaskException {
162
                var job = getJob(jobId);
3✔
163
                job.access(host());
3✔
164
                var machine = job.getMachine()
3✔
165
                                .orElseThrow(() -> new TaskException("boards not allocated"));
3✔
166
                int w, h;
167
                if (machine.getDepth() == 1) {
3✔
168
                        // Special case: single board alloc
169
                        w = SIZE_X_OF_ONE_BOARD;
3✔
170
                        h = SIZE_Y_OF_ONE_BOARD;
3✔
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()
3✔
188
                                .map(ci -> new Connection(ci.getChip(), ci.getHostname()))
3✔
189
                                .collect(toList()), machine.getMachine().getName(),
3✔
190
                                machine.getBoards());
3✔
191
        }
192

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

214
        @Override
215
        protected final void jobKeepalive(int jobId) throws TaskException {
216
                getJob(jobId).access(host());
3✔
217
        }
3✔
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)) {
3✔
228
                        return mainProps.getCompat().getDefaultKeepalive();
3✔
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>();
3✔
249
                if (src instanceof List) {
3✔
250
                        for (var o : (List<?>) src) {
×
251
                                vals.add(Objects.toString(o));
×
252
                        }
×
253
                } else if (src instanceof String) {
3✔
254
                        vals.add((String) src);
×
255
                }
256
                if (vals.isEmpty() && mayForceDefault) {
3✔
257
                        return List.of("default");
×
258
                }
259
                return vals;
3✔
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"));
3✔
266
                return createJob(new CreateNumBoards(numBoards, maxDead), kwargs, cmd);
3✔
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"));
3✔
273
                return createJob(new CreateDimensions(width, height, maxDead), kwargs,
3✔
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, cmd);
3✔
281
        }
282

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

565
        // instance-aware utilities
566

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

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

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