/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * 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 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package org.codice.ddf.itests.common; import java.util.ArrayList; import java.util.Arrays; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang.ArrayUtils; import org.apache.karaf.features.FeaturesService; import org.osgi.framework.Constants; import org.osgi.service.cm.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class that holds the state of the system at some given point * and provides a way to get back to that state. */ public class SystemStateManager { private static final Logger LOGGER = LoggerFactory.getLogger(SystemStateManager.class); private static final int FEATURE_STOP_RETRY_COUNT = 3; private static final String CONFIGURATION_FILTER = "(" + Constants.SERVICE_PID + "=*)"; private static SystemStateManager instance; private List<String> baseFeatures; private Map<String, Configuration> baseConfigurations = new HashMap<>(); private ServiceManager serviceManager; private FeaturesService features; private KarafConsole console; private AdminConfig adminConfig; private boolean stateInitiallized = false; SystemStateManager(ServiceManager serviceManager, FeaturesService features, AdminConfig adminConfig, KarafConsole console) { this.serviceManager = serviceManager; this.features = features; this.adminConfig = adminConfig; this.console = console; } public static SystemStateManager getManager(ServiceManager serviceManager, FeaturesService features, AdminConfig adminConfig, KarafConsole console) { //not worried about threading here since the tests are called serially if (instance == null) { instance = new SystemStateManager(serviceManager, features, adminConfig, console); } return instance; } public void setSystemBaseState(Runnable runnable, boolean overwrite) throws Exception { if (overwrite || !stateInitiallized) { runnable.run(); captureSystemState(); stateInitiallized = true; } } public void waitForSystemBaseState() throws Exception { if (stateInitiallized) { resetSystem(); } else { throw new IllegalStateException("The base system state has not been set"); } } private void resetSystem() { LOGGER.info("Resetting system to base state"); try { long start = System.currentTimeMillis(); //reset the features List<String> currentFeatures = Arrays.stream(features.listInstalledFeatures()) .map(e -> e.getName()) .collect(Collectors.toList()); List<String> featuresToStart = baseFeatures.stream() .filter(e -> !currentFeatures.contains(e)) .collect(Collectors.toList()); List<String> featuresToStop = currentFeatures.stream() .filter(e -> !baseFeatures.contains(e)) .collect(Collectors.toList()); for (String feature : featuresToStart) { LOGGER.debug("Starting feature {}", feature); serviceManager.startFeature(false, feature); } serviceManager.waitForAllBundles(); //we try a couple of times here because some features might not stop the first time //due to dependencies List<String> stoppedFeatures = new ArrayList<>(); for (int i = 0; i < FEATURE_STOP_RETRY_COUNT; i++) { stoppedFeatures.clear(); for (String feature : featuresToStop) { LOGGER.debug("Stopping feature {}", feature); try { serviceManager.stopFeature(false, feature); stoppedFeatures.add(feature); } catch (Exception e) { LOGGER.debug("Failed to stop feature {}", feature); } } featuresToStop.removeAll(stoppedFeatures); } serviceManager.waitForAllBundles(); //reset the configurations Configuration[] configs = adminConfig.listConfigurations(CONFIGURATION_FILTER); Map<String, Configuration> currentConfigurations = new HashMap<>(); for (Configuration config : configs) { currentConfigurations.put(config.getPid(), config); } Map<String, Configuration> addedConfigs = new HashMap<>(currentConfigurations); addedConfigs.keySet() .removeAll(baseConfigurations.keySet()); for (Configuration config : addedConfigs.values()) { LOGGER.debug("Deleting configuration {}", config.getPid()); config.delete(); } Map<String, Configuration> removedConfigs = new HashMap<>(baseConfigurations); removedConfigs.keySet() .removeAll(currentConfigurations.keySet()); for (Configuration config : removedConfigs.values()) { LOGGER.debug("Adding configuration {}", config.getPid()); Configuration newConfig = adminConfig.getConfiguration(config.getPid(), null); newConfig.update(config.getProperties()); } for (Configuration config : baseConfigurations.values()) { if (currentConfigurations.containsKey(config.getPid()) && !propertiesMatch(config.getProperties(), currentConfigurations.get(config.getPid()) .getProperties())) { LOGGER.debug("Updating configuration {}", config.getPid()); config.update(baseConfigurations.get(config.getPid()) .getProperties()); } } serviceManager.waitForAllBundles(); //reset the catalog console.runCommand("catalog:removeall -f -p"); console.runCommand("catalog:removeall -f -p --cache"); console.runCommand( "catalog:import --provider --force --skip-signature-verification itest-catalog-entries.zip"); LOGGER.debug("Reset took {} sec", (System.currentTimeMillis() - start) / 1000.0); } catch (Exception e) { LOGGER.error("Error resetting system configuration.", e); } } private boolean propertiesMatch(Dictionary<String, Object> dictionary1, Dictionary<String, Object> dictionary2) { if (dictionary1.size() != dictionary2.size()) { return false; } Enumeration<String> keys = dictionary1.keys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); Object o = dictionary1.get(key); Object o1 = dictionary2.get(key); if (o.getClass() .isArray() && o1.getClass() .isArray()) { if (!ArrayUtils.isEquals(o, o1)) { return false; } } else if (!o.equals(o1)) { return false; } } return true; } private void captureSystemState() { LOGGER.info("Capturing system state"); try { baseFeatures = Arrays.stream(features.listInstalledFeatures()) .map(e -> e.getName()) .collect(Collectors.toList()); Configuration[] configs = adminConfig.listConfigurations(CONFIGURATION_FILTER); for (Configuration config : configs) { baseConfigurations.put(config.getPid(), config); } console.runCommand( "catalog:export --provider --force --skip-signature-verification --delete=false --output \"./itest-catalog-entries.zip\" --cql \"\\\"metacard-tags\\\" like '*'\""); LOGGER.info("Feature Count: {}", baseFeatures.size()); LOGGER.info("Configuration Count: {}", baseConfigurations.size()); } catch (Exception e) { LOGGER.error("Error capturing system configuration.", e); } } }