From f086d8fc314478bb05e0787502e9043f18d9ef15 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Wed, 3 Jun 2026 12:54:16 +0200 Subject: [PATCH] Save&restore endpoint for restore to also support composite snapshots --- services/save-and-restore/doc/index.rst | 8 ++-- .../SnapshotRestoreController.java | 46 +++++++++++-------- .../SnapshotRestorerControllerTest.java | 36 ++++++++++++++- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst index dab386c7d9..12aa662614 100644 --- a/services/save-and-restore/doc/index.rst +++ b/services/save-and-restore/doc/index.rst @@ -910,15 +910,15 @@ the list is empty, all PVs in the snapshot were restored correctly. } ] -Restore from snapshot node -"""""""""""""""""""""""""" +Restore from snapshot or composite snapshot node +"""""""""""""""""""""""""""""""""""""""""""""""" -**.../restore/node?nodeId=** +**.../restore/node?nodeId=** Method: POST This is the same as the endpoint to restore from snapshot items, however it uses snapshot items -from an existing node rather than providing them explicitly. It returns the same result. +from an existing snapshot node or composite snapshot rather than providing them explicitly. It returns the same result. Compare Endpoint ---------------- diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java index 099e5af91c..866b9b481a 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestoreController.java @@ -1,30 +1,15 @@ /** - * Copyright (C) 2018 European Spallation Source ERIC. - *

- * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - *

- * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - *

- * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Copyright (C) 2026 European Spallation Source ERIC. */ package org.phoebus.service.saveandrestore.web.controllers; import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.NodeType; import org.phoebus.applications.saveandrestore.model.RestoreResult; import org.phoebus.applications.saveandrestore.model.SnapshotItem; import org.phoebus.saveandrestore.util.SnapshotUtil; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.PropertySource; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -49,19 +34,40 @@ public class SnapshotRestoreController extends BaseController { private static final Logger LOG = Logger.getLogger(SnapshotRestoreController.class.getName()); + /** + * Restores a snapshot or composite snapshot + * + * @param snapshotItems List of {@link SnapshotItem}s subject to a restore operation. Callee will need to + * retrieve this list from a snapshot or composite snapshot. + * @return The result of the operation + */ @PostMapping(value = "/restore/items", produces = JSON) public List restoreFromSnapshotItems( @RequestBody List snapshotItems) { return snapshotUtil.restore(snapshotItems, connectionTimeout); } + /** + * Restores a snapshot or composite snapshot + * + * @param nodeId Unique id of a snapshot or composite snapshot + * @return The result of the operation + */ @PostMapping(value = "/restore/node", produces = JSON) public List restoreFromSnapshotNode( - @RequestParam(value = "nodeId") String nodeId){ + @RequestParam(value = "nodeId") String nodeId) { Node snapshotNode = nodeDAO.getNode(nodeId); LOG.log(Level.INFO, "Restore requested for snapshot '" + snapshotNode.getName() + "'"); - var snapshot = nodeDAO.getSnapshotData(nodeId); - return snapshotUtil.restore(snapshot.getSnapshotItems(), connectionTimeout); + List snapshotItems; + if (snapshotNode.getNodeType().equals(NodeType.SNAPSHOT)) { + snapshotItems = nodeDAO.getSnapshotData(nodeId).getSnapshotItems(); + } else if(snapshotNode.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)){ + snapshotItems = nodeDAO.getSnapshotItemsFromCompositeSnapshot(nodeId); + } + else{ + throw new IllegalArgumentException("Node " + snapshotNode + " is not a snapshot or composite snapshot"); + } + return snapshotUtil.restore(snapshotItems, connectionTimeout); } } diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java index 1d7485b9da..e43d0c9eaa 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerControllerTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.applications.saveandrestore.model.ConfigPv; import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.NodeType; import org.phoebus.applications.saveandrestore.model.RestoreResult; import org.phoebus.applications.saveandrestore.model.SnapshotData; import org.phoebus.applications.saveandrestore.model.SnapshotItem; @@ -64,7 +65,7 @@ public void testRestoreFromSnapshotNode() throws Exception { item.setConfigPv(configPv); snapshotData.setSnapshotItems(List.of(item)); - when(nodeDAO.getNode("uniqueId")).thenReturn(Node.builder().name("name").uniqueId("uniqueId").build()); + when(nodeDAO.getNode("uniqueId")).thenReturn(Node.builder().name("name").nodeType(NodeType.SNAPSHOT).uniqueId("uniqueId").build()); when(nodeDAO.getSnapshotData("uniqueId")).thenReturn(snapshotData); MockHttpServletRequestBuilder request = post("/restore/node?nodeId=uniqueId") @@ -80,6 +81,39 @@ public void testRestoreFromSnapshotNode() throws Exception { }); } + @Test + public void testRestoreFromCompositeSnapshotNode() throws Exception { + + SnapshotItem item = new SnapshotItem(); + ConfigPv configPv = new ConfigPv(); + configPv.setPvName("loc://x"); + item.setValue(VFloat.of(1.0, Alarm.none(), Time.now(), Display.none())); + item.setConfigPv(configPv); + + when(nodeDAO.getNode("uniqueId")).thenReturn(Node.builder().name("name").nodeType(NodeType.COMPOSITE_SNAPSHOT).uniqueId("uniqueId").build()); + when(nodeDAO.getSnapshotItemsFromCompositeSnapshot("uniqueId")).thenReturn(List.of(item)); + + MockHttpServletRequestBuilder request = post("/restore/node?nodeId=uniqueId") + .header(HttpHeaders.AUTHORIZATION, userAuthorization); + + MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON)) + .andReturn(); + + // Make sure response is in the Restore Result json format + objectMapper.readValue( + result.getResponse().getContentAsString(), + new TypeReference>() { + }); + } + + @Test + public void testRestoreFromInvalidNodeType() throws Exception { + when(nodeDAO.getNode("uniqueId")).thenReturn(Node.builder().name("name").uniqueId("uniqueId").build()); + MockHttpServletRequestBuilder request = post("/restore/node?nodeId=uniqueId") + .header(HttpHeaders.AUTHORIZATION, userAuthorization); + mockMvc.perform(request).andExpect(status().isBadRequest()); + } + @Test public void testRestoreFromSnapshotItems() throws Exception {