| /* |
| * 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.brooklyn.launcher.blueprints; |
| |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| |
| import org.apache.brooklyn.api.entity.EntitySpec; |
| import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode; |
| import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatform; |
| import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; |
| import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer; |
| import org.apache.brooklyn.camp.spi.PlatformRootSummary; |
| import org.apache.brooklyn.core.entity.trait.Startable; |
| import org.apache.brooklyn.core.mgmt.EntityManagementUtils; |
| import org.apache.brooklyn.core.mgmt.persist.PersistMode; |
| import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl; |
| import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.collections.MutableSet; |
| import static org.testng.Assert.assertNotEquals; |
| import static org.testng.Assert.assertTrue; |
| |
| import java.io.File; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.util.Collection; |
| |
| import org.apache.brooklyn.api.catalog.CatalogItem; |
| import org.apache.brooklyn.api.entity.Application; |
| import org.apache.brooklyn.api.entity.Entity; |
| import org.apache.brooklyn.api.mgmt.ManagementContext; |
| import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract; |
| import org.apache.brooklyn.core.entity.Attributes; |
| import org.apache.brooklyn.core.entity.Entities; |
| import org.apache.brooklyn.core.entity.EntityAsserts; |
| import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; |
| import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext; |
| import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore; |
| import org.apache.brooklyn.core.mgmt.rebind.RebindOptions; |
| import org.apache.brooklyn.core.mgmt.rebind.RebindTestUtils; |
| import org.apache.brooklyn.entity.software.base.SoftwareProcess; |
| import org.apache.brooklyn.launcher.BrooklynViewerLauncher; |
| import org.apache.brooklyn.launcher.SimpleYamlLauncherForTests; |
| import org.apache.brooklyn.test.Asserts; |
| import org.apache.brooklyn.util.core.ResourceUtils; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.os.Os; |
| import org.apache.brooklyn.util.stream.Streams; |
| import org.apache.brooklyn.util.text.Strings; |
| 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; |
| |
| public abstract class AbstractBlueprintTest { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(AbstractBlueprintTest.class); |
| |
| protected File mementoDir; |
| protected ClassLoader classLoader = AbstractBlueprintTest.class.getClassLoader(); |
| |
| protected ManagementContext mgmt; |
| protected SimpleYamlLauncherForTests launcher; |
| protected Set<BrooklynViewerLauncher> viewers = MutableSet.of(); |
| |
| @BeforeMethod(alwaysRun=true) |
| public void setUp() throws Exception { |
| mementoDir = Os.newTempDir(getClass()); |
| mgmt = createOrigManagementContext(); |
| |
| // required for REST access - otherwise it is viewed as not yet ready |
| ((RebindManagerImpl)mgmt.getRebindManager()).setAwaitingInitialRebind(false); |
| |
| LOG.info("Test "+getClass()+" persisting to "+mementoDir); |
| |
| startViewer(true); |
| |
| launcher = new SimpleYamlLauncherForTests() { |
| @Override |
| protected BrooklynCampPlatformLauncherAbstract newPlatformLauncher() { |
| return new BrooklynCampPlatformLauncherNoServer() { |
| @Override |
| public BrooklynCampPlatformLauncherAbstract launch() { |
| useManagementContext(AbstractBlueprintTest.this.mgmt); |
| return super.launch(); |
| } |
| }; |
| } |
| }; |
| } |
| |
| /** starts a REST API to explore details; a UI can be created pointing at this API to view details graphically. |
| * typical usage is for a thread-only breakpoint to be set at the point in the test where you want to explore. */ |
| protected void startViewer(boolean killCurrent) { |
| if (isViewerEnabled()) { |
| if (killCurrent) { |
| // typically we kill the old and restart on the same port during rebind; |
| // the old mgmt context is no longer active so isn't useful; |
| // but if we wanted to have multiple viewers we could |
| stopAllViewers(); |
| } |
| |
| BrooklynViewerLauncher viewer = BrooklynViewerLauncher.newInstance(); |
| synchronized (viewers) { |
| viewers.add(viewer); |
| } |
| |
| viewer.managementContext(mgmt); |
| |
| // other persistence options come from mgmt console but launcher needs to know this: |
| viewer.persistMode(PersistMode.AUTO); |
| |
| viewer.start(); |
| } |
| } |
| |
| protected void stopAllViewers() { |
| synchronized (viewers) { |
| viewers.forEach(BrooklynViewerLauncher::terminate); |
| viewers.clear(); |
| } |
| } |
| |
| @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, true); |
| LOG.debug("destroyed app "+app+"; mgmt now "+mgmt); |
| } catch (Exception e) { |
| LOG.error("problems destroying app "+app, e); |
| } |
| } |
| } |
| if (launcher != null) launcher.destroyAll(); |
| if (viewers!=null) stopAllViewers(); |
| if (mgmt != null) Entities.destroyAll(mgmt); |
| if (mementoDir != null) FileBasedObjectStore.deleteCompletely(mementoDir); |
| } catch (Throwable t) { |
| LOG.error("Caught exception in tearDown method", t); |
| } finally { |
| mgmt = null; |
| launcher = null; |
| } |
| } |
| |
| protected void runCatalogTest(String catalogFile, Reader yamlApp) throws Exception { |
| runCatalogTest(catalogFile, yamlApp, Predicates.alwaysTrue()); |
| } |
| |
| protected void runCatalogTest(String catalogFile, Reader yamlApp, Predicate<? super Application> assertion) throws Exception { |
| Reader catalogInput = Streams.reader(new ResourceUtils(this).getResourceFromUrl(catalogFile)); |
| String catalogContent = Streams.readFullyAndClose(catalogInput); |
| Iterable<? extends CatalogItem<?, ?>> items = launcher.getManagementContext().getCatalog().addItems(catalogContent); |
| |
| try { |
| final Application app = launcher.launchAppYaml(yamlApp); |
| |
| assertNoFires(app); |
| assertTrue(assertion.apply(app)); |
| |
| Application newApp = rebind(); |
| assertNoFires(newApp); |
| assertTrue(assertion.apply(app)); |
| |
| } finally { |
| for (CatalogItem<?, ?> item : items) { |
| launcher.getManagementContext().getCatalog().deleteCatalogItem(item.getSymbolicName(), item.getVersion()); |
| } |
| } |
| } |
| |
| protected Application runTest(String yamlFile) throws Exception { |
| return runTestOnFile(yamlFile); |
| } |
| |
| protected Application runTestOnFile(String yamlFile) throws Exception { |
| return runTest(launcher.launchAppYaml(yamlFile)); |
| } |
| |
| protected Application runTestOnBlueprint(String blueprint) throws Exception { |
| return runTest(launcher.launchAppYaml(new StringReader(blueprint))); |
| } |
| |
| protected Application runTest(Application app) throws Exception { |
| return runTest(app, this::assertNoFires); |
| } |
| |
| protected Application runTest(Application app, Consumer<Application> check) throws Exception { |
| check.accept(app); |
| |
| Application newApp = rebind(); |
| check.accept(newApp); |
| |
| return app; |
| } |
| |
| protected void runTest(Reader yaml) throws Exception { |
| runTest(launcher.launchAppYaml(yaml), this::assertNoFires); |
| } |
| |
| protected void assertNoFires(final Entity app) { |
| EntityAsserts.assertAttributeEqualsEventually(app, Attributes.SERVICE_UP, true); |
| EntityAsserts.assertAttributeEqualsEventually(app, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); |
| |
| Asserts.succeedsEventually(new Runnable() { |
| @Override |
| public void run() { |
| for (Entity entity : Entities.descendantsAndSelf(app)) { |
| assertNotEquals(entity.getAttribute(Attributes.SERVICE_STATE_ACTUAL), Lifecycle.ON_FIRE); |
| assertNotEquals(entity.getAttribute(Attributes.SERVICE_UP), false); |
| |
| if (entity instanceof SoftwareProcess) { |
| EntityAsserts.assertAttributeEquals(entity, Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); |
| EntityAsserts.assertAttributeEquals(entity, Attributes.SERVICE_UP, Boolean.TRUE); |
| } |
| } |
| }}); |
| } |
| |
| 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 { |
| if (!isRebindEnabled()) { |
| throw new IllegalStateException("Rebind not enabled for this test; override isRebindEnabled"); |
| } |
| |
| 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.stopPersistence(origApp); |
| } |
| |
| mgmt = options.newManagementContext; |
| |
| startViewer(!isUsingNewViewerForRebind()); |
| |
| Application newApp = RebindTestUtils.rebind(options); |
| return newApp; |
| } |
| |
| /** override this to specify whether you want a viewer created (for testing) */ |
| protected boolean isViewerEnabled() { |
| return true; |
| } |
| |
| /** override this to return true if you want separate viewers for pre- and post- rebind */ |
| protected boolean isUsingNewViewerForRebind() { |
| return false; |
| } |
| |
| protected RebindTestUtils.ManagementContextBuilder createBuilderForRebindingManagementContext() { |
| return RebindTestUtils.managementContextBuilder(this.mementoDir, this.classLoader) |
| .persistPeriodMillis(1L) |
| .forLive(true) |
| .emptyCatalog(true); |
| } |
| |
| /** @return An unstarted management context */ |
| protected ManagementContext createNewManagementContext() { |
| ManagementContext newMgmt; |
| if (isRebindEnabled()) { |
| newMgmt = createBuilderForRebindingManagementContext().buildUnstarted(); |
| } else { |
| newMgmt = LocalManagementContextForTests.newInstance(); |
| } |
| |
| // add camp, for consistency with orig mgmt context |
| new BrooklynCampPlatform( |
| PlatformRootSummary.builder().name("Brooklyn CAMP Platform").build(), |
| newMgmt) |
| .setConfigKeyAtManagmentContext(); |
| |
| return newMgmt; |
| } |
| |
| // ----- |
| |
| |
| protected boolean isRebindEnabled() { |
| return true; |
| } |
| |
| /** @return A started management context -- with or without rebind depending on the value of {@link #isRebindEnabled()} */ |
| protected ManagementContext createOrigManagementContext() { |
| if (isRebindEnabled()) { |
| return createBuilderForRebindingManagementContext().buildStarted(); |
| } else { |
| return LocalManagementContextForTests.newInstance(); |
| } |
| } |
| |
| protected Application createAndStartApplication(String input) throws Exception { |
| Application app = this.createApplicationUnstarted(input); |
| app.invoke(Startable.START, MutableMap.of()).get(); |
| return app; |
| } |
| |
| protected Application createApplicationUnstarted(String yaml) throws Exception { |
| EntitySpec<Application> spec = this.createAppEntitySpec(yaml); |
| Entity app = this.mgmt.getEntityManager().createEntity(spec); |
| return (Application)app; |
| } |
| |
| protected <T extends Application> EntitySpec<T> createAppEntitySpec(String yaml) { |
| return (EntitySpec) EntityManagementUtils.createEntitySpecForApplication(this.mgmt, CampTypePlanTransformer.FORMAT, yaml); |
| } |
| |
| protected void addCatalogItems(String catalogYaml) { |
| mgmt.getCatalog().addTypesAndValidateAllowInconsistent(catalogYaml, (Map)null, false); |
| } |
| |
| /** read the given item from the classpath, relative to this class */ |
| protected String read(String filenameOnClasspath) { |
| // first try relative |
| try { |
| if (!filenameOnClasspath.startsWith("/")) { |
| String absolute = Strings.replaceAllNonRegex(getClass().getPackage().getName(), ".", "/") + "/" + filenameOnClasspath; |
| return ResourceUtils.create(this).getResourceAsString("classpath:"+"/"+absolute); |
| } |
| } catch (Exception e) { |
| Exceptions.propagateIfFatal(e); |
| // otherwise ignore, try as non-relative |
| } |
| |
| return ResourceUtils.create(this).getResourceAsString("classpath:"+filenameOnClasspath); |
| } |
| |
| } |