From c6bcff9827fef6baa0859c590bd996b87864bf2d Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 12 Nov 2026 15:28:23 +0100 Subject: [PATCH 1/3] Save&restore service property to allow/block server side restore --- .../saveandrestore/web/controllers/InfoController.java | 5 +++++ .../src/main/resources/application.properties | 3 +++ 2 files changed, 8 insertions(+) diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java index ea0647b6e2..fdf277ec61 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java @@ -40,6 +40,9 @@ public class InfoController extends BaseController { @Qualifier("restClient") Rest5Client restClient; + @Value("${serverRestoreAllowed:false}") + private boolean serverRestoreAllowed; + private static final ObjectMapper objectMapper = JsonMapper.builder() .enable(SerializationFeature.INDENT_OUTPUT) .build(); @@ -73,6 +76,8 @@ public String info() { saveRestoreServiceInfo.put("rootNodeID", ROOT_FOLDER_UNIQUE_ID); + saveRestoreServiceInfo.put("serverRestoreAllowed", serverRestoreAllowed); + try { return objectMapper.writeValueAsString(saveRestoreServiceInfo); } catch (JacksonException e) { diff --git a/services/save-and-restore/src/main/resources/application.properties b/services/save-and-restore/src/main/resources/application.properties index 3ac9ac69fd..9457b410e4 100644 --- a/services/save-and-restore/src/main/resources/application.properties +++ b/services/save-and-restore/src/main/resources/application.properties @@ -72,3 +72,6 @@ inMemoryPropertySource=classpath:/security/in_memory.properties # embeddedLdap - embedded LDAP # activeDirectory - Active Directory authenticationProviders=inMemory + +########### Allow server side restore ############# +serverRestoreAllowed=false From 99793cf4c151652d711376016f492a60cd6f5bc5 Mon Sep 17 00:00:00 2001 From: georgweiss Date: Thu, 12 Nov 2026 17:29:53 +0100 Subject: [PATCH 2/3] Disable/enable restore endpoints based on property --- services/save-and-restore/doc/index.rst | 4 ++ .../SnapshotRestoreController.java | 4 +- .../BlockedServerRestoreControllerTest.java | 58 +++++++++++++++++++ ...pplication_block_server_restore.properties | 3 + .../test_application_permit_all.properties | 1 + 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/BlockedServerRestoreControllerTest.java create mode 100644 services/save-and-restore/src/test/resources/test_application_block_server_restore.properties diff --git a/services/save-and-restore/doc/index.rst b/services/save-and-restore/doc/index.rst index cb8f2b0578..ba0a4ed48e 100644 --- a/services/save-and-restore/doc/index.rst +++ b/services/save-and-restore/doc/index.rst @@ -839,6 +839,10 @@ The response is a snapshot object representing the persisted data, see :ref:`Get Server Restore Endpoints ------------------------ +**NOTE:** the restore endpoints are disabled by default. To enable these, the service must be launched with +runtime property ``serverRestoreAllowed=true``. This can specified on the Java command line (``-DserverRestoreAllowed=true``) +or set as an environment variable. + Restore from snapshot items """"""""""""""""""""""""""" 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 866b9b481a..783aba59d9 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 @@ -10,6 +10,8 @@ import org.phoebus.saveandrestore.util.SnapshotUtil; import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -24,6 +26,7 @@ */ @SuppressWarnings("unused") @RestController +@ConditionalOnBooleanProperty(name = "serverRestoreAllowed") public class SnapshotRestoreController extends BaseController { @Autowired @@ -70,4 +73,3 @@ public List restoreFromSnapshotNode( return snapshotUtil.restore(snapshotItems, connectionTimeout); } } - diff --git a/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/BlockedServerRestoreControllerTest.java b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/BlockedServerRestoreControllerTest.java new file mode 100644 index 0000000000..be2c913a3c --- /dev/null +++ b/services/save-and-restore/src/test/java/org/phoebus/service/saveandrestore/web/controllers/BlockedServerRestoreControllerTest.java @@ -0,0 +1,58 @@ +package org.phoebus.service.saveandrestore.web.controllers; + +import org.epics.vtype.Alarm; +import org.epics.vtype.Display; +import org.epics.vtype.Time; +import org.epics.vtype.VFloat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.phoebus.applications.saveandrestore.model.*; +import org.phoebus.service.saveandrestore.persistence.dao.NodeDAO; +import org.phoebus.service.saveandrestore.web.config.ControllersTestConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.phoebus.service.saveandrestore.web.controllers.BaseController.JSON; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(classes = {ControllersTestConfig.class}, + webEnvironment = SpringBootTest.WebEnvironment.MOCK, + properties = "spring.main.allow-bean-definition-overriding=true") +@TestPropertySource(locations = "classpath:test_application_block_server_restore.properties") +public class BlockedServerRestoreControllerTest { + + @Autowired + private String userAuthorization; + + @Autowired + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).apply(springSecurity()).build(); + } + + @Test + public void testServerRestoreBlocked() throws Exception{ + MockHttpServletRequestBuilder request = post("/restore/node?nodeId=uniqueId") + .header(HttpHeaders.AUTHORIZATION, userAuthorization); + mockMvc.perform(request).andExpect(status().isNotFound()); + } +} diff --git a/services/save-and-restore/src/test/resources/test_application_block_server_restore.properties b/services/save-and-restore/src/test/resources/test_application_block_server_restore.properties new file mode 100644 index 0000000000..a004dd5ffa --- /dev/null +++ b/services/save-and-restore/src/test/resources/test_application_block_server_restore.properties @@ -0,0 +1,3 @@ +authenticationProivers=inMemory +authorization.permitall=true +serverRestoreAllowed=false diff --git a/services/save-and-restore/src/test/resources/test_application_permit_all.properties b/services/save-and-restore/src/test/resources/test_application_permit_all.properties index 0687337db8..a41b4cf62c 100644 --- a/services/save-and-restore/src/test/resources/test_application_permit_all.properties +++ b/services/save-and-restore/src/test/resources/test_application_permit_all.properties @@ -1,2 +1,3 @@ authenticationProivers=inMemory authorization.permitall=true +serverRestoreAllowed=true From 28d15a56f0abe38a286fa7e6c5be15eb5be2efea Mon Sep 17 00:00:00 2001 From: georgweiss Date: Fri, 13 Nov 2026 15:38:42 +0100 Subject: [PATCH 3/3] Added data object for save&restore server info --- .../client/SaveAndRestoreClient.java | 3 + .../client/SaveAndRestoreClientImpl.java | 7 ++ .../ui/SaveAndRestoreController.java | 1 - .../ui/SaveAndRestoreService.java | 4 + .../saveandrestore/model/ServiceInfo.java | 16 ++++ .../util/elasticsearch/ElasticInfo.java | 24 +++++ .../util/elasticsearch/IndexStats.java | 16 ++++ .../persistence/config/ElasticConfig.java | 22 +---- .../persistence/config/IndexConfig.java | 36 ++++++++ .../ElasticsearchTreeRepository.java | 12 +-- .../saveandrestore/search/SearchUtil.java | 16 +--- .../web/controllers/BaseController.java | 3 +- .../web/controllers/InfoController.java | 88 +++++++++++-------- 13 files changed, 170 insertions(+), 78 deletions(-) create mode 100644 app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/ServiceInfo.java create mode 100644 core/util/src/main/java/org/phoebus/util/elasticsearch/ElasticInfo.java create mode 100644 core/util/src/main/java/org/phoebus/util/elasticsearch/IndexStats.java create mode 100644 services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java 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 a9cfa50944..5938d2bbc9 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 @@ -36,6 +36,7 @@ import javax.ws.rs.core.MultivaluedMap; import java.util.List; +import java.util.Map; /** * Implementations of this interface shall handle the communication to the save-and-restore service. @@ -275,4 +276,6 @@ public interface SaveAndRestoreClient { * @return A {@link List} of {@link SnapshotItem}s carrying snapshot values read by the service. */ List takeSnapshot(String configurationNodeId); + + Map getServerInfo(); } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java index 67ec7536c8..0551b2f0f7 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClientImpl.java @@ -43,6 +43,7 @@ import java.time.Duration; import java.util.Base64; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -665,6 +666,12 @@ public List takeSnapshot(String configurationNodeId) { }); } + @Override + public Map getServerInfo(){ + return getCall("/", new TypeReference<>() { + }); + } + private T getCall(String relativeUrl, Class clazz) { HttpResponse response = getCall(relativeUrl); try { diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java index 62694681f2..fbb85bd9f5 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java @@ -352,7 +352,6 @@ public Filter fromString(String s) { saveAndRestoreService.setConnectCallback(this::handleWebSocketConnected); saveAndRestoreService.setDisconnectCallback(this::handleWebSocketDisconnected); saveAndRestoreService.connectWebSocket(); - } /** 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 458bd7f7db..f02ec6118f 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 @@ -58,6 +58,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; @@ -517,4 +518,7 @@ public void handleWebSocketMessage(String message) { } } + public Map getServerInfo(){ + return saveAndRestoreClient.getServerInfo(); + } } diff --git a/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/ServiceInfo.java b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/ServiceInfo.java new file mode 100644 index 0000000000..44a1a443d3 --- /dev/null +++ b/app/save-and-restore/model/src/main/java/org/phoebus/applications/saveandrestore/model/ServiceInfo.java @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2026 European Spallation Source ERIC. + * + */ + +package org.phoebus.applications.saveandrestore.model; + +import org.phoebus.util.elasticsearch.ElasticInfo; +/** + * POJO for server side information, e.g. versions and enabled/disabled features. + * @param serviceVersion Version of the service + * @param serverRestoreEnabled Indicates if restore endpoints are enabled + * @param elasticInfo Information on Elasticsearch and its indices + */ +public record ServiceInfo(String serviceVersion, boolean serverRestoreEnabled, ElasticInfo elasticInfo) { +} diff --git a/core/util/src/main/java/org/phoebus/util/elasticsearch/ElasticInfo.java b/core/util/src/main/java/org/phoebus/util/elasticsearch/ElasticInfo.java new file mode 100644 index 0000000000..79a1771c09 --- /dev/null +++ b/core/util/src/main/java/org/phoebus/util/elasticsearch/ElasticInfo.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2026 European Spallation Source ERIC. + * + */ + +package org.phoebus.util.elasticsearch; + +import java.util.Collections; +import java.util.List; + +/** + * Wraps a few properties related to the Elasticsearch service. + * @param connectStatus Indicates if the Elasticsearch connection is functional. + * @param version Version of Elasticsearch. + * @param indexStats Statistics for relevant indices as determined by the service. + */ +public record ElasticInfo(String connectStatus, String version, List indexStats) { + public ElasticInfo(){ + this("Disconnected", "n/a", Collections.emptyList()); + } + public ElasticInfo(String version, List indexStats){ + this("Connected", version, indexStats); + } +} diff --git a/core/util/src/main/java/org/phoebus/util/elasticsearch/IndexStats.java b/core/util/src/main/java/org/phoebus/util/elasticsearch/IndexStats.java new file mode 100644 index 0000000000..4dfe0907d4 --- /dev/null +++ b/core/util/src/main/java/org/phoebus/util/elasticsearch/IndexStats.java @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2026 European Spallation Source ERIC. + * + */ + +package org.phoebus.util.elasticsearch; + +/** + * Wraps a few properties for an Elasticsearch index. + * @param indexName Name of the index, e.g. saveandrestore_configuration + * @param documentCount Number of indexed documents + * @param deletedDocuments Number of deleted documents + * @param indexSize Index size in bytes + */ +public record IndexStats(String indexName, int documentCount, int deletedDocuments, long indexSize) { +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java index 7a95899914..5d07e0f42c 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/ElasticConfig.java @@ -48,30 +48,10 @@ @Configuration @ComponentScan(basePackages = {"org.phoebus.service.saveandrestore"}) @PropertySource("classpath:application.properties") -public class ElasticConfig { +public class ElasticConfig extends IndexConfig{ private static final Logger logger = Logger.getLogger(ElasticConfig.class.getName()); - @SuppressWarnings("unused") - @Value("${elasticsearch.tree_node.index:saveandrestore_tree}") - private String ES_TREE_INDEX; - - @SuppressWarnings("unused") - @Value("${elasticsearch.configuration_node.index:saveandrestore_configuration}") - private String ES_CONFIGURATION_INDEX; - - @SuppressWarnings("unused") - @Value("${elasticsearch.snapshot_node.index:saveandrestore_snapshot}") - private String ES_SNAPSHOT_INDEX; - - @SuppressWarnings("unused") - @Value("${elasticsearch.composite_snapshot_node.index:saveandrestore_composite_snapshot}") - private String ES_COMPOSITE_SNAPSHOT_INDEX; - - @SuppressWarnings("unused") - @Value("${elasticsearch.filter.index:saveandrestore_filter}") - private String ES_FILTER_INDEX; - @SuppressWarnings("unused") @Value("${elasticsearch.network.host:localhost}") private String host; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java new file mode 100644 index 0000000000..92483541b4 --- /dev/null +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2026 European Spallation Source ERIC. + * + */ + +package org.phoebus.service.saveandrestore.persistence.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Component to centralize values related to Elasticsearch index names. + */ +@Component +public class IndexConfig { + + @SuppressWarnings("unused") + @Value("${elasticsearch.tree_node.index:saveandrestore_tree}") + public String ES_TREE_INDEX; + + @SuppressWarnings("unused") + @Value("${elasticsearch.configuration_node.index:saveandrestore_configuration}") + public String ES_CONFIGURATION_INDEX; + + @SuppressWarnings("unused") + @Value("${elasticsearch.snapshot_node.index:saveandrestore_snapshot}") + public String ES_SNAPSHOT_INDEX; + + @SuppressWarnings("unused") + @Value("${elasticsearch.composite_snapshot_node.index:saveandrestore_composite_snapshot}") + public String ES_COMPOSITE_SNAPSHOT_INDEX; + + @SuppressWarnings("unused") + @Value("${elasticsearch.filter.index:saveandrestore_filter}") + public String ES_FILTER_INDEX; +} diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java index 4157391cc4..b71aec6d30 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/dao/impl/elasticsearch/ElasticsearchTreeRepository.java @@ -15,10 +15,10 @@ import org.phoebus.applications.saveandrestore.model.search.SearchResult; import org.phoebus.service.saveandrestore.NodeNotFoundException; import org.phoebus.service.saveandrestore.model.ESTreeNode; +import org.phoebus.service.saveandrestore.persistence.config.IndexConfig; import org.phoebus.service.saveandrestore.search.SearchUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.repository.CrudRepository; import org.springframework.lang.NonNull; import org.springframework.stereotype.Repository; @@ -39,14 +39,10 @@ * Repository for {@link ESTreeNode} objects. */ @Repository -public class ElasticsearchTreeRepository implements CrudRepository { +public class ElasticsearchTreeRepository extends IndexConfig implements CrudRepository { private static final Logger logger = Logger.getLogger(ElasticsearchTreeRepository.class.getName()); - @SuppressWarnings("unused") - @Value("${elasticsearch.tree_node.index:saveandrestore_tree}") - private String ES_TREE_INDEX; - /** * Used to determine if the {@link ESTreeNode} is saved or updated in connection to a migration * operation. If so, the last modified date should be preserved. @@ -286,7 +282,7 @@ public ESTreeNode getParentNode(String uniqueId) { logger.log(Level.SEVERE, "Node " + uniqueId + " is child node of multiple nodes. Should not happen!"); throw new RuntimeException("Node " + uniqueId + " contained in multiple parent nodes. Should not happen!"); } - return result.get(0); + return result.getFirst(); } else { throw new NodeNotFoundException("Unable to locate parent node for unique id " + uniqueId); } @@ -345,7 +341,7 @@ public SearchResult search(MultiValueMap searchParameters) { List hits = parseSearchHits(searchResponse, ESTreeNode.class); SearchResult searchResult = new SearchResult(); searchResult.setHitCount((int) getTotalHits(searchResponse)); - searchResult.setNodes(hits.stream().map(e -> e.getNode()).filter(Objects::nonNull).toList()); + searchResult.setNodes(hits.stream().map(ESTreeNode::getNode).filter(Objects::nonNull).toList()); return searchResult; } catch (IOException e) { throw new RuntimeException(e); diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/search/SearchUtil.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/search/SearchUtil.java index f40b869315..aba9aece2d 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/search/SearchUtil.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/search/SearchUtil.java @@ -17,6 +17,7 @@ import co.elastic.clients.elasticsearch._types.query_dsl.WildcardQuery; import co.elastic.clients.elasticsearch.core.SearchRequest; import org.phoebus.applications.saveandrestore.model.Tag; +import org.phoebus.service.saveandrestore.persistence.config.IndexConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -44,20 +45,11 @@ * @author Kunal Shroff * @author Georg Weiss */ -public class SearchUtil { +public class SearchUtil extends IndexConfig { final private static String MILLI_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; final private static DateTimeFormatter MILLI_FORMAT = DateTimeFormatter.ofPattern(MILLI_PATTERN).withZone(ZoneId.systemDefault()); - @SuppressWarnings("unused") - @Value("${elasticsearch.tree_node.index:saveandrestore_tree}") - private String ES_TREE_INDEX; - @SuppressWarnings("unused") - @Value("${elasticsearch.configuration_node.index:saveandrestore_configuration}") - private String ES_CONFIGURATION_INDEX; - @SuppressWarnings("unused") - @Value("${elasticsearch.composite_snapshot_node.index:saveandrestore_composite_snapshot}") - private String ES_COMPOSITE_SNAPSHOT_INDEX; @SuppressWarnings("unused") @Value("${elasticsearch.result.size.search.default:100}") private int defaultSearchSize; @@ -402,7 +394,7 @@ public List getSearchTerms(String searchQueryTerms) { // Add remaining terms... List remaining = Arrays.asList(searchQueryTerms.split("[\\|,;\\s+]")); //...but remove empty strings, which are "leftovers" when quoted terms are removed - terms.addAll(remaining.stream().filter(t -> t.length() > 0).collect(Collectors.toList())); + terms.addAll(remaining.stream().filter(t -> t.length() > 0).toList()); return terms; } @@ -437,7 +429,7 @@ private SearchRequest buildSearchRequestForContainedIn(MultiValueMap diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java index b732ed5c6c..d4edb52e1e 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/BaseController.java @@ -19,6 +19,7 @@ import org.phoebus.service.saveandrestore.NodeNotFoundException; import org.phoebus.service.saveandrestore.SnapshotNotFoundException; +import org.phoebus.service.saveandrestore.persistence.config.IndexConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -39,7 +40,7 @@ */ @RestController @SuppressWarnings("unused") -public abstract class BaseController { +public abstract class BaseController extends IndexConfig { protected static final String JSON = "application/json"; diff --git a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java index fdf277ec61..44f5cac2f2 100644 --- a/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java +++ b/services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/web/controllers/InfoController.java @@ -2,7 +2,10 @@ import co.elastic.clients.transport.rest5_client.low_level.Request; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; -import tools.jackson.core.JacksonException; +import jakarta.annotation.PostConstruct; +import org.phoebus.applications.saveandrestore.model.ServiceInfo; +import org.phoebus.util.elasticsearch.ElasticInfo; +import org.phoebus.util.elasticsearch.IndexStats; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.SerializationFeature; @@ -11,16 +14,14 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import static org.phoebus.applications.saveandrestore.model.Node.ROOT_FOLDER_UNIQUE_ID; /** * Controller implementing endpoints to retrieve service info @@ -30,9 +31,6 @@ public class InfoController extends BaseController { private final Logger logger = Logger.getLogger(InfoController.class.getName()); - @Value("${app.name:4.7.0}") - private String name; - @Value("${app.version:4.7.0}") private String version; @@ -43,46 +41,66 @@ public class InfoController extends BaseController { @Value("${serverRestoreAllowed:false}") private boolean serverRestoreAllowed; + + @SuppressWarnings("unused") + @Value("${elasticsearch.configuration_node.index:saveandrestore_configuration}") + private String ES_CONFIGURATION_INDEX; + + @SuppressWarnings("unused") + @Value("${elasticsearch.snapshot_node.index:saveandrestore_snapshot}") + private String ES_SNAPSHOT_INDEX; + + @SuppressWarnings("unused") + @Value("${elasticsearch.composite_snapshot_node.index:saveandrestore_composite_snapshot}") + private String ES_COMPOSITE_SNAPSHOT_INDEX; + + @SuppressWarnings("unused") + @Value("${elasticsearch.filter.index:saveandrestore_filter}") + private String ES_FILTER_INDEX; + + private String[] indices; + private static final ObjectMapper objectMapper = JsonMapper.builder() .enable(SerializationFeature.INDENT_OUTPUT) .build(); + @PostConstruct + public void init(){ + indices = new String[]{ES_TREE_INDEX, + ES_CONFIGURATION_INDEX, + ES_SNAPSHOT_INDEX, + ES_COMPOSITE_SNAPSHOT_INDEX, + ES_FILTER_INDEX}; + } + /** * - * @return Information about the Save and Restore service + * @return Information about the Save and Restore service and Elasticsearch index metrics */ @GetMapping - public String info() { + public ServiceInfo info() { - Map saveRestoreServiceInfo = new LinkedHashMap<>(); - saveRestoreServiceInfo.put("name", name); - saveRestoreServiceInfo.put("version", version); + List indexMetrics = new ArrayList<>(indices.length); - Map elasticInfo = new LinkedHashMap<>(); try { - Request request = new Request("GET", "/"); + Request request; + for(String indexName : indices){ + request = new Request("GET", "/" + indexName + "/_stats"); + JsonNode docsNode = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()) + .path("indices").path(indexName).path("primaries").path("docs"); + int documentCount = docsNode.path("count").asInt(); + int deletedDocumentCount = docsNode.path("deleted").asInt(); + long indexSize = docsNode.path("total_size_in_bytes").asLong(); + indexMetrics.add(new IndexStats(indexName, documentCount, deletedDocumentCount, indexSize)); + } + request = new Request("GET", "/"); JsonNode response = objectMapper.readTree(restClient.performRequest(request).getEntity().getContent()); - elasticInfo.put("status", "Connected"); - elasticInfo.put("clusterName", response.path("cluster_name").asText("")); - elasticInfo.put("clusterUuid", response.path("cluster_uuid").asText("")); - elasticInfo.put("version", response.path("version").path("number").asText("")); - //elasticInfo.put("elasticHost", host); - //elasticInfo.put("elasticPort", String.valueOf(port)); + String elasticVersion = response.path("version").path("number").asString(); + ElasticInfo elasticInfo = new ElasticInfo(elasticVersion, indexMetrics); + return new ServiceInfo(version, serverRestoreAllowed, elasticInfo); } catch (IOException e) { - logger.log(Level.WARNING, "Failed to create Save and Restore service info resource.", e); - elasticInfo.put("status", "Failed to connect to elastic " + e.getLocalizedMessage()); - } - saveRestoreServiceInfo.put("elastic", elasticInfo); - - saveRestoreServiceInfo.put("rootNodeID", ROOT_FOLDER_UNIQUE_ID); - - saveRestoreServiceInfo.put("serverRestoreAllowed", serverRestoreAllowed); - - try { - return objectMapper.writeValueAsString(saveRestoreServiceInfo); - } catch (JacksonException e) { - logger.log(Level.WARNING, "Failed to create Save and Restore service info resource.", e); - return "Failed to gather Save and Restore service info"; + logger.log(Level.WARNING, "Failed to create query for Elasticsearch statistics.", e); + return new ServiceInfo(version, serverRestoreAllowed, new ElasticInfo()); } } }