diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java index b48e686d3b..9688a35962 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 European Spallation Source ERIC. + * Copyright (C) 2024 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 @@ -118,6 +118,8 @@ public class Messages { public static String nodeSelectionForConfiguration; public static String noValueAvailable; public static String readbackPVName; + public static String restoreFailed; + public static String restoreFailedPVs; public static String saveFilter; public static String saveFilterConfirmOverwrite; diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java index a95994721d..394e514bf5 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java @@ -231,4 +231,18 @@ public interface SaveAndRestoreClient { * an exception. */ UserData authenticate(String userName, String password); + + /** + * Requests service to restore the specified {@link SnapshotItem}s + * @param snapshotItems A {@link List} of {@link SnapshotItem}s + * @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s. + */ + List restore(List snapshotItems); + + /** + * Requests service to restore the specified snapshot. + * @param snapshotNodeId Unique id of a snapshot + * @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s. + */ + List restore(String snapshotNodeId); } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java index 53287edd87..7fd7d1ff8e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java @@ -638,4 +638,55 @@ public UserData authenticate(String userName, String password) { return response.getEntity(new GenericType<>() { }); } + + @Override + public List restore(List snapshotItems){ + WebResource webResource = + getClient().resource(Preferences.jmasarServiceUrl + "/restore/items"); + ClientResponse response; + try { + response = webResource.accept(CONTENT_TYPE_JSON) + .entity(snapshotItems, CONTENT_TYPE_JSON) + .post(ClientResponse.class); + } catch (UniformInterfaceException e) { + throw new RuntimeException(e); + } + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { + String message = "Restore failed"; + try { + message = new String(response.getEntityInputStream().readAllBytes()); + } catch (IOException e) { + logger.log(Level.WARNING, "Unable to parse response", e); + } + throw new SaveAndRestoreClientException(message); + } + return response.getEntity(new GenericType<>() { + }); + } + + + public List restore(String snapshotNodeId){ + WebResource webResource = + getClient() + .resource(Preferences.jmasarServiceUrl + "/restore/node") + .queryParam("codeId", snapshotNodeId); + ClientResponse response; + try { + response = webResource.accept(CONTENT_TYPE_JSON) + .post(ClientResponse.class); + } catch (UniformInterfaceException e) { + throw new RuntimeException(e); + } + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { + String message = "Restore failed"; + try { + message = new String(response.getEntityInputStream().readAllBytes()); + } catch (IOException e) { + logger.log(Level.WARNING, "Unable to parse response", e); + } + throw new SaveAndRestoreClientException(message); + } + return response.getEntity(new GenericType<>() { + }); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java index fee17559ba..bafc1dd3b1 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java @@ -394,4 +394,26 @@ public UserData authenticate(String userName, String password) throws Exception{ executor.submit(() -> saveAndRestoreClient.authenticate(userName, password)); return future.get(); } + + /** + * Requests service to restore the specified {@link SnapshotItem}s + * @param snapshotItems A {@link List} of {@link SnapshotItem}s + * @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s. + */ + public List restore(List snapshotItems) throws Exception{ + Future> future = + executor.submit(() -> saveAndRestoreClient.restore(snapshotItems)); + return future.get(); + } + + /** + * Requests service to restore the specified snapshot. + * @param snapshotNodeId Unique id of a snapshot + * @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s. + */ + public List restore(String snapshotNodeId) throws Exception{ + Future> future = + executor.submit(() -> saveAndRestoreClient.restore(snapshotNodeId)); + return future.get(); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java index 035c832d79..a6d33c89f4 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java @@ -23,14 +23,21 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; +import javafx.scene.control.TableView; import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.scene.text.Font; import javafx.scene.text.Text; - import org.epics.vtype.VType; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication; @@ -42,18 +49,16 @@ import org.phoebus.ui.javafx.FocusUtil; import org.phoebus.util.time.TimestampFormats; -import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import static org.phoebus.ui.application.PhoebusApplication.logger; - /** * Base controller class for the snapshot table view. It handles common items (UI components, methods) needed * by all subclasses. @@ -93,8 +98,6 @@ public abstract class BaseSnapshotTableViewController { protected SnapshotController snapshotController; - protected static boolean resizePolicyNotInitialized = true; - protected static final Logger LOGGER = Logger.getLogger(BaseSnapshotTableViewController.class.getName()); @FXML @@ -127,27 +130,6 @@ public abstract class BaseSnapshotTableViewController { */ protected List snapshots = new ArrayList<>(); - public BaseSnapshotTableViewController() { - if (resizePolicyNotInitialized) { - AccessController.doPrivileged(resizePolicyAction); - } - } - - protected static PrivilegedAction resizePolicyAction = () -> { - try { - // Java FX bugfix: the table columns are not properly resized for the first table - Field f = TableView.CONSTRAINED_RESIZE_POLICY.getClass().getDeclaredField("isFirstRun"); - f.setAccessible(true); - f.set(TableView.CONSTRAINED_RESIZE_POLICY, Boolean.FALSE); - } catch (NoSuchFieldException | IllegalAccessException | RuntimeException e) { - // ignore - } - // Even if failed to set the policy, pretend that it was set. In such case the UI will be slightly dorked the - // first time, but will be OK in all other cases. - resizePolicyNotInitialized = false; - return null; - }; - public void initialize() { snapshotTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); snapshotTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); @@ -235,7 +217,7 @@ protected void updateItem(TableEntry item, boolean empty) { VType updatedValue = e.getRowValue().readOnlyProperty().get() ? e.getOldValue() : e.getNewValue(); ObjectProperty value = e.getRowValue().valueProperty(); value.setValue(new VTypePair(value.get().base, updatedValue, value.get().threshold)); - snapshotController.updateLoadedSnapshot( e.getRowValue(), updatedValue); + snapshotController.updateLoadedSnapshot(e.getRowValue(), updatedValue); }); liveValueColumn.setCellFactory(e -> new VTypeCellEditor<>()); @@ -278,11 +260,10 @@ protected String getPVKey(String pvName, boolean isReadonly) { return pvName + "_" + isReadonly; } - protected void showSnapshotInTable(Snapshot snapshot){ - if(snapshots.isEmpty()){ + protected void showSnapshotInTable(Snapshot snapshot) { + if (snapshots.isEmpty()) { snapshots.add(snapshot); - } - else{ + } else { snapshots.set(0, snapshot); } AtomicInteger counter = new AtomicInteger(0); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java index 5d32f295a3..29d287f03b 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2019 European Spallation Source ERIC. + * Copyright (C) 2024 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 @@ -42,6 +42,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * This controller is for the use case of loading a configuration {@link Node} to take a new snapshot. @@ -350,12 +351,28 @@ public void loadSnapshot(Node snapshotNode) { } public void restore(ActionEvent actionEvent) { - snapshotTableViewController.restore(snapshotProperty.get(), restoreFailedPVNames -> { + snapshotTableViewController.restore(snapshotProperty.get(), restoreResultList -> { javafx.scene.Node jfxNode = (javafx.scene.Node) actionEvent.getSource(); String userData = (String) jfxNode.getUserData(); if (userData.equalsIgnoreCase("true")) { - eventReceivers.forEach(r -> r.snapshotRestored(snapshotProperty.get().getSnapshotNode(), restoreFailedPVNames, this::showLoggingError)); + eventReceivers.forEach(r -> r.snapshotRestored(snapshotProperty.get().getSnapshotNode(), restoreResultList, this::showLoggingError)); } + if(restoreResultList != null && !restoreResultList.isEmpty()){ + showFailedRestoreResult(restoreResultList); + } + }); + } + + private void showFailedRestoreResult(List restoreResultList){ + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(Messages.restoreFailedPVs).append(System.lineSeparator()); + stringBuilder.append(restoreResultList.stream() + .map(r -> r.getSnapshotItem().getConfigPv().getPvName()).collect(Collectors.joining(System.lineSeparator()))); + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(Messages.restoreFailed); + alert.setContentText(stringBuilder.toString()); + alert.show(); }); } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java index 7557728093..5a47b08446 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 European Spallation Source ERIC. + * Copyright (C) 2024 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 @@ -23,7 +23,13 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.Alert; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; +import javafx.scene.control.Tooltip; import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.control.cell.PropertyValueFactory; import org.epics.vtype.VNumber; @@ -32,16 +38,31 @@ import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.Preferences; import org.phoebus.applications.saveandrestore.SafeMultiply; -import org.phoebus.applications.saveandrestore.model.*; -import org.phoebus.applications.saveandrestore.ui.*; -import org.phoebus.core.vtypes.VTypeHelper; +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.Snapshot; +import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.SnapshotItem; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; +import org.phoebus.applications.saveandrestore.ui.Threshold; +import org.phoebus.applications.saveandrestore.ui.Utilities; +import org.phoebus.applications.saveandrestore.ui.VNoData; +import org.phoebus.applications.saveandrestore.ui.VTypePair; import org.phoebus.core.vtypes.VDisconnectedData; +import org.phoebus.core.vtypes.VTypeHelper; import org.phoebus.framework.jobs.JobManager; +import org.phoebus.ui.dialog.DialogHelper; import org.phoebus.util.time.TimestampFormats; import java.text.SimpleDateFormat; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -343,7 +364,54 @@ public void hideEqualItems() { Platform.runLater(() -> updateTable(arrayList)); } - public void restore(Snapshot snapshot, Consumer> completion) { + /** + * Restores a snapshot through a call to the remote service. + * + * @param snapshot The {@link Snapshot} object subject to restore + * @param completion A handler for the outcome of the restore operation + */ + public void restore(Snapshot snapshot, Consumer> completion) { + JobManager.schedule("Restore snapshot " + snapshot.getSnapshotNode().getName(), monitor -> { + List itemsToRestore = new ArrayList<>(); + List restoreResultList = null; + // Before invoking restore, compile a list of items where non-restorable PVs are excluded. + for (SnapshotItem entry : snapshot.getSnapshotData().getSnapshotItems()) { + TableEntry e = tableEntryItems.get(getPVKey(entry.getConfigPv().getPvName(), entry.getConfigPv().isReadOnly())); + + boolean restorable = e.selectedProperty().get() && + !e.readOnlyProperty().get() && + entry.getValue() != null && + !entry.getValue().equals(VNoData.INSTANCE); + + if (restorable) { + itemsToRestore.add(entry); + } + } + + try { + restoreResultList = SaveAndRestoreService.getInstance().restore(itemsToRestore); + } catch (Exception e) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(Messages.errorActionFailed); + alert.setContentText(e.getMessage()); + alert.setHeaderText(Messages.restoreFailed); + DialogHelper.positionDialog(alert, snapshotTableView, -150, -150); + alert.showAndWait(); + }); + } + completion.accept(restoreResultList); + }); + } + + /** + * Restores a snapshot from client. + * + * @param snapshot The {@link Snapshot} object subject to restore + * @param completion A handler for the outcome of the restore operation + */ + @SuppressWarnings("unused") + public void restoreFromClient(Snapshot snapshot, Consumer> completion) { new Thread(() -> { List restoreFailedPVNames = new ArrayList<>(); CountDownLatch countDownLatch = new CountDownLatch(snapshot.getSnapshotData().getSnapshotItems().size()); @@ -394,6 +462,7 @@ public void restore(Snapshot snapshot, Consumer> completion) { }).start(); } + public void setShowDeltaPercentage(boolean showDeltaPercentage) { this.showDeltaPercentage.set(showDeltaPercentage); } diff --git a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties index 0cfe09306d..9dc28c7992 100644 --- a/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties +++ b/app/save-and-restore/app/src/main/resources/org/phoebus/applications/saveandrestore/messages.properties @@ -145,6 +145,8 @@ restore=Restore restoreAndLog=Restore And Log restoreErrorTitle=Restore Error restoreErrorContent=Not all PVs were restored. +restoreFailed=Restore operation failed +restoreFailedPVs=Failed to write the following PVs: save=Save saveAsFilter=Save As Filter saveFilter=Save Filter diff --git a/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionInfo.java b/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionInfo.java index ead1d8a018..62f8a3a1ba 100644 --- a/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionInfo.java +++ b/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 European Spallation Source ERIC. + * Copyright (C) 2024 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 @@ -24,7 +24,7 @@ /** * Object wrapping information about a restore action. */ -public class RestoreSnapshotActionInfo extends SaveSnapshotActionInfo{ +public class RestoreSnapshotActionInfo extends SaveSnapshotActionInfo { private boolean isGolden; private List failedPVs; diff --git a/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/SaveAndRestoreEventLogger.java b/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/SaveAndRestoreEventLogger.java index 59e7a96665..469e322832 100644 --- a/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/SaveAndRestoreEventLogger.java +++ b/app/save-and-restore/logging/src/main/java/org/phoebus/applications/saveandrestore/logging/SaveAndRestoreEventLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 European Spallation Source ERIC. + * Copyright (C) 2024 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 @@ -20,6 +20,7 @@ import javafx.application.Platform; import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.RestoreResult; import org.phoebus.applications.saveandrestore.model.Tag; import org.phoebus.applications.saveandrestore.model.event.SaveAndRestoreEventReceiver; import org.phoebus.framework.selection.SelectionService; @@ -74,7 +75,7 @@ public void snapshotSaved(Node node, Consumer errorHandler) { * @param errorHandler An error handler callback. */ @Override - public void snapshotRestored(Node node, List failedPVs, Consumer errorHandler) { + public void snapshotRestored(Node node, List failedPVs, Consumer errorHandler) { if (!LogbookPreferences.is_supported) { return; @@ -87,7 +88,7 @@ public void snapshotRestored(Node node, List failedPVs, Consumer restoreSnapshotActionInfo.setComment(node.getDescription()); restoreSnapshotActionInfo.setGolden(node.hasTag(Tag.GOLDEN)); restoreSnapshotActionInfo.setSnapshotName(node.getName()); - restoreSnapshotActionInfo.setFailedPVs(failedPVs); + restoreSnapshotActionInfo.setFailedPVs(failedPVs.stream().map(restoreResult -> restoreResult.getSnapshotItem().getConfigPv().getPvName()).toList()); SelectionService.getInstance().setSelection("SaveAndRestoreLogging", List.of(restoreSnapshotActionInfo)); Platform.runLater(() -> ApplicationService.createInstance("logbook")); } diff --git a/app/save-and-restore/logging/src/test/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionAdapterFactoryTestData.java b/app/save-and-restore/logging/src/test/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionAdapterFactoryTestData.java index 5fbe0691a3..41734e7c8e 100644 --- a/app/save-and-restore/logging/src/test/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionAdapterFactoryTestData.java +++ b/app/save-and-restore/logging/src/test/java/org/phoebus/applications/saveandrestore/logging/RestoreSnapshotActionAdapterFactoryTestData.java @@ -32,18 +32,18 @@ public class RestoreSnapshotActionAdapterFactoryTestData { private RestoreSnapshotActionAdapterFactory restoreSnapshotActionAdapterFactory = new RestoreSnapshotActionAdapterFactory(); @Test - public void testGetAdaptableObject(){ + public void testGetAdaptableObject() { assertTrue(restoreSnapshotActionAdapterFactory.getAdaptableObject().isAssignableFrom(RestoreSnapshotActionInfo.class)); } @Test - public void testGetAdapterList(){ + public void testGetAdapterList() { List list = restoreSnapshotActionAdapterFactory.getAdapterList(); assertTrue(list.get(0).isAssignableFrom(LogEntry.class)); } @Test - public void testAdaptRestoreAction(){ + public void testAdaptRestoreAction() { RestoreSnapshotActionInfo restoreSnapshotActionInfo = new RestoreSnapshotActionInfo(); restoreSnapshotActionInfo.setSnapshotName("snapshot name"); restoreSnapshotActionInfo.setFailedPVs(List.of("pv name")); diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/RestoreResult.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/RestoreResult.java similarity index 80% rename from services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/RestoreResult.java rename to app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/RestoreResult.java index aa5d6b5cd5..cab1d697d8 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/RestoreResult.java +++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/RestoreResult.java @@ -1,9 +1,7 @@ -package org.phoebus.service.saveandrestore.epics; +package org.phoebus.applications.saveandrestore.model; import java.io.Serializable; -import org.phoebus.applications.saveandrestore.model.SnapshotItem; - public class RestoreResult implements Serializable { private SnapshotItem snapshotItem; private String errorMsg; diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/SaveAndRestorePv.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/SaveAndRestorePv.java deleted file mode 100644 index 06c053ed21..0000000000 --- a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/SaveAndRestorePv.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2020 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. - * - */ - -package org.phoebus.applications.saveandrestore.model; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.epics.vtype.VType; -import org.phoebus.applications.saveandrestore.model.json.VTypeDeserializer; -import org.phoebus.applications.saveandrestore.model.json.VTypeSerializer; - -public class SaveAndRestorePv { - - private String pvName; - - @JsonSerialize(using = VTypeSerializer.class) - @JsonDeserialize(using = VTypeDeserializer.class) - private VType value; - - public String getPvName() { - return pvName; - } - - public void setPvName(String pvName) { - this.pvName = pvName; - } - - public VType getValue() { - return value; - } - - public void setValue(VType value) { - this.value = value; - } - - public static Builder builder(){ - return new Builder(); - } - - public static class Builder{ - private SaveAndRestorePv saveAndRestorePv; - - private Builder(){ - saveAndRestorePv = new SaveAndRestorePv(); - } - - public Builder pvName(String pvName){ - saveAndRestorePv.setPvName(pvName); - return this; - } - - public Builder value(VType value){ - saveAndRestorePv.setValue(value); - return this; - } - - public SaveAndRestorePv build(){ - return saveAndRestorePv; - } - } -} diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/event/SaveAndRestoreEventReceiver.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/event/SaveAndRestoreEventReceiver.java index 735fb38082..9635a98a59 100644 --- a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/event/SaveAndRestoreEventReceiver.java +++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/event/SaveAndRestoreEventReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 European Spallation Source ERIC. + * Copyright (C) 2024 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 @@ -19,10 +19,10 @@ package org.phoebus.applications.saveandrestore.model.event; import org.phoebus.applications.saveandrestore.model.Node; +import org.phoebus.applications.saveandrestore.model.RestoreResult; import java.util.List; import java.util.function.Consumer; -import java.util.function.Function; /** * Implementations registered over Java SPI will be called when: @@ -36,16 +36,18 @@ public interface SaveAndRestoreEventReceiver { /** * Called when a new snapshot {@link Node} has been successfully created and saved by the remote * save-and-restore service. - * @param node The {@link Node} representing the snapshot. + * + * @param node The {@link Node} representing the snapshot. * @param errorHandler An error handler callback. */ void snapshotSaved(Node node, Consumer errorHandler); /** * Called when a new snapshot {@link Node} has been restored. - * @param node The {@link Node} representing the snapshot. - * @param failedPVs List of PVs that for any reason could not be restored. + * + * @param node The {@link Node} representing the snapshot. + * @param failedPVs List of PVs that for any reason could not be restored. * @param errorHandler An error handler callback. */ - void snapshotRestored(Node node, List failedPVs, Consumer errorHandler); + void snapshotRestored(Node node, List failedPVs, Consumer errorHandler); } diff --git a/services/save-and-restore/pom.xml b/services/save-and-restore/pom.xml index 708523fe2c..da39500453 100644 --- a/services/save-and-restore/pom.xml +++ b/services/save-and-restore/pom.xml @@ -74,6 +74,21 @@ core-pv 4.7.4-SNAPSHOT + + org.phoebus + core-pv-pva + 4.7.4-SNAPSHOT + + + org.phoebus + core-pv-ca + 4.7.4-SNAPSHOT + + + org.phoebus + core-vtype + 4.7.4-SNAPSHOT + org.springframework.boot diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java index 6f0ff6a2ae..3968a5e9b3 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorer.java @@ -1,50 +1,49 @@ package org.phoebus.service.saveandrestore.epics; -import java.io.File; -import java.io.FileInputStream; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import org.epics.pva.client.PVAClient; -import org.epics.vtype.VBoolean; -import org.epics.vtype.VBooleanArray; -import org.epics.vtype.VByteArray; -import org.epics.vtype.VDoubleArray; -import org.epics.vtype.VEnum; -import org.epics.vtype.VEnumArray; -import org.epics.vtype.VFloatArray; -import org.epics.vtype.VIntArray; -import org.epics.vtype.VLongArray; -import org.epics.vtype.VNumber; -import org.epics.vtype.VNumberArray; -import org.epics.vtype.VShortArray; -import org.epics.vtype.VString; -import org.epics.vtype.VStringArray; -import org.epics.vtype.VType; -import org.epics.vtype.VUByteArray; -import org.epics.vtype.VUIntArray; -import org.epics.vtype.VULongArray; -import org.epics.vtype.VUShortArray; - -import org.phoebus.framework.preferences.PropertyPreferenceLoader; +import org.phoebus.applications.saveandrestore.model.RestoreResult; import org.phoebus.applications.saveandrestore.model.SnapshotItem; import org.phoebus.core.vtypes.VTypeHelper; +import org.phoebus.framework.preferences.PropertyPreferenceLoader; import org.phoebus.pv.PV; import org.phoebus.pv.PVPool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import java.io.File; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; public class SnapshotRestorer { - PVAClient pva; private final Logger LOG = Logger.getLogger(SnapshotRestorer.class.getName()); - public SnapshotRestorer() throws Exception { - pva = new PVAClient(); + @Value("${connection.timeout:5000}") + private int connectionTimeout; + + @Value("${write.timeout:5000}") + private int writeTimeout; + + @Autowired + private ExecutorService executorService; + + public SnapshotRestorer() { final File site_settings = new File("settings.ini"); if (site_settings.canRead()) { LOG.config("Loading settings from " + site_settings); - PropertyPreferenceLoader.load(new FileInputStream(site_settings)); + try { + PropertyPreferenceLoader.load(new FileInputStream(site_settings)); + } catch (Exception e) { + LOG.log(Level.WARNING, "Unable to read settings.ini, falling back to default values."); + } } } @@ -52,101 +51,105 @@ public SnapshotRestorer() throws Exception { * Restore PV values from a list of snapshot items * *

- * Writes concurrently the pv value to the non null set PVs in + * Writes concurrently the pv value to the non-null set PVs in * the snapshot items. - * Uses synchonized to ensure only one frontend can write at a time. + * Uses synchronized to ensure only one frontend can write at a time. * Returns a list of the snapshot items you have set, with an error message if * an error occurred. * * @param snapshotItems {@link SnapshotItem} */ public synchronized List restorePVValues(List snapshotItems) { + // Attempt to connect to all PVs before trying to write/restore. + List connectedPvs = connectPVs(snapshotItems); + List failedPvs = new ArrayList<>(); + final CountDownLatch countDownLatch = new CountDownLatch(snapshotItems.size()); + snapshotItems.forEach(item -> { + String pvName = item.getConfigPv().getPvName(); + Optional pvOptional = null; + try { + // Check if PV is connected. If not, do not even try to write/restore. + pvOptional = connectedPvs.stream().filter(pv -> pv.getName().equals(pvName)).findFirst(); + if (pvOptional.isPresent()) { + pvOptional.get().write(VTypeHelper.toObject(item.getValue())); + } else { + RestoreResult restoreResult = new RestoreResult(); + restoreResult.setSnapshotItem(item); + restoreResult.setErrorMsg("PV disconnected"); + failedPvs.add(restoreResult); + } + } catch (Exception e) { + LOG.log(Level.WARNING, "Failed to restore PV " + pvName); + RestoreResult restoreResult = new RestoreResult(); + restoreResult.setSnapshotItem(item); + restoreResult.setErrorMsg(e.getMessage()); + failedPvs.add(restoreResult); + } finally { + if (pvOptional != null && pvOptional.isPresent()) { + PVPool.releasePV(pvOptional.get()); + } + countDownLatch.countDown(); + } + }); - var futures = snapshotItems.stream().filter( - (snapshot_item) -> snapshot_item.getConfigPv().getPvName() != null) - .map((snapshotItem) -> { - var pvName = snapshotItem.getConfigPv().getPvName(); - var pvValue = snapshotItem.getValue(); - Object rawValue = vTypeToObject(pvValue); - PV pv; - CompletableFuture future; - try { - pv = PVPool.getPV(pvName); - future = pv.asyncWrite(rawValue); - } catch (Exception e) { - var restoreResult = new RestoreResult(); - var errorMsg = e.getMessage(); - restoreResult.setSnapshotItem(snapshotItem); - restoreResult.setErrorMsg(errorMsg); - LOG.warning(String.format("Error writing to channel %s %s", pvName, errorMsg)); - return CompletableFuture.completedFuture(restoreResult); - } - return future.handle((result, ex) -> { - String errorMsg; - if (ex != null) { - errorMsg = ex.getMessage(); - LOG.warning(String.format("Error writing to channel %s %s", pvName, errorMsg)); - } else { - errorMsg = null; - } - var restoreResult = new RestoreResult(); - restoreResult.setSnapshotItem(snapshotItem); - restoreResult.setErrorMsg(errorMsg); - return restoreResult; - }); - }) - .collect(Collectors.toList()); - - CompletableFuture all_done = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - - // Wait on the futures concurrently - all_done.join(); + try { + countDownLatch.await(writeTimeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + LOG.log(Level.INFO, "Encountered InterruptedException", e); + } - // Joins should not block as all the futures should be completed. - return futures.stream().map( - (future) -> future.join()).collect(Collectors.toList()); + return failedPvs; } /** - * Convert a vType to its Object representation - * - * @param type {@link VType} + * Attempts to connect to all PVs using {@link PVPool}. A connection is considered successful once an + * event is received that does not indicate disconnection. + * + *

+ * A timeout of {@link #connectionTimeout} ms is used to wait for a PV to supply a value message indicating + * successful connection. + *

+ *

+ * An {@link ExecutorService} is used to run connection attempts concurrently. However, no timeout is employed + * for the overall execution of all connection attempts. + *

+ * + * @param snapshotItems List of {@link SnapshotItem}s in a snapshot. + * @return A {@link List} of {@link PV}s for which connection succeeded. Ideally this should be all + * PVs as listed in the input argument. */ - private Object vTypeToObject(VType type) { - if (type == null) { - return null; - } - if (type instanceof VNumberArray) { - if (type instanceof VIntArray || type instanceof VUIntArray) { - return VTypeHelper.toIntegers(type); - } else if (type instanceof VDoubleArray) { - return VTypeHelper.toDoubles(type); - } else if (type instanceof VFloatArray) { - return VTypeHelper.toFloats(type); - } else if (type instanceof VLongArray || type instanceof VULongArray) { - return VTypeHelper.toLongs(type); - } else if (type instanceof VShortArray || type instanceof VUShortArray) { - return VTypeHelper.toShorts(type); - } else if (type instanceof VByteArray || type instanceof VUByteArray) { - return VTypeHelper.toBytes(type); - } - } else if (type instanceof VEnumArray) { - List data = ((VEnumArray) type).getData(); - return data.toArray(new String[data.size()]); - } else if (type instanceof VStringArray) { - List data = ((VStringArray) type).getData(); - return data.toArray(new String[data.size()]); - } else if (type instanceof VBooleanArray) { - return VTypeHelper.toBooleans(type); - } else if (type instanceof VNumber) { - return ((VNumber) type).getValue(); - } else if (type instanceof VEnum) { - return ((VEnum) type).getIndex(); - } else if (type instanceof VString) { - return ((VString) type).getValue(); - } else if (type instanceof VBoolean) { - return ((VBoolean) type).getValue(); + private List connectPVs(List snapshotItems) { + List connectedPvs = new ArrayList<>(); + + List> callables = snapshotItems.stream().map(snapshotItem -> { + return (Callable) () -> { + CountDownLatch countDownLatch = new CountDownLatch(1); + try { + PV pv = PVPool.getPV(snapshotItem.getConfigPv().getPvName()); + pv.onValueEvent().subscribe(value -> { + if (!PV.isDisconnected(value)) { + connectedPvs.add(pv); + countDownLatch.countDown(); + } + }); + // Wait for a value message indicating connection + countDownLatch.await(connectionTimeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOG.log(Level.WARNING, "Failed to connect to PV " + snapshotItem.getConfigPv().getPvName(), e); + countDownLatch.countDown(); + } + return null; + }; + }).toList(); + + try { + executorService.invokeAll(callables); + } catch (InterruptedException e) { + LOG.log(Level.WARNING, "Got exception waiting for all tasks to finish", e); + // Return empty list here? + return Collections.emptyList(); } - return null; + + return connectedPvs; } } diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java index e44a8ef857..48087c22fa 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/config/WebConfiguration.java @@ -17,10 +17,16 @@ */ package org.phoebus.service.saveandrestore.web.config; +import org.phoebus.service.saveandrestore.epics.SnapshotRestorer; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ElasticsearchDAO; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; /** * {@link Configuration} class setting up beans for {@link org.springframework.stereotype.Controller} classes. @@ -47,4 +53,16 @@ public NodeDAO nodeDAO() { public AcceptHeaderResolver acceptHeaderResolver() { return new AcceptHeaderResolver(); } + + @SuppressWarnings("unused") + @Bean + @Scope("singleton") + public SnapshotRestorer snapshotRestorer(){ + return new SnapshotRestorer(); + } + + @Bean + public ExecutorService executorService(){ + return Executors.newCachedThreadPool(); + } } 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 e60e7e664a..9a51d9647a 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 @@ -17,28 +17,21 @@ */ package org.phoebus.service.saveandrestore.web.controllers; -import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; -import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; -import org.phoebus.applications.saveandrestore.model.ConfigPv; -import org.phoebus.applications.saveandrestore.model.Node; -import org.phoebus.applications.saveandrestore.model.Snapshot; -import org.phoebus.applications.saveandrestore.model.SnapshotData; +import org.phoebus.applications.saveandrestore.model.RestoreResult; import org.phoebus.applications.saveandrestore.model.SnapshotItem; import org.phoebus.service.saveandrestore.epics.SnapshotRestorer; -import org.phoebus.service.saveandrestore.epics.RestoreResult; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +/** + * {@link RestController} performing server-side restore operation. + */ @SuppressWarnings("unused") @RestController public class SnapshotRestoreController extends BaseController { @@ -46,18 +39,20 @@ public class SnapshotRestoreController extends BaseController { @Autowired private NodeDAO nodeDAO; + @Autowired + private SnapshotRestorer snapshotRestorer; + @PostMapping(value = "/restore/items", produces = JSON) public List restoreFromSnapshotItems( - @RequestBody List snapshotItems) throws Exception { - var snapshotRestorer = new SnapshotRestorer(); - return snapshotRestorer.restorePVValues(snapshotItems); + @RequestBody List snapshotItems) { + return snapshotRestorer.restorePVValues(snapshotItems); } + @PostMapping(value = "/restore/node", produces = JSON) public List restoreFromSnapshotNode( - @RequestParam(value = "parentNodeId") String parentNodeId) throws Exception { - var snapshotRestorer = new SnapshotRestorer(); - var snapshot = nodeDAO.getSnapshotData(parentNodeId); - return snapshotRestorer.restorePVValues(snapshot.getSnapshotItems()); + @RequestParam(value = "nodeId") String nodeId){ + var snapshot = nodeDAO.getSnapshotData(nodeId); + return snapshotRestorer.restorePVValues(snapshot.getSnapshotItems()); } } diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties index 525552efcb..6ad52e731e 100644 --- a/services/save-and-restore/src/main/resources/application.properties +++ b/services/save-and-restore/src/main/resources/application.properties @@ -92,3 +92,6 @@ authorization.permitall = true role.user=sar-user role.admin=sar-admin +############## EPICS related ################# +connection.timeout=5000 +write.timout=5000 diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorerTest.java similarity index 54% rename from services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java rename to services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorerTest.java index 8ef2b834d6..02126392cf 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/SnapshotRestorerTest.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorerTest.java @@ -1,4 +1,23 @@ -package org.phoebus.service.saveandrestore.web.controllers; +/* + * Copyright (C) 2023 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. + * + */ + +package org.phoebus.service.saveandrestore.epics; import org.junit.jupiter.api.Assertions; @@ -9,18 +28,26 @@ import org.epics.vtype.Alarm; import org.epics.vtype.Display; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.phoebus.applications.saveandrestore.model.ConfigPv; import org.phoebus.applications.saveandrestore.model.SnapshotItem; import org.phoebus.core.vtypes.VTypeHelper; import org.phoebus.pv.PV; import org.phoebus.pv.PVPool; -import org.phoebus.service.saveandrestore.epics.SnapshotRestorer; +import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = SnapshotRestorerTestConfig.class) public class SnapshotRestorerTest { + @Autowired + private SnapshotRestorer snapshotRestorer; + @Test public void testRestorePVValues() throws Exception { - var snapshotRestorer = new SnapshotRestorer(); PV pv = PVPool.getPV("loc://x(42.0)"); var configPv = new ConfigPv(); configPv.setPvName("loc://x"); @@ -36,7 +63,6 @@ public void testRestorePVValues() throws Exception { @Test public void testCannotConnectPV() throws Exception { - var snapshotRestorer = new SnapshotRestorer(); var configPv = new ConfigPv(); configPv.setPvName("pva://x"); diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorerTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorerTestConfig.java new file mode 100644 index 0000000000..eb0d996797 --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/epics/SnapshotRestorerTestConfig.java @@ -0,0 +1,58 @@ +/** + * 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. + */ + +package org.phoebus.service.saveandrestore.epics; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import org.mockito.Mockito; +import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ConfigurationDataRepository; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ElasticsearchTreeRepository; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.FilterRepository; +import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.SnapshotDataRepository; +import org.phoebus.service.saveandrestore.search.SearchUtil; +import org.phoebus.service.saveandrestore.web.config.AcceptHeaderResolver; +import org.phoebus.service.saveandrestore.web.config.WebSecurityConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Scope; +import org.springframework.util.Base64Utils; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@SpringBootConfiguration +@ComponentScan(basePackages = "org.phoebus.service.saveandrestore.epics") +@SuppressWarnings("unused") +@Profile("!IT") +public class SnapshotRestorerTestConfig { + + @Bean + public SnapshotRestorer snapshotRestorer(){ + return new SnapshotRestorer(); + } + + @Bean + public ExecutorService executorService(){ + return Executors.newCachedThreadPool(); + } +} diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java index 8e29211f0d..55613e09dd 100644 --- a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/config/ControllersTestConfig.java @@ -20,6 +20,7 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import org.mockito.Mockito; +import org.phoebus.service.saveandrestore.epics.SnapshotRestorer; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ConfigurationDataRepository; import org.phoebus.service.saveandrestore.persistence.dao.impl.elasticsearch.ElasticsearchTreeRepository; @@ -32,8 +33,12 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Scope; import org.springframework.util.Base64Utils; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + @SpringBootConfiguration @ComponentScan(basePackages = "org.phoebus.service.saveandrestore.web.controllers") @Import(WebSecurityConfig.class) @@ -115,4 +120,16 @@ public String adminAuthorization() { public String readOnlyAuthorization() { return "Basic " + Base64Utils.encodeToString((demoReadOnly + ":" + demoReadOnlyPassword).getBytes()); } + + @SuppressWarnings("unused") + @Bean + @Scope("singleton") + public SnapshotRestorer snapshotRestorer(){ + return new SnapshotRestorer(); + } + + @Bean + public ExecutorService executorService(){ + return Executors.newCachedThreadPool(); + } } 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 4182abedaf..221ddbe9ff 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 @@ -12,10 +12,10 @@ 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.Snapshot; import org.phoebus.applications.saveandrestore.model.SnapshotData; import org.phoebus.applications.saveandrestore.model.SnapshotItem; -import org.phoebus.service.saveandrestore.epics.RestoreResult; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; import org.springframework.beans.factory.annotation.Autowired; @@ -73,7 +73,7 @@ public void testRestoreFromSnapshotNode() throws Exception { when(nodeDAO.getSnapshotData("uniqueId")).thenReturn(snapshotData); - MockHttpServletRequestBuilder request = post("/restore/node?parentNodeId=uniqueId") + MockHttpServletRequestBuilder request = post("/restore/node?nodeId=uniqueId") .header(HttpHeaders.AUTHORIZATION, userAuthorization); MvcResult result = mockMvc.perform(request).andExpect(status().isOk()).andExpect(content().contentType(JSON))