This is an automated email from the ASF dual-hosted git repository. yasith pushed a commit to branch AIRAVATA-3981/integration-health-check in repository https://gitbox.apache.org/repos/asf/airavata.git
commit 2e94e32ae061939515b451976e72f7873774d195 Author: yasithdev <[email protected]> AuthorDate: Thu Mar 26 15:27:30 2026 -0500 feat: add ServiceRegistry and ServiceStatus for background service tracking --- .../patform/monitoring/ServiceRegistry.java | 149 +++++++++++++++++++++ .../airavata/patform/monitoring/ServiceStatus.java | 62 +++++++++ 2 files changed, 211 insertions(+) diff --git a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java new file mode 100644 index 0000000000..cce0eeb2c2 --- /dev/null +++ b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceRegistry.java @@ -0,0 +1,149 @@ +/** +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +package org.apache.airavata.patform.monitoring; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.apache.airavata.common.utils.IServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tracks background {@link IServer} instances and their threads, exposing + * lifecycle operations (status query, restart, stop-all) and error recording. + */ +public class ServiceRegistry { + + private static final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class); + + private static class Entry { + final IServer server; + Thread thread; + final Supplier<IServer> factory; + final long startedAt; + String lastError; + + Entry(IServer server, Thread thread, Supplier<IServer> factory) { + this.server = server; + this.thread = thread; + this.factory = factory; + this.startedAt = System.currentTimeMillis(); + } + } + + private final Map<String, Entry> entries = new LinkedHashMap<>(); + + /** + * Registers a service with an optional restart factory. + * + * @param label human-readable service identifier + * @param service the IServer instance + * @param thread the thread running the service + * @param factory optional supplier to create a fresh IServer for restart; may be null + */ + public synchronized void register(String label, IServer service, Thread thread, Supplier<IServer> factory) { + entries.put(label, new Entry(service, thread, factory)); + } + + /** + * Registers a service without a restart factory. + */ + public synchronized void register(String label, IServer service, Thread thread) { + register(label, service, thread, null); + } + + /** + * Returns a snapshot of statuses keyed by label. + * Status is {@code "UP"} when the thread is alive, {@code "DOWN"} otherwise. + */ + public synchronized Map<String, ServiceStatus> getStatuses() { + Map<String, ServiceStatus> result = new LinkedHashMap<>(); + for (Map.Entry<String, Entry> e : entries.entrySet()) { + Entry entry = e.getValue(); + String status = entry.thread.isAlive() ? "UP" : "DOWN"; + long uptimeMs = entry.thread.isAlive() ? System.currentTimeMillis() - entry.startedAt : 0L; + result.put(e.getKey(), new ServiceStatus(status, uptimeMs, entry.lastError)); + } + return result; + } + + /** + * Attempts to restart the named service using its registered factory. + * + * @param name the service label + * @throws IllegalArgumentException if no service with that name exists + * @throws IllegalStateException if the service has no restart factory + * @throws Exception if the new service thread fails to start + */ + public synchronized void restart(String name) throws Exception { + Entry entry = entries.get(name); + if (entry == null) { + throw new IllegalArgumentException("No service registered with name: " + name); + } + if (entry.factory == null) { + throw new IllegalStateException("Service '" + name + "' has no restart factory"); + } + // Stop the old instance + try { + entry.server.stop(); + } catch (Exception e) { + logger.warn("Error stopping '{}' before restart: {}", name, e.getMessage()); + } + entry.thread.interrupt(); + + // Start a fresh instance + IServer newServer = entry.factory.get(); + Thread newThread = new Thread(newServer, "airavata-" + name); + newThread.setDaemon(true); + newThread.start(); + + Entry newEntry = new Entry(newServer, newThread, entry.factory); + entries.put(name, newEntry); + logger.info("Service '{}' restarted", name); + } + + /** + * Records an error message against the named service. + * Silently ignores unknown names. + */ + public synchronized void recordError(String name, String errorMessage) { + Entry entry = entries.get(name); + if (entry != null) { + entry.lastError = errorMessage; + } + } + + /** + * Stops all registered services and interrupts their threads. + */ + public synchronized void stopAll() { + for (Map.Entry<String, Entry> e : entries.entrySet()) { + String label = e.getKey(); + Entry entry = e.getValue(); + try { + entry.server.stop(); + } catch (Exception ex) { + logger.warn("Error stopping '{}': {}", label, ex.getMessage()); + } + entry.thread.interrupt(); + } + } +} diff --git a/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java new file mode 100644 index 0000000000..9d1b48854a --- /dev/null +++ b/airavata-api/src/main/java/org/apache/airavata/patform/monitoring/ServiceStatus.java @@ -0,0 +1,62 @@ +/** +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ +package org.apache.airavata.patform.monitoring; + +/** + * DTO representing the runtime status of a single background service. + */ +public class ServiceStatus { + + private String status; + private long uptimeMs; + private String lastError; + + public ServiceStatus() {} + + public ServiceStatus(String status, long uptimeMs, String lastError) { + this.status = status; + this.uptimeMs = uptimeMs; + this.lastError = lastError; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public long getUptimeMs() { + return uptimeMs; + } + + public void setUptimeMs(long uptimeMs) { + this.uptimeMs = uptimeMs; + } + + public String getLastError() { + return lastError; + } + + public void setLastError(String lastError) { + this.lastError = lastError; + } +}
