Java tutorial
/** * Copyright 2014 SeaClouds * Contact: SeaClouds * * Licensed 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 eu.seaclouds.common.compose.apps; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import java.io.File; import java.io.Reader; import java.io.StringReader; import java.net.URI; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import brooklyn.catalog.CatalogLoadMode; import brooklyn.config.BrooklynProperties; import brooklyn.config.BrooklynServerConfig; import brooklyn.entity.Application; import brooklyn.entity.Entity; import brooklyn.entity.basic.Attributes; import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.EntityLocal; import brooklyn.entity.basic.Lifecycle; import brooklyn.entity.basic.SoftwareProcess; import brooklyn.entity.basic.SoftwareProcess.RestartSoftwareParameters.RestartMachineMode; import brooklyn.entity.basic.SoftwareProcess.StopSoftwareParameters.StopMode; import brooklyn.entity.rebind.RebindOptions; import brooklyn.entity.rebind.RebindTestUtils; import brooklyn.entity.rebind.persister.FileBasedObjectStore; import brooklyn.entity.trait.Startable; import brooklyn.event.AttributeSensor; import brooklyn.launcher.BrooklynLauncher; import brooklyn.launcher.SimpleYamlLauncherForTests; import brooklyn.launcher.camp.BrooklynCampPlatformLauncher; import brooklyn.location.Location; import brooklyn.management.ManagementContext; import brooklyn.management.Task; import brooklyn.management.internal.LocalManagementContext; import brooklyn.test.Asserts; import brooklyn.test.EntityTestUtils; import brooklyn.util.ResourceUtils; import brooklyn.util.exceptions.Exceptions; import brooklyn.util.http.HttpTool; import brooklyn.util.http.HttpToolResponse; import brooklyn.util.net.Urls; import brooklyn.util.os.Os; import brooklyn.util.yaml.Yamls; import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract; public abstract class AbstractSeaCloudsAppTest { private static final Logger LOG = LoggerFactory.getLogger(AbstractSeaCloudsAppTest.class); private File mementoDir; private ClassLoader classLoader = AbstractSeaCloudsAppTest.class.getClassLoader(); private ManagementContext mgmt; private SimpleYamlLauncherForTests launcher; private BrooklynLauncher viewer; private ExecutorService executor; @BeforeMethod(alwaysRun = true) public void setUp() throws Exception { mementoDir = Os.newTempDir(getClass()); mgmt = createOrigManagementContext(); LOG.info("Test " + getClass() + " persisting to " + mementoDir); launcher = new SimpleYamlLauncherForTests() { @Override protected BrooklynCampPlatformLauncherAbstract newPlatformLauncher() { return new BrooklynCampPlatformLauncher() { protected ManagementContext newManagementContext() { return AbstractSeaCloudsAppTest.this.mgmt; } }; } }; viewer = BrooklynLauncher.newInstance().managementContext(mgmt).start(); executor = Executors.newCachedThreadPool(); } @AfterMethod(alwaysRun = true) public void tearDown() throws Exception { try { if (mgmt != null) { for (Application app : mgmt.getApplications()) { LOG.debug("destroying app " + app + " (managed? " + Entities.isManaged(app) + "; mgmt is " + mgmt + ")"); try { Entities.destroy(app); LOG.debug("destroyed app " + app + "; mgmt now " + mgmt); } catch (Exception e) { LOG.error("problems destroying app " + app, e); } } } if (launcher != null) launcher.destroyAll(); if (viewer != null) viewer.terminate(); if (mgmt != null) Entities.destroyAll(mgmt); if (mementoDir != null) FileBasedObjectStore.deleteCompletely(mementoDir); } catch (Throwable t) { LOG.error("Caught exception in tearDown method", t); } finally { if (executor != null) executor.shutdownNow(); executor = null; mgmt = null; launcher = null; } } protected abstract String getLocationSpec(); protected Map<String, ?> getBrooklynProperties() { return ImmutableMap.of(); } protected Map<String, String> getCustomBlueprintConfig() { return ImmutableMap.of(); } protected Map<String, String> getCustomLocationConfig() { return ImmutableMap.of(); } protected void runTest(String url, Class<? extends SoftwareProcess> expectedType, AttributeSensor<String> endpoint, boolean expectsSubnetTier) throws Exception { runTest(url, Predicates.instanceOf(expectedType), endpoint, expectsSubnetTier); } protected void runTest(String url, Predicate<? super Entity> typePredicate, AttributeSensor<String> endpoint, boolean expectsSubnetTier) throws Exception { String catalogId = addToCatalog(url); // Generate the yaml blueprint Map<String, String> blueprintConfig = getCustomBlueprintConfig(); Map<String, String> locationConfig = getCustomLocationConfig(); StringBuilder yaml = new StringBuilder(); if (locationConfig.size() > 0) { yaml.append("location: \n" + " " + getLocationSpec() + ":\n"); for (Map.Entry<String, String> entry : locationConfig.entrySet()) { yaml.append(" " + entry.getKey() + ": " + entry.getValue() + "\n"); } } else { yaml.append("location: " + getLocationSpec() + "\n"); } yaml.append("services: \n" + "- type: " + catalogId + "\n"); if (blueprintConfig.size() > 0) { yaml.append(" brooklyn.config:" + "\n"); for (Map.Entry<String, String> entry : blueprintConfig.entrySet()) { yaml.append(" " + entry.getKey() + ": " + entry.getValue() + "\n"); } } // Deploy the blueprint LOG.info("Deploying YAML:\n" + yaml); Application app = launcher.launchAppYaml(new StringReader(yaml.toString())); // Confirm has expected entity/structure Entity entity = Iterables.getOnlyElement(app.getChildren()); assertTrue(typePredicate.apply(entity), "entity=" + entity); // Confirm healthy, and operations work assertNoFires(app); for (Entity softwareProcess : Entities.descendants(entity, Predicates.instanceOf(SoftwareProcess.class), true)) { assertCanRestartProcess((SoftwareProcess) softwareProcess); } // Rebind, and find new entity instances Application newApp = rebind(); Entity newEntity = Iterables.getOnlyElement(newApp.getChildren()); // Confirm still healthy, and operations still work assertNoFires(newApp); for (Entity softwareProcess : Entities.descendants(newEntity, Predicates.instanceOf(SoftwareProcess.class), true)) { assertCanRestartProcess((SoftwareProcess) softwareProcess); } } protected void runTestConcurrentDeploys(Map<String, Integer> urls) throws Exception { runTestConcurrentDeploys(1, urls); } protected void runTestConcurrentDeploys(int numCycles, Map<String, Integer> urls) throws Exception { Map<String, String> urlToCatalogId = Maps.newLinkedHashMap(); for (String url : urls.keySet()) { String catalogId = addToCatalog(url); urlToCatalogId.put(url, catalogId); } for (int cycle = 0; cycle < numCycles; cycle++) { List<Future<Application>> futures = Lists.newArrayList(); for (Map.Entry<String, Integer> entry : urls.entrySet()) { String catalogId = urlToCatalogId.get(entry.getKey()); int num = entry.getValue(); for (int i = 0; i < num; i++) { // Generate the yaml blueprint final StringBuilder yaml = new StringBuilder(); Map<String, String> blueprintConfig = getCustomBlueprintConfig(); Map<String, String> locationConfig = getCustomLocationConfig(); if (locationConfig.size() > 0) { yaml.append("location: \n" + " " + getLocationSpec() + ":\n"); for (Map.Entry<String, String> entry2 : locationConfig.entrySet()) { yaml.append(" " + entry2.getKey() + ": " + entry2.getValue() + "\n"); } } else { yaml.append("location: " + getLocationSpec() + "\n"); } yaml.append("services: \n" + "- type: " + catalogId + "\n"); if (blueprintConfig.size() > 0) { yaml.append(" brooklyn.config:" + "\n"); for (Map.Entry<String, String> entry2 : blueprintConfig.entrySet()) { yaml.append(" " + entry2.getKey() + ": " + entry2.getValue() + "\n"); } } // Deploy the blueprint Future<Application> future = executor.submit(new Callable<Application>() { public Application call() { LOG.info("Deploying YAML:\n" + yaml); return launcher.launchAppYaml(new StringReader(yaml.toString())); } }); futures.add(future); } } // Confirm all started without error for (Future<Application> future : futures) { Application app = future.get(); assertNoFires(app); } } // Confirm can rebind, and that nothing goes/stays on fire after rebind Application newApp = rebind(); Collection<Application> newApps = newApp.getManagementContext().getApplications(); // Check apps all healthy; // TODO simplify code again (i.e. remove try-catch) when confident not having transient errors after rebind try { for (Application app : newApps) { assertNoFires(app); } } catch (Throwable t) { // Try again, in case it's a transient error LOG.error("App(s) on fire after rebind; waiting 60 seconds and checking again", t); Thread.sleep(60 * 1000); try { for (Application app : newApps) { assertNoFires(app); } LOG.error("App(s) on fire after rebind; waiting 60 seconds and checking again", t); throw new RuntimeException("App(s) were temporarily on fire, but recovered after 60 seconds", t); } catch (Throwable t2) { LOG.error("App(s) still on fire 60 seconds after rebind; throwing original exception", t2); throw Exceptions.propagate(t); } } // Confirm can stop all the apps for (Application app : newApps) { ((Startable) app).stop(); } } protected String addToCatalog(String yamlUri) { String baseUri = viewer.getServerDetails().getWebServer().getRootUrl(); URI uri = URI.create(Urls.mergePaths(baseUri, "/v1/catalog/")); String yaml = ResourceUtils.create(this).getResourceAsString(yamlUri); // TODO Should really get symbolicName + version from the returned HTTP response Iterable<Object> parsedYaml = Yamls.parseAll(yaml); Object catalogSection = ((Map<?, ?>) Iterables.get(parsedYaml, 0)).get("brooklyn.catalog"); String catalogId = (String) ((Map<?, ?>) catalogSection).get("id"); String iconUrl = (String) ((Map<?, ?>) catalogSection).get("iconUrl"); Double version = (Double) ((Map<?, ?>) catalogSection).get("version"); String catalogRef = catalogId + ":" + Double.toString(version); assertNotNull(catalogId); assertTrue(ResourceUtils.create(this).doesUrlExist(iconUrl), "url=" + iconUrl); HttpClient httpClient = HttpTool.httpClientBuilder() .credentials(new UsernamePasswordCredentials("admin", "password")).uri(uri).build(); HttpToolResponse resp = HttpTool.httpPost(httpClient, uri, ImmutableMap.<String, String>of(), yaml.getBytes()); assertEquals(resp.getResponseCode(), 201); LOG.info("Added to catalog: " + resp.getContentAsString()); // TODO Could/should return id:version, but that is currently breaking rebind. // Need to investigate further. return catalogId; } protected void assertNoFires(final Entity app) { EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); EntityTestUtils.assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); Asserts.succeedsEventually(new Runnable() { public void run() { for (Entity entity : Entities.descendants(app)) { assertNotEquals(entity.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE); assertNotEquals(entity.getAttribute(Attributes.SERVICE_UP), false); if (entity instanceof SoftwareProcess) { EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); EntityTestUtils.assertAttributeEquals(entity, Attributes.SERVICE_UP, Boolean.TRUE); } } } }); } protected void assertCanRestartProcess(final SoftwareProcess entity) throws Exception { Collection<Location> origLocs = entity.getLocations(); String origHostname = entity.getAttribute(SoftwareProcess.HOSTNAME); // Stop the process Task<?> stopTask = Entities.invokeEffector((EntityLocal) entity, entity, SoftwareProcess.STOP, ImmutableMap.of(SoftwareProcess.StopSoftwareParameters.STOP_MACHINE_MODE.getName(), StopMode.NEVER, SoftwareProcess.StopSoftwareParameters.STOP_PROCESS_MODE.getName(), StopMode.ALWAYS)); stopTask.get(); EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_UP, false); EntityTestUtils.assertAttributeEqualsEventually(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.STOPPED); // Re-start the process - expect the same set of locations and IPs Task<?> restartTask = Entities.invokeEffector((EntityLocal) entity, entity, SoftwareProcess.RESTART, ImmutableMap.of(SoftwareProcess.RestartSoftwareParameters.RESTART_MACHINE.getName(), RestartMachineMode.FALSE)); restartTask.get(); assertNoFires(entity); assertEquals(entity.getLocations(), origLocs); assertEquals(entity.getAttribute(SoftwareProcess.HOSTNAME), origHostname); } protected Reader loadYaml(String url, String location) { String yaml = "location: " + location + "\n" + new ResourceUtils(this).getResourceAsString(url); return new StringReader(yaml); } ////////////////////////////////////////////////////////////////// // FOR REBIND // // See brooklyn.entity.rebind.RebindTestFixture in core's tests // ////////////////////////////////////////////////////////////////// /** * rebinds, and sets newApp */ protected Application rebind() throws Exception { return rebind(RebindOptions.create()); } protected Application rebind(RebindOptions options) throws Exception { ManagementContext origMgmt = mgmt; ManagementContext newMgmt = createNewManagementContext(); Collection<Application> origApps = origMgmt.getApplications(); options = RebindOptions.create(options); if (options.classLoader == null) options.classLoader(classLoader); if (options.mementoDir == null) options.mementoDir(mementoDir); if (options.origManagementContext == null) options.origManagementContext(origMgmt); if (options.newManagementContext == null) options.newManagementContext(newMgmt); for (Application origApp : origApps) { RebindTestUtils.waitForPersisted(origApp); } mgmt = options.newManagementContext; Application newApp = RebindTestUtils.rebind(options); if (launcher != null) { launcher = new SimpleYamlLauncherForTests() { @Override protected BrooklynCampPlatformLauncherAbstract newPlatformLauncher() { return new BrooklynCampPlatformLauncher() { protected ManagementContext newManagementContext() { return AbstractSeaCloudsAppTest.this.mgmt; } }; } }; } if (viewer != null) { viewer.terminate(); viewer = BrooklynLauncher.newInstance().managementContext(mgmt).start(); } return newApp; } /** * @return A started management context */ protected LocalManagementContext createOrigManagementContext() { BrooklynProperties properties = BrooklynProperties.Factory.newDefault(); properties.put(BrooklynServerConfig.CATALOG_LOAD_MODE, CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL); properties.putAll(getBrooklynProperties()); return RebindTestUtils.managementContextBuilder(mementoDir, classLoader).properties(properties) .persistPeriodMillis(1).forLive(true).emptyCatalog(true).buildStarted(); } /** * @return An unstarted management context */ protected LocalManagementContext createNewManagementContext() { BrooklynProperties properties = BrooklynProperties.Factory.newDefault(); properties.put(BrooklynServerConfig.CATALOG_LOAD_MODE, CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL_IF_NO_PERSISTED_STATE); return RebindTestUtils.managementContextBuilder(mementoDir, classLoader).properties(properties) .persistPeriodMillis(1).forLive(true).emptyCatalog(true).buildUnstarted(); } }