From c9f2863e27323e54788f483ba8db14ddcdebd5b9 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 7 Jul 2015 16:38:56 -0400 Subject: [PATCH] Refactored service monitor to perform on-demand checks when service status is unknown --- .../autopsy/core/ServicesMonitor.java | 242 +++++++++++------- 1 file changed, 150 insertions(+), 92 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java b/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java index b054672ccf..d2e157e9a1 100644 --- a/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/core/ServicesMonitor.java @@ -38,7 +38,6 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; -import org.sleuthkit.datamodel.CaseDbConnectionInfo; /** * This class periodically checks availability of collaboration resources - @@ -59,52 +58,55 @@ public class ServicesMonitor { private static final Set serviceNames = Stream.of(ServicesMonitor.ServiceName.values()) .map(ServicesMonitor.ServiceName::toString) .collect(Collectors.toSet()); - - /** - * The service monitor maintains a mapping of each service to it's last status update. - */ - private final ConcurrentHashMap statusByService; /** - * List of services that are being monitored. The service names should be - * representative of the service functionality and readable as they get + * The service monitor maintains a mapping of each service to it's last + * status update. + */ + private final ConcurrentHashMap statusByService; + + /** + * List of services that are being monitored. The service names should be + * representative of the service functionality and readable as they get * logged when service outage occurs. */ public enum ServiceName { /** - * Property change event fired when remote case database status changes. + * Property change event fired when remote case database status changes. * New value is set to updated ServiceStatus, old value is null. */ REMOTE_CASE_DATABASE, - /** - * Property change event fired when remote keyword search service status changes. - * New value is set to updated ServiceStatus, old value is null. - */ + * Property change event fired when remote keyword search service status + * changes. New value is set to updated ServiceStatus, old value is + * null. + */ REMOTE_KEYWORD_SEARCH, - /** * Property change event fired when messaging service status changes. * New value is set to updated ServiceStatus, old value is null. - */ + */ MESSAGING }; /** * List of possible service statuses. - */ + */ public enum ServiceStatus { /** * Service is currently up. */ UP, - /** * Service is currently down. */ - DOWN + DOWN, + /** + * Service status is unknown. + */ + UNKNOWN, }; public synchronized static ServicesMonitor getInstance() { @@ -118,10 +120,8 @@ public class ServicesMonitor { this.eventPublisher = new AutopsyEventPublisher(); this.statusByService = new ConcurrentHashMap<>(); - - // Services are assumed to be "UP" by default. See CrashDetectionTask class for more info. for (String serviceName : serviceNames) { - this.statusByService.put(serviceName, ServiceStatus.UP.toString()); + this.statusByService.put(serviceName, ServiceStatus.UNKNOWN.toString()); } /** @@ -131,42 +131,86 @@ public class ServicesMonitor { periodicTasksExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_PERIODIC_TASK_THREADS, new ThreadFactoryBuilder().setNameFormat(PERIODIC_TASK_THREAD_NAME).build()); periodicTasksExecutor.scheduleAtFixedRate(new CrashDetectionTask(), CRASH_DETECTION_INTERVAL_MINUTES, CRASH_DETECTION_INTERVAL_MINUTES, TimeUnit.MINUTES); } - + /** - * Store and publish service status update. - * + * Store and publish service status update. + * * @param service Name of the service. * @param status Updated status for the service. */ - private void setServiceStatus(String service, String status){ + private void setServiceStatus(String service, String status) { this.statusByService.put(service, status); publishServiceStatusUpdate(service, status); } - + /** * Get last status update for a service. - * + * * @param service Name of the service. * @return ServiceStatus Status for the service. * @throws org.sleuthkit.autopsy.core.UnknownServiceException */ public String getServiceStatus(String service) throws UnknownServiceException { - - if (service == null){ + + if (service == null) { // TODO NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir")); throw new UnknownServiceException("Requested service name is null"); } - + String status = this.statusByService.get(service); - if (status == null){ + if (status == null) { // no such service throw new UnknownServiceException("Requested service name " + service + " is unknown"); + + } else if (status.equals(ServiceStatus.UNKNOWN.toString())) { + // status for the service is not known. This is likely because we haven't + // checked it's status yet. Perform an on-demand check of the service status. + status = checkServiceStatusStatus(service); } return status; } - + /** - * Publish an event signifying change in service status. Event is published locally. + * Performs on-demand check of service availability. + * + * @param service Name of the service. + * @return String Status for the service. + */ + private String checkServiceStatusStatus(String service) { + + if (service.equals(ServiceName.REMOTE_CASE_DATABASE.toString())) { + if (canConnectToRemoteDb()) { + setServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString(), ServiceStatus.UP.toString()); + return ServiceStatus.UP.toString(); + } else { + setServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString(), ServiceStatus.DOWN.toString()); + return ServiceStatus.DOWN.toString(); + } + } else if (service.equals(ServiceName.REMOTE_KEYWORD_SEARCH.toString())){ + KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class); + // TODO - do I need to check for kwsService == null? + if (kwsService.canConnectToRemoteSolrServer()) { + setServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.UP.toString()); + return ServiceStatus.UP.toString(); + } else { + setServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.DOWN.toString()); + return ServiceStatus.DOWN.toString(); + } + } else if (service.equals(ServiceName.MESSAGING.toString())) { + if (canConnectToMessagingService()) { + setServiceStatus(ServiceName.MESSAGING.toString(), ServiceStatus.UP.toString()); + return ServiceStatus.UP.toString(); + } else { + setServiceStatus(ServiceName.MESSAGING.toString(), ServiceStatus.DOWN.toString()); + return ServiceStatus.DOWN.toString(); + } + } + return ServiceStatus.UNKNOWN.toString(); + } + + /** + * Publish an event signifying change in service status. Event is published + * locally. * * @param service Name of the service. * @param status Updated status for the event. @@ -184,7 +228,7 @@ public class ServicesMonitor { */ public void publishCustomServiceStatus(String service, String status, String details) { eventPublisher.publishLocally(new ServiceEvent(service, status, details)); - } + } /** * Adds an event subscriber to this publisher. Subscriber will be subscribed @@ -246,20 +290,41 @@ public class ServicesMonitor { eventPublisher.removeSubscriber(serviceNames, subscriber); } + /** + * Verifies connection to remote database. + * + * @return True if connection can be established, false otherwise. + */ + private boolean canConnectToRemoteDb() { + return UserPreferences.getDatabaseConnectionInfo().canConnect(); + } + + /** + * Verifies connection to messaging service. + * + * @return True if connection can be established, false otherwise. + */ + private boolean canConnectToMessagingService() { + MessageServiceConnectionInfo msgInfo = UserPreferences.getMessageServiceConnectionInfo(); + try { + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(msgInfo.getUserName(), msgInfo.getPassword(), msgInfo.getURI()); + Connection connection = connectionFactory.createConnection(); + connection.start(); + connection.close(); + return true; + } catch (URISyntaxException | JMSException ex) { + return false; + } + } + /** * A Runnable task that periodically checks the availability of - * collaboration resources (PostgreSQL server, Solr server, Active MQ + * collaboration resources (remote database, remote keyword search service, * message broker) and reports status to the user in case of a gap in * service. */ private final class CrashDetectionTask implements Runnable { - // Services are assumed to be "UP" by default. Change default value in ServicesMonitor() - // constructor if this assumption changes. - private boolean dbServerIsRunning = true; - private boolean solrServerIsRunning = true; - private boolean messageServerIsRunning = true; - private final Object lock = new Object(); /** @@ -268,60 +333,53 @@ public class ServicesMonitor { @Override public void run() { synchronized (lock) { - CaseDbConnectionInfo dbInfo = UserPreferences.getDatabaseConnectionInfo(); - if (dbInfo.canConnect()) { - if (!dbServerIsRunning) { - dbServerIsRunning = true; - logger.log(Level.INFO, "Connection to PostgreSQL server restored"); //NON-NLS - MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredDbService.notify.msg")); - setServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString(), ServiceStatus.UP.toString()); - } - } else { - if (dbServerIsRunning) { - dbServerIsRunning = false; - logger.log(Level.SEVERE, "Failed to connect to PostgreSQL server"); //NON-NLS - MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedDbService.notify.msg")); - setServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString(), ServiceStatus.DOWN.toString()); - } - } - - KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class); - // TODO - do I need to check for kwsService == null? - if (kwsService.canConnectToRemoteSolrServer()) { - if (!solrServerIsRunning) { - solrServerIsRunning = true; - logger.log(Level.INFO, "Connection to Solr server restored"); //NON-NLS - MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredSolrService.notify.msg")); - setServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.UP.toString()); - } - } else { - if (solrServerIsRunning) { - solrServerIsRunning = false; - logger.log(Level.SEVERE, "Failed to connect to Solr server"); //NON-NLS - MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedSolrService.notify.msg")); - setServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.DOWN.toString()); - } - } - - MessageServiceConnectionInfo msgInfo = UserPreferences.getMessageServiceConnectionInfo(); try { - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(msgInfo.getUserName(), msgInfo.getPassword(), msgInfo.getURI()); - Connection connection = connectionFactory.createConnection(); - connection.start(); - connection.close(); - if (!messageServerIsRunning) { - messageServerIsRunning = true; - logger.log(Level.INFO, "Connection to ActiveMQ server restored"); //NON-NLS - MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredMessageService.notify.msg")); - setServiceStatus(ServiceName.MESSAGING.toString(), ServiceStatus.UP.toString()); + if (canConnectToRemoteDb()) { + if (!getServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString()).equals(ServiceStatus.UP.toString())) { + logger.log(Level.INFO, "Connection to PostgreSQL server restored"); //NON-NLS + MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredDbService.notify.msg")); + setServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString(), ServiceStatus.UP.toString()); + } + } else { + if (!getServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString()).equals(ServiceStatus.DOWN.toString())) { + logger.log(Level.SEVERE, "Failed to connect to PostgreSQL server"); //NON-NLS + MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedDbService.notify.msg")); + setServiceStatus(ServiceName.REMOTE_CASE_DATABASE.toString(), ServiceStatus.DOWN.toString()); + } } - } catch (URISyntaxException | JMSException ex) { - if (messageServerIsRunning) { - messageServerIsRunning = false; - logger.log(Level.SEVERE, "Failed to connect to ActiveMQ server", ex); //NON-NLS - MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedMessageService.notify.msg")); - setServiceStatus(ServiceName.MESSAGING.toString(), ServiceStatus.DOWN.toString()); + + KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class); + // TODO - do I need to check for kwsService == null? + if (kwsService.canConnectToRemoteSolrServer()) { + if (!getServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString()).equals(ServiceStatus.UP.toString())) { + logger.log(Level.INFO, "Connection to Solr server restored"); //NON-NLS + MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredSolrService.notify.msg")); + setServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.UP.toString()); + } + } else { + if (!getServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString()).equals(ServiceStatus.DOWN.toString())) { + logger.log(Level.SEVERE, "Failed to connect to Solr server"); //NON-NLS + MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedSolrService.notify.msg")); + setServiceStatus(ServiceName.REMOTE_KEYWORD_SEARCH.toString(), ServiceStatus.DOWN.toString()); + } } + + if (canConnectToMessagingService()) { + if (!getServiceStatus(ServiceName.MESSAGING.toString()).equals(ServiceStatus.UP.toString())) { + logger.log(Level.INFO, "Connection to ActiveMQ server restored"); //NON-NLS + MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredMessageService.notify.msg")); + setServiceStatus(ServiceName.MESSAGING.toString(), ServiceStatus.UP.toString()); + } + } else { + if (!getServiceStatus(ServiceName.MESSAGING.toString()).equals(ServiceStatus.DOWN.toString())) { + logger.log(Level.SEVERE, "Failed to connect to ActiveMQ server"); //NON-NLS + MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"), NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedMessageService.notify.msg")); + setServiceStatus(ServiceName.MESSAGING.toString(), ServiceStatus.DOWN.toString()); + } + } + + } catch (UnknownServiceException ex) { + logger.log(Level.SEVERE, "Exception while checking current service status", ex); //NON-NLS } } }