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

SpiNNakerManchester / JavaSpiNNaker / 6187366298

14 Sep 2023 03:05PM UTC coverage: 36.916% (+0.04%) from 36.88%
6187366298

push

github

web-flow
Merge pull request #1054 from SpiNNakerManchester/blacklist_ui

Blacklist UI

61 of 61 new or added lines in 8 files covered. (100.0%)

8669 of 23483 relevant lines covered (36.92%)

1.11 hits per line

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

0.0
/SpiNNaker-allocserv/src/main/java/uk/ac/manchester/spinnaker/alloc/admin/AdminController.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.admin;
17

18
import static uk.ac.manchester.spinnaker.alloc.security.SecurityConfig.IS_ADMIN;
19

20
import java.security.Principal;
21
import java.util.Objects;
22

23
import javax.validation.Valid;
24
import javax.validation.constraints.AssertTrue;
25
import javax.validation.constraints.NotBlank;
26
import javax.validation.constraints.NotEmpty;
27
import javax.validation.constraints.NotNull;
28
import javax.validation.constraints.Positive;
29
import javax.validation.constraints.PositiveOrZero;
30

31
import org.springframework.http.ResponseEntity;
32
import org.springframework.security.access.prepost.PreAuthorize;
33
import org.springframework.ui.ModelMap;
34
import org.springframework.web.bind.annotation.GetMapping;
35
import org.springframework.web.bind.annotation.ModelAttribute;
36
import org.springframework.web.bind.annotation.PathVariable;
37
import org.springframework.web.bind.annotation.PostMapping;
38
import org.springframework.web.bind.annotation.RequestBody;
39
import org.springframework.web.bind.annotation.RequestMapping;
40
import org.springframework.web.bind.annotation.RequestParam;
41
import org.springframework.web.bind.annotation.ResponseBody;
42
import org.springframework.web.multipart.MultipartFile;
43
import org.springframework.web.servlet.ModelAndView;
44
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
45

46
import com.google.errorprone.annotations.Keep;
47

48
import uk.ac.manchester.spinnaker.alloc.model.BoardRecord;
49
import uk.ac.manchester.spinnaker.alloc.model.BoardTemperatures;
50
import uk.ac.manchester.spinnaker.alloc.model.GroupRecord;
51
import uk.ac.manchester.spinnaker.alloc.model.TagList;
52
import uk.ac.manchester.spinnaker.alloc.model.UserRecord;
53
import uk.ac.manchester.spinnaker.alloc.web.SystemController;
54
import uk.ac.manchester.spinnaker.messages.model.Blacklist;
55

56
/**
57
 * The API for the controller for the admin user interface.
58
 *
59
 * @author Donal Fellows
60
 */
61
@RequestMapping(AdminController.BASE_PATH)
62
@PreAuthorize(IS_ADMIN)
63
public interface AdminController {
64
        /** The base path of this interface within the MVC domain. */
65
        String BASE_PATH = SystemController.ROOT_PATH + "/admin";
66

67
        /** Path to all-users operations. */
68
        String USERS_PATH = "/users";
69

70
        /** Path to user creation operations. */
71
        String CREATE_USER_PATH = "/create-user";
72

73
        /** Path to single-user operations. */
74
        String USER_PATH = USERS_PATH + "/{id}";
75

76
        /** Path to single-user-deletion operation. */
77
        String USER_DELETE_PATH = USER_PATH + "/delete";
78

79
        /** Path to all-groups operations. */
80
        String GROUPS_PATH = "/groups";
81

82
        /** Path to group creation operations. */
83
        String CREATE_GROUP_PATH = "/create-group";
84

85
        /** Path to single-group operations. */
86
        String GROUP_PATH = GROUPS_PATH + "/{id}";
87

88
        /** Path to user-add-to-group operation. */
89
        String GROUP_ADD_USER_PATH = GROUP_PATH + "/add-user";
90

91
        /** Path to user-remove-from-group operation. */
92
        String GROUP_REMOVE_USER_PATH = GROUP_PATH + "/remove-user/{userid}";
93

94
        /** Path to quota-adjustment operation. */
95
        String GROUP_QUOTA_PATH = GROUP_PATH + "/adjust-quota";
96

97
        /** Path to single-user-deletion operation. */
98
        String GROUP_DELETE_PATH = GROUP_PATH + "/delete";
99

100
        /** Path to boards operations. */
101
        String BOARDS_PATH = "/boards";
102

103
        /** Path to blacklist operations. */
104
        String BLACKLIST_PATH = "/boards/blacklist";
105

106
        /** Path to temperature operations. */
107
        String TEMPERATURE_PATH = "/boards/temperature";
108

109
        /** Path to machine-instantiation operations. */
110
        String MACHINE_PATH = "/machine";
111

112
        /** Name of parameter used when submitting a new machine definition. */
113
        String MACHINE_FILE_PARAM = "file";
114

115
        /** Name of parameter used to mark a retagging. */
116
        String MACHINE_RETAG_PARAM = "retag";
117

118
        /**
119
         * Get supported ops.
120
         *
121
         * @param model
122
         *            Overall model
123
         * @return the view
124
         */
125
        @GetMapping("/")
126
        ModelAndView mainUI(ModelMap model);
127

128
        /**
129
         * List all users.
130
         *
131
         * @param model
132
         *            Overall model
133
         * @return the model and view
134
         */
135
        @GetMapping(USERS_PATH)
136
        ModelAndView listUsers(ModelMap model);
137

138
        /**
139
         * Get the form for creating a user.
140
         *
141
         * @param model
142
         *            Overall model
143
         * @return the model and view
144
         */
145
        @GetMapping(CREATE_USER_PATH)
146
        ModelAndView getUserCreationForm(ModelMap model);
147

148
        /**
149
         * Create a user.
150
         *
151
         * @param user
152
         *            The description of the user to create
153
         * @param model
154
         *            Overall model
155
         * @param attrs
156
         *            Where to put attributes of the model so that they are
157
         *            respected after the redirect without being present in the URL.
158
         * @return the model and view
159
         */
160
        @PostMapping(CREATE_USER_PATH)
161
        ModelAndView createUser(@Valid @ModelAttribute("user") UserRecord user,
162
                        ModelMap model, RedirectAttributes attrs);
163

164
        /**
165
         * Show user details.
166
         *
167
         * @param id
168
         *            The user ID
169
         * @return the model and view
170
         */
171
        @GetMapping(USER_PATH)
172
        ModelAndView showUserForm(@PathVariable("id") int id);
173

174
        /**
175
         * Modify user details.
176
         *
177
         * @param id
178
         *            The user ID
179
         * @param user
180
         *            The description of the user to update
181
         * @param model
182
         *            Overall model
183
         * @param principal
184
         *            Who is the admin? Used for safety checks.
185
         * @return the model and view
186
         */
187
        @PostMapping(USER_PATH)
188
        ModelAndView submitUserForm(@PathVariable("id") int id,
189
                        @Valid @ModelAttribute("user") UserRecord user, ModelMap model,
190
                        Principal principal);
191

192
        /**
193
         * Delete a user.
194
         *
195
         * @param id
196
         *            The user ID to delete
197
         * @param principal
198
         *            Who is the admin? Used for safety checks.
199
         * @param attrs
200
         *            Where to put attributes of the model so that they are
201
         *            respected after the redirect without being present in the URL.
202
         * @return the model and view
203
         */
204
        @PostMapping(USER_DELETE_PATH)
205
        ModelAndView deleteUser(@PathVariable("id") int id, Principal principal,
206
                        RedirectAttributes attrs);
207

208
        /**
209
         * List all groups.
210
         *
211
         * @param model
212
         *            Overall model
213
         * @return the model and view
214
         */
215
        @GetMapping(GROUPS_PATH)
216
        ModelAndView listGroups(ModelMap model);
217

218
        /**
219
         * Get info about a particular group.
220
         *
221
         * @param id
222
         *            The ID of the group to get info about.
223
         * @return the model and view
224
         */
225
        @GetMapping(GROUP_PATH)
226
        ModelAndView showGroupInfo(@PathVariable("id") int id);
227

228
        /**
229
         * Get the form for creating a group.
230
         *
231
         * @param model
232
         *            Overall model
233
         * @return the model (a {@link CreateGroupModel}) and view
234
         */
235
        @GetMapping(CREATE_GROUP_PATH)
236
        ModelAndView getGroupCreationForm(ModelMap model);
237

238
        /**
239
         * Create a group.
240
         *
241
         * @param group
242
         *            The description of the group to create
243
         * @param attrs
244
         *            Where to put attributes of the model so that they are
245
         *            respected after the redirect without being present in the URL.
246
         * @return the model and view
247
         */
248
        @PostMapping(CREATE_GROUP_PATH)
249
        ModelAndView createGroup(
250
                        @Valid @ModelAttribute("group") CreateGroupModel group,
251
                        RedirectAttributes attrs);
252

253
        /**
254
         * Model used when creating a group. No other purpose.
255
         *
256
         * @author Donal Fellows
257
         */
258
        class CreateGroupModel {
×
259
                private String name = "";
×
260

261
                @PositiveOrZero
×
262
                private Long quota = null;
263

264
                private boolean quotaDefined = false;
×
265

266
                private static final int DECIMAL = 10;
267

268
                /** @return The name of the group to create. */
269
                @NotBlank
270
                public String getName() {
271
                        return name;
×
272
                }
273

274
                /**
275
                 * @param name
276
                 *            The name of the group to create.
277
                 */
278
                public void setName(String name) {
279
                        this.name = name.strip();
×
280
                }
×
281

282
                /**
283
                 * @return The quota of the group to create, as a {@link String}. Empty
284
                 *         if the group is quota-less.
285
                 */
286
                public String getQuota() {
287
                        return Objects.toString(quota, "");
×
288
                }
289

290
                /**
291
                 * @param quota
292
                 *            The quota of the group to create, as a {@link String}.
293
                 */
294
                public void setQuota(String quota) {
295
                        try {
296
                                this.quota = Long.parseLong(quota, DECIMAL);
×
297
                        } catch (NumberFormatException e) {
×
298
                                this.quota = null;
×
299
                        }
×
300
                }
×
301

302
                /**
303
                 * @return This request, as a partial group record.
304
                 */
305
                public GroupRecord toGroupRecord() {
306
                        var gr = new GroupRecord();
×
307
                        gr.setGroupName(name);
×
308
                        if (quotaDefined) {
×
309
                                gr.setQuota(quota);
×
310
                        }
311
                        return gr;
×
312
                }
313

314
                /** @return Whether the group has a quota. */
315
                public boolean isQuotad() {
316
                        return quotaDefined;
×
317
                }
318

319
                /**
320
                 * @param value
321
                 *            Whether the group has a quota.
322
                 */
323
                public void setQuotad(boolean value) {
324
                        quotaDefined = value;
×
325
                }
×
326
        }
327

328
        /**
329
         * Add a user to a group.
330
         *
331
         * @param id
332
         *            The group ID to adjust
333
         * @param user
334
         *            The name of the user to add
335
         * @param attrs
336
         *            Where to put attributes of the model so that they are
337
         *            respected after the redirect without being present in the URL.
338
         * @return the model and view
339
         */
340
        @PostMapping(GROUP_ADD_USER_PATH)
341
        ModelAndView addUserToGroup(@PathVariable("id") int id,
342
                        @NotBlank @RequestParam("user") String user,
343
                        RedirectAttributes attrs);
344

345
        /**
346
         * Remove a user from a group.
347
         *
348
         * @param id
349
         *            The group ID to adjust
350
         * @param userid
351
         *            The ID of the user to remove
352
         * @param attrs
353
         *            Where to put attributes of the model so that they are
354
         *            respected after the redirect without being present in the URL.
355
         * @return the model and view
356
         */
357
        @PostMapping(GROUP_REMOVE_USER_PATH)
358
        ModelAndView removeUserFromGroup(@PathVariable("id") int id,
359
                        @PathVariable("userid") int userid,
360
                        RedirectAttributes attrs);
361

362
        /**
363
         * Adjust the quota of a group.
364
         *
365
         * @param id
366
         *            The group ID to adjust
367
         * @param delta
368
         *            By how much are we to adjust the quota. In
369
         *            <em>board-hours</em>.
370
         * @param attrs
371
         *            Where to put attributes of the model so that they are
372
         *            respected after the redirect without being present in the URL.
373
         * @return the model and view
374
         */
375
        @PostMapping(GROUP_QUOTA_PATH)
376
        ModelAndView adjustGroupQuota(@PathVariable("id") int id,
377
                        @RequestParam("delta") int delta, RedirectAttributes attrs);
378

379
        /**
380
         * Delete a group. It is legal for a user to not be a member of any group;
381
         * they just won't be able to submit jobs if that's the case.
382
         *
383
         * @param id
384
         *            The group ID to delete
385
         * @param attrs
386
         *            Where to put attributes of the model so that they are
387
         *            respected after the redirect without being present in the URL.
388
         * @return the model and view
389
         */
390
        @PostMapping(GROUP_DELETE_PATH)
391
        ModelAndView deleteGroup(@PathVariable("id") int id,
392
                        RedirectAttributes attrs);
393

394
        /**
395
         * UI for boards.
396
         *
397
         * @param model
398
         *            Overall model
399
         * @return the model and view
400
         */
401
        @GetMapping(BOARDS_PATH)
402
        ModelAndView boards(ModelMap model);
403

404
        /**
405
         * Manipulate a board.
406
         *
407
         * @param board
408
         *            The board coordinates, and possibly the state change
409
         * @param model
410
         *            Overall model
411
         * @return the model and view
412
         */
413
        @PostMapping(BOARDS_PATH)
414
        ModelAndView board(@Valid @ModelAttribute("board") BoardRecord board,
415
                        ModelMap model);
416

417
        /**
418
         * Save a new blacklist.
419
         *
420
         * @param bldata The blacklist data.
421
         * @return The empty response with the appropriate code.
422
         */
423
        @PostMapping(BLACKLIST_PATH)
424
        ResponseEntity<Void> blacklistSave(
425
                        @Valid @RequestBody BlacklistData bldata);
426

427
        /**
428
         * Fetch the blacklist for a board from the machine.
429
         *
430
         * @param boardId The board to get the data for.
431
         * @param bmpId The BMP of the board.
432
         * @return the blacklist data.
433
         */
434
        @GetMapping(value = BLACKLIST_PATH)
435
        @ResponseBody
436
        BlacklistData blacklistFetch(
437
                        @Valid @RequestParam("board_id") int boardId,
438
                        @Valid @RequestParam("bmp_id") int bmpId);
439

440
        /**
441
         * Get the temperature data for a board.
442
         *
443
         * @param boardId
444
         *            Which board to get the data for.
445
         * @return the model and view in a future
446
         */
447
        @GetMapping(value = TEMPERATURE_PATH)
448
        @ResponseBody
449
        BoardTemperatures getTemperatures(
450
                        @Valid @RequestParam("board_id") int boardId);
451

452
        /**
453
         * Provide the form for uploading a machine definition.
454
         *
455
         * @param model
456
         *            Overall model
457
         * @return the model and view
458
         */
459
        @GetMapping(MACHINE_PATH)
460
        ModelAndView machineManagement(ModelMap model);
461

462
        /**
463
         * Handle the change of the tags of a machine.
464
         *
465
         * @param machineName
466
         *            The name of the machine being retagged
467
         * @param newTags
468
         *            The tags of the machine; comma-separated list
469
         * @return the model and view
470
         */
471
        @PostMapping(path = MACHINE_PATH, params = MACHINE_RETAG_PARAM)
472
        ModelAndView retagMachine(
473
                        @NotEmpty @ModelAttribute("machine") String machineName, @TagList
474
                        @NotNull @ModelAttribute(MACHINE_RETAG_PARAM) String newTags);
475

476
        /**
477
         * Mark a machine as out of service.
478
         *
479
         * @param machineName
480
         *            The name of the machine being disabled
481
         * @return the model and view
482
         */
483
        @PostMapping(value = MACHINE_PATH, params = "outOfService")
484
        ModelAndView disableMachine(
485
                        @NotEmpty @ModelAttribute("machine") String machineName);
486

487
        /**
488
         * Mark a machine as in service.
489
         *
490
         * @param machineName
491
         *            The name of the machine being enabled
492
         * @return the model and view
493
         */
494
        @PostMapping(value = MACHINE_PATH, params = "intoService")
495
        ModelAndView enableMachine(
496
                        @NotEmpty @ModelAttribute("machine") String machineName);
497

498
        /**
499
         * Handle the upload of a machine definition. Note that no user has any
500
         * quota set on a newly defined machine; it's totally free to use by
501
         * default.
502
         *
503
         * @param file
504
         *            The file being uploaded
505
         * @return the model and view
506
         */
507
        @PostMapping(path = MACHINE_PATH)
508
        ModelAndView defineMachine(
509
                        @NotNull @RequestParam(MACHINE_FILE_PARAM) MultipartFile file);
510

511
        /**
512
         * The model of a blacklist used by the administration web interface.
513
         */
514
        class BlacklistData {
×
515
                private int boardId;
516

517
                private int bmpId;
518

519
                private String blacklist;
520

521
                private boolean present;
522

523
                private boolean synched;
524

525
                /** @return The board ID. */
526
                @Positive
527
                public int getBoardId() {
528
                        return boardId;
×
529
                }
530

531
                /**
532
                 * @param boardId
533
                 *            The board ID.
534
                 */
535
                public void setBoardId(int boardId) {
536
                        this.boardId = boardId;
×
537
                }
×
538

539
                /** @return The BMP ID. */
540
                @Positive
541
                public int getBmpId() {
542
                        return bmpId;
×
543
                }
544

545
                /**
546
                 * @param bmpId The BMP ID.
547
                 */
548
                public void setBmpId(int bmpId) {
549
                        this.bmpId = bmpId;
×
550
                }
×
551

552
                /** @return The text of the blacklist, if present. */
553
                public String getBlacklist() {
554
                        return blacklist;
×
555
                }
556

557
                /**
558
                 * @param blacklist
559
                 *            The text of the blacklist.
560
                 */
561
                public void setBlacklist(String blacklist) {
562
                        this.blacklist = blacklist;
×
563
                }
×
564

565
                /**
566
                 * @return The parsed blacklist.
567
                 * @throws NullPointerException
568
                 *             If no blacklist data is present.
569
                 * @throws IllegalArgumentException
570
                 *             If the string is invalid.
571
                 */
572
                public Blacklist getParsedBlacklist() {
573
                        return new Blacklist(blacklist);
×
574
                }
575

576
                /** @return Whether there is blacklist data present. */
577
                public boolean isPresent() {
578
                        return present;
×
579
                }
580

581
                /**
582
                 * @param present
583
                 *            Whether there is blacklist data present.
584
                 */
585
                public void setPresent(boolean present) {
586
                        this.present = present;
×
587
                }
×
588

589
                /**
590
                 * @return Whether the blacklist data is believed to be the same as the
591
                 *         data on the board.
592
                 */
593
                public boolean isSynched() {
594
                        return synched;
×
595
                }
596

597
                /**
598
                 * @param synched
599
                 *            Whether the blacklist data is believed to be the same as
600
                 *            the data on the board.
601
                 */
602
                public void setSynched(boolean synched) {
603
                        this.synched = synched;
×
604
                }
×
605

606
                @Keep
607
                @AssertTrue(message = "blacklist must be validly formatted")
608
                private boolean isValidBlacklist() {
609
                        if (present) {
×
610
                                try {
611
                                        new Blacklist(blacklist);
×
612
                                } catch (Exception e) {
×
613
                                        return false;
×
614
                                }
×
615
                        }
616
                        return true;
×
617
                }
618
        }
619
}
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