Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -275,4 +276,6 @@ public interface SaveAndRestoreClient {
* @return A {@link List} of {@link SnapshotItem}s carrying snapshot values read by the service.
*/
List<SnapshotItem> takeSnapshot(String configurationNodeId);

Map<String, String> getServerInfo();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -665,6 +666,12 @@ public List<SnapshotItem> takeSnapshot(String configurationNodeId) {
});
}

@Override
public Map<String, String> getServerInfo(){
return getCall("/", new TypeReference<>() {
});
}

private <T> T getCall(String relativeUrl, Class<T> clazz) {
HttpResponse<String> response = getCall(relativeUrl);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,6 @@ public Filter fromString(String s) {
saveAndRestoreService.setConnectCallback(this::handleWebSocketConnected);
saveAndRestoreService.setDisconnectCallback(this::handleWebSocketDisconnected);
saveAndRestoreService.connectWebSocket();

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -517,4 +518,7 @@ public void handleWebSocketMessage(String message) {
}
}

public Map<String, String> getServerInfo(){
return saveAndRestoreClient.getServerInfo();
}
}
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
@@ -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> indexStats) {
public ElasticInfo(){
this("Disconnected", "n/a", Collections.emptyList());
}
public ElasticInfo(String version, List<IndexStats> indexStats){
this("Connected", version, indexStats);
}
}
Original file line number Diff line number Diff line change
@@ -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) {
}
4 changes: 4 additions & 0 deletions services/save-and-restore/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""""""""""""""""""""""""""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Check warning on line 19 in services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "ES_TREE_INDEX" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaHVW8BW7_R4cNbqVg93&open=AaHVW8BW7_R4cNbqVg93&pullRequest=4021

@SuppressWarnings("unused")
@Value("${elasticsearch.configuration_node.index:saveandrestore_configuration}")
public String ES_CONFIGURATION_INDEX;

Check warning on line 23 in services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "ES_CONFIGURATION_INDEX" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaHVW8BW7_R4cNbqVg94&open=AaHVW8BW7_R4cNbqVg94&pullRequest=4021

@SuppressWarnings("unused")
@Value("${elasticsearch.snapshot_node.index:saveandrestore_snapshot}")
public String ES_SNAPSHOT_INDEX;

Check warning on line 27 in services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "ES_SNAPSHOT_INDEX" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaHVW8BW7_R4cNbqVg95&open=AaHVW8BW7_R4cNbqVg95&pullRequest=4021

@SuppressWarnings("unused")
@Value("${elasticsearch.composite_snapshot_node.index:saveandrestore_composite_snapshot}")
public String ES_COMPOSITE_SNAPSHOT_INDEX;

Check warning on line 31 in services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "ES_COMPOSITE_SNAPSHOT_INDEX" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaHVW8BW7_R4cNbqVg96&open=AaHVW8BW7_R4cNbqVg96&pullRequest=4021

@SuppressWarnings("unused")
@Value("${elasticsearch.filter.index:saveandrestore_filter}")
public String ES_FILTER_INDEX;

Check warning on line 35 in services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/persistence/config/IndexConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this field "ES_FILTER_INDEX" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaHVW8BW7_R4cNbqVg97&open=AaHVW8BW7_R4cNbqVg97&pullRequest=4021
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,14 +39,10 @@
* Repository for {@link ESTreeNode} objects.
*/
@Repository
public class ElasticsearchTreeRepository implements CrudRepository<ESTreeNode, String> {
public class ElasticsearchTreeRepository extends IndexConfig implements CrudRepository<ESTreeNode, String> {

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.
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -345,7 +341,7 @@ public SearchResult search(MultiValueMap<String, String> searchParameters) {
List<ESTreeNode> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -402,7 +394,7 @@
// Add remaining terms...
List<String> 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());

Check warning on line 397 in services/save-and-restore/src/main/java/org/phoebus/service/saveandrestore/search/SearchUtil.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "isEmpty()" to check whether a "String" is empty or not.

See more on https://sonarcloud.io/project/issues?id=ControlSystemStudio_phoebus&issues=AaHVW8Ek7_R4cNbqVg98&open=AaHVW8Ek7_R4cNbqVg98&pullRequest=4021
return terms;
}

Expand Down Expand Up @@ -437,7 +429,7 @@
}

// Only consider first node id value if multiple are specified
String nodeId = value.get(0);
String nodeId = value.getFirst();
int _from = from;
int _size = size;
MatchPhraseQuery matchPhraseQuery = MatchPhraseQuery.of(m ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,7 +40,7 @@
*/
@RestController
@SuppressWarnings("unused")
public abstract class BaseController {
public abstract class BaseController extends IndexConfig {

protected static final String JSON = "application/json";

Expand Down
Loading
Loading