http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java new file mode 100644 index 0000000..5fc2670 --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncher.java @@ -0,0 +1,433 @@ +/* + * 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.rest; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.EnumSet; +import java.util.List; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; + +import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherAbstract; +import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.webapp.WebAppContext; +import org.reflections.util.ClasspathHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.BrooklynProperties; +import brooklyn.config.BrooklynServerConfig; +import brooklyn.config.BrooklynServiceAttributes; +import brooklyn.management.ManagementContext; +import brooklyn.management.internal.LocalManagementContext; +import brooklyn.management.internal.ManagementContextInternal; +import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter; +import org.apache.brooklyn.rest.filter.HaMasterCheckFilter; +import org.apache.brooklyn.rest.filter.LoggingFilter; +import org.apache.brooklyn.rest.filter.NoCacheFilter; +import org.apache.brooklyn.rest.filter.RequestTaggingFilter; +import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider; +import org.apache.brooklyn.rest.security.provider.SecurityProvider; +import org.apache.brooklyn.rest.util.ManagementContextProvider; +import org.apache.brooklyn.rest.util.ShutdownHandlerProvider; +import org.apache.brooklyn.rest.util.TestShutdownHandler; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.net.Networking; +import brooklyn.util.text.WildcardGlobs; + +import com.google.common.annotations.Beta; +import com.google.common.base.Charsets; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import com.sun.jersey.api.core.DefaultResourceConfig; +import com.sun.jersey.api.core.ResourceConfig; +import com.sun.jersey.spi.container.servlet.ServletContainer; + +/** Convenience and demo for launching programmatically. Also used for automated tests. + * <p> + * BrooklynLauncher has a more full-featured CLI way to start, + * but if you want more control you can: + * <li> take the WAR this project builds (REST API) -- NB probably want the unshaded one (containing all deps) + * <li> take the WAR from the jsgui project _and_ this WAR and combine them + * (this one should run as a filter on the others, _not_ as a ResourceCollection where they fight over who's got root) + * <li> programmatically install things, following the examples herein; + * in particular {@link #installAsServletFilter(ServletContextHandler)} is quite handy! + * <p> + * You can also just run this class. In most installs it just works, assuming your IDE or maven-fu gives you the classpath. + * Add more apps and entities on the classpath and they'll show up in the catalog. + **/ +public class BrooklynRestApiLauncher { + + private static final Logger log = LoggerFactory.getLogger(BrooklynRestApiLauncher.class); + final static int FAVOURITE_PORT = 8081; + public static final String SCANNING_CATALOG_BOM_URL = "classpath://brooklyn/scanning.catalog.bom"; + + enum StartMode { + FILTER, SERVLET, WEB_XML + } + + public static final List<Class<? extends Filter>> DEFAULT_FILTERS = ImmutableList.of( + RequestTaggingFilter.class, + BrooklynPropertiesSecurityFilter.class, + LoggingFilter.class, + HaMasterCheckFilter.class); + + private boolean forceUseOfDefaultCatalogWithJavaClassPath = false; + private Class<? extends SecurityProvider> securityProvider; + private List<Class<? extends Filter>> filters = DEFAULT_FILTERS; + private StartMode mode = StartMode.FILTER; + private ManagementContext mgmt; + private ContextHandler customContext; + private boolean deployJsgui = true; + private boolean disableHighAvailability = true; + private final TestShutdownHandler shutdownListener = new TestShutdownHandler(); + + protected BrooklynRestApiLauncher() {} + + public BrooklynRestApiLauncher managementContext(ManagementContext mgmt) { + this.mgmt = mgmt; + return this; + } + + public BrooklynRestApiLauncher forceUseOfDefaultCatalogWithJavaClassPath(boolean forceUseOfDefaultCatalogWithJavaClassPath) { + this.forceUseOfDefaultCatalogWithJavaClassPath = forceUseOfDefaultCatalogWithJavaClassPath; + return this; + } + + public BrooklynRestApiLauncher securityProvider(Class<? extends SecurityProvider> securityProvider) { + this.securityProvider = securityProvider; + return this; + } + + /** + * Runs the server with the given set of filters. + * Overrides any previously supplied set (or {@link #DEFAULT_FILTERS} which is used by default). + */ + public BrooklynRestApiLauncher filters(Class<? extends Filter>... filters) { + this.filters = Lists.newArrayList(filters); + return this; + } + + public BrooklynRestApiLauncher mode(StartMode mode) { + this.mode = checkNotNull(mode, "mode"); + return this; + } + + /** Overrides start mode to use an explicit context */ + public BrooklynRestApiLauncher customContext(ContextHandler customContext) { + this.customContext = checkNotNull(customContext, "customContext"); + return this; + } + + public BrooklynRestApiLauncher withJsgui() { + this.deployJsgui = true; + return this; + } + + public BrooklynRestApiLauncher withoutJsgui() { + this.deployJsgui = false; + return this; + } + + public BrooklynRestApiLauncher disableHighAvailability(boolean value) { + this.disableHighAvailability = value; + return this; + } + + public Server start() { + if (this.mgmt == null) { + mgmt = new LocalManagementContext(); + } + BrooklynCampPlatformLauncherAbstract platform = new BrooklynCampPlatformLauncherNoServer() + .useManagementContext(mgmt) + .launch(); + ((LocalManagementContext)mgmt).noteStartupComplete(); + log.debug("started "+platform); + + ContextHandler context; + String summary; + if (customContext == null) { + switch (mode) { + case SERVLET: + context = servletContextHandler(mgmt); + summary = "programmatic Jersey ServletContainer servlet"; + break; + case WEB_XML: + context = webXmlContextHandler(mgmt); + summary = "from WAR at " + ((WebAppContext) context).getWar(); + break; + case FILTER: + default: + context = filterContextHandler(mgmt); + summary = "programmatic Jersey ServletContainer filter on webapp at " + ((WebAppContext) context).getWar(); + break; + } + } else { + context = customContext; + summary = (context instanceof WebAppContext) + ? "from WAR at " + ((WebAppContext) context).getWar() + : "from custom context"; + } + + if (securityProvider != null) { + ((BrooklynProperties) mgmt.getConfig()).put( + BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, securityProvider.getName()); + } + + if (forceUseOfDefaultCatalogWithJavaClassPath) { + // sets URLs for a surefire + ((BrooklynProperties) mgmt.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, SCANNING_CATALOG_BOM_URL); + ((LocalManagementContext) mgmt).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath()); + } else { + // don't use any catalog.xml which is set + ((BrooklynProperties) mgmt.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, ManagementContextInternal.EMPTY_CATALOG_URL); + } + + return startServer(mgmt, context, summary, disableHighAvailability); + } + + private ContextHandler filterContextHandler(ManagementContext mgmt) { + WebAppContext context = new WebAppContext(); + context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, mgmt); + context.setContextPath("/"); + // here we run with the JS GUI, for convenience, if we can find it, else set up an empty dir + // TODO pretty sure there is an option to monitor this dir and load changes to static content + // NOTE: When running Brooklyn from an IDE (i.e. by launching BrooklynJavascriptGuiLauncher.main()) + // you will need to ensure that the working directory is set to the jsgui folder. For IntelliJ, + // set the 'Working directory' of the Run/Debug Configuration to $MODULE_DIR/../jsgui. + // For Eclipse, use the default option of ${workspace_loc:brooklyn-jsgui}. + // If the working directory is not set correctly, Brooklyn will be unable to find the jsgui .war + // file and the 'gui not available' message will be shown. + context.setWar(this.deployJsgui && findJsguiWebapp() != null + ? findJsguiWebapp() + : createTempWebDirWithIndexHtml("Brooklyn REST API <p> (gui not available)")); + installAsServletFilter(context, this.filters); + return context; + } + + private ContextHandler servletContextHandler(ManagementContext managementContext) { + ResourceConfig config = new DefaultResourceConfig(); + for (Object r: BrooklynRestApi.getAllResources()) + config.getSingletons().add(r); + config.getSingletons().add(new ShutdownHandlerProvider(shutdownListener)); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, managementContext); + ServletHolder servletHolder = new ServletHolder(new ServletContainer(config)); + context.addServlet(servletHolder, "/*"); + context.setContextPath("/"); + + installBrooklynFilters(context, this.filters); + return context; + } + + private ContextHandler webXmlContextHandler(ManagementContext mgmt) { + // TODO add security to web.xml + WebAppContext context; + if (findMatchingFile("src/main/webapp")!=null) { + // running in source mode; need to use special classpath + context = new WebAppContext("src/main/webapp", "/"); + context.setExtraClasspath("./target/classes"); + } else if (findRestApiWar()!=null) { + context = new WebAppContext(findRestApiWar(), "/"); + } else { + throw new IllegalStateException("Cannot find WAR for REST API. Expected in target/*.war, Maven repo, or in source directories."); + } + context.setAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT, mgmt); + + return context; + } + + /** starts a server, on all NICs if security is configured, + * otherwise (no security) only on loopback interface */ + public static Server startServer(ManagementContext mgmt, ContextHandler context, String summary, boolean disableHighAvailability) { + // TODO this repeats code in BrooklynLauncher / WebServer. should merge the two paths. + boolean secure = mgmt != null && !BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig()); + if (secure) { + log.debug("Detected security configured, launching server on all network interfaces"); + } else { + log.debug("Detected no security configured, launching server on loopback (localhost) network interface only"); + if (mgmt!=null) { + log.debug("Detected no security configured, running on loopback; disabling authentication"); + ((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, AnyoneSecurityProvider.class.getName()); + } + } + if (mgmt != null && disableHighAvailability) + mgmt.getHighAvailabilityManager().disabled(); + InetSocketAddress bindLocation = new InetSocketAddress( + secure ? Networking.ANY_NIC : Networking.LOOPBACK, + Networking.nextAvailablePort(FAVOURITE_PORT)); + return startServer(context, summary, bindLocation); + } + + public static Server startServer(ContextHandler context, String summary, InetSocketAddress bindLocation) { + Server server = new Server(bindLocation); + server.setHandler(context); + try { + server.start(); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + log.info("Brooklyn REST server started ("+summary+") on"); + log.info(" http://localhost:"+server.getConnectors()[0].getLocalPort()+"/"); + + return server; + } + + public static BrooklynRestApiLauncher launcher() { + return new BrooklynRestApiLauncher(); + } + + public static void main(String[] args) throws Exception { + startRestResourcesViaFilter(); + log.info("Press Ctrl-C to quit."); + } + + public static Server startRestResourcesViaFilter() { + return new BrooklynRestApiLauncher() + .mode(StartMode.FILTER) + .start(); + } + + public static Server startRestResourcesViaServlet() throws Exception { + return new BrooklynRestApiLauncher() + .mode(StartMode.SERVLET) + .start(); + } + + public static Server startRestResourcesViaWebXml() throws Exception { + return new BrooklynRestApiLauncher() + .mode(StartMode.WEB_XML) + .start(); + } + + public void installAsServletFilter(ServletContextHandler context) { + installAsServletFilter(context, DEFAULT_FILTERS); + } + + private void installAsServletFilter(ServletContextHandler context, List<Class<? extends Filter>> filters) { + installBrooklynFilters(context, filters); + + // now set up the REST servlet resources + ResourceConfig config = new DefaultResourceConfig(); + // load all our REST API modules, JSON, and Swagger + for (Object r: BrooklynRestApi.getAllResources()) + config.getSingletons().add(r); + + // disable caching for dynamic content + config.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, NoCacheFilter.class.getName()); + // Checks if appropriate request given HA status + config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter.class.getName()); + // configure to match empty path, or any thing which looks like a file path with /assets/ and extension html, css, js, or png + // and treat that as static content + config.getProperties().put(ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX, "(/?|[^?]*/assets/[^?]+\\.[A-Za-z0-9_]+)"); + // and anything which is not matched as a servlet also falls through (but more expensive than a regex check?) + config.getFeatures().put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true); + // finally create this as a _filter_ which falls through to a web app or something (optionally) + FilterHolder filterHolder = new FilterHolder(new ServletContainer(config)); + context.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class)); + + ManagementContext mgmt = (ManagementContext) context.getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT); + config.getSingletons().add(new ManagementContextProvider(mgmt)); + config.getSingletons().add(new ShutdownHandlerProvider(shutdownListener)); + } + + private static void installBrooklynFilters(ServletContextHandler context, List<Class<? extends Filter>> filters) { + for (Class<? extends Filter> filter : filters) { + context.addFilter(filter, "/*", EnumSet.allOf(DispatcherType.class)); + } + } + + /** + * Starts the server on all nics (even if security not enabled). + * @deprecated since 0.6.0; use {@link #launcher()} and set a custom context + */ + @Deprecated + public static Server startServer(ContextHandler context, String summary) { + return BrooklynRestApiLauncher.startServer(context, summary, + new InetSocketAddress(Networking.ANY_NIC, Networking.nextAvailablePort(FAVOURITE_PORT))); + } + + /** look for the JS GUI webapp in common places, returning path to it if found, or null */ + private static String findJsguiWebapp() { + // could also look in maven repo ? + return Optional + .fromNullable(findMatchingFile("../jsgui/src/main/webapp")) + .or(findMatchingFile("../jsgui/target/*.war")) + .orNull(); + } + + /** look for the REST WAR file in common places, returning path to it if found, or null */ + private static String findRestApiWar() { + // don't look at src/main/webapp here -- because classes won't be there! + // could also look in maven repo ? + return findMatchingFile("../rest/target/*.war").orNull(); + } + + /** returns the supplied filename if it exists (absolute or relative to the current directory); + * supports globs in the filename portion only, in which case it returns the _newest_ matching file. + * <p> + * otherwise returns null */ + @Beta // public because used in dependent test projects + public static Optional<String> findMatchingFile(String filename) { + final File f = new File(filename); + if (f.exists()) return Optional.of(filename); + File dir = f.getParentFile(); + File result = null; + if (dir.exists()) { + File[] matchingFiles = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return WildcardGlobs.isGlobMatched(f.getName(), name); + } + }); + for (File mf: matchingFiles) { + if (result==null || mf.lastModified() > result.lastModified()) result = mf; + } + } + if (result==null) return Optional.absent(); + return Optional.of(result.getAbsolutePath()); + } + + /** create a directory with a simple index.html so we have some content being served up */ + private static String createTempWebDirWithIndexHtml(String indexHtmlContent) { + File dir = Files.createTempDir(); + dir.deleteOnExit(); + try { + Files.write(indexHtmlContent, new File(dir, "index.html"), Charsets.UTF_8); + } catch (IOException e) { + Exceptions.propagate(e); + } + return dir.getAbsolutePath(); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java new file mode 100644 index 0000000..34ea94d --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTest.java @@ -0,0 +1,76 @@ +/* + * 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.rest; + +import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.FILTER; +import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.SERVLET; +import static org.apache.brooklyn.rest.BrooklynRestApiLauncher.StartMode.WEB_XML; + +import java.util.concurrent.Callable; + +import org.apache.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.testng.annotations.Test; + +import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtilsTest.SampleNoOpApplication; +import brooklyn.test.Asserts; +import brooklyn.test.HttpTestUtils; + +public class BrooklynRestApiLauncherTest extends BrooklynRestApiLauncherTestFixture { + + @Test + public void testFilterStart() throws Exception { + checkRestCatalogApplications(useServerForTest(baseLauncher().mode(FILTER).start())); + } + + @Test + public void testServletStart() throws Exception { + checkRestCatalogApplications(useServerForTest(baseLauncher().mode(SERVLET).start())); + } + + @Test + public void testWebAppStart() throws Exception { + checkRestCatalogApplications(useServerForTest(baseLauncher().mode(WEB_XML).start())); + } + + private BrooklynRestApiLauncher baseLauncher() { + return BrooklynRestApiLauncher.launcher() + .securityProvider(AnyoneSecurityProvider.class) + .forceUseOfDefaultCatalogWithJavaClassPath(true); + } + + private static void checkRestCatalogApplications(Server server) throws Exception { + final String rootUrl = "http://localhost:"+server.getConnectors()[0].getLocalPort(); + int code = Asserts.succeedsEventually(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + int code = HttpTestUtils.getHttpStatusCode(rootUrl+"/v1/catalog/applications"); + if (code == HttpStatus.SC_FORBIDDEN) { + throw new RuntimeException("Retry request"); + } else { + return code; + } + } + }); + HttpTestUtils.assertHealthyStatusCode(code); + HttpTestUtils.assertContentContainsText(rootUrl+"/v1/catalog/applications", SampleNoOpApplication.class.getSimpleName()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java new file mode 100644 index 0000000..5f2f0c6 --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/BrooklynRestApiLauncherTestFixture.java @@ -0,0 +1,110 @@ +/* + * 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.rest; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.reflections.util.ClasspathHelper; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; + +import brooklyn.config.BrooklynProperties; +import brooklyn.config.BrooklynServerConfig; +import brooklyn.config.BrooklynServiceAttributes; +import brooklyn.entity.basic.Entities; +import brooklyn.management.ManagementContext; +import brooklyn.management.internal.LocalManagementContext; +import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider; +import brooklyn.util.exceptions.Exceptions; + +public abstract class BrooklynRestApiLauncherTestFixture { + + Server server = null; + + @AfterMethod(alwaysRun=true) + public void stopServer() throws Exception { + if (server!=null) { + ManagementContext mgmt = getManagementContextFromJettyServerAttributes(server); + server.stop(); + if (mgmt!=null) Entities.destroyAll(mgmt); + server = null; + } + } + + protected Server newServer() { + try { + Server server = BrooklynRestApiLauncher.launcher() + .forceUseOfDefaultCatalogWithJavaClassPath(true) + .securityProvider(AnyoneSecurityProvider.class) + .start(); + return server; + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + protected Server useServerForTest(Server server) { + if (this.server!=null) { + Assert.fail("Test only meant for single server; already have "+this.server+" when checking "+server); + } else { + this.server = server; + } + return server; + } + + protected String getBaseUri() { + return getBaseUri(server); + } + public static String getBaseUri(Server server) { + return "http://localhost:"+server.getConnectors()[0].getLocalPort(); + } + + public static void forceUseOfDefaultCatalogWithJavaClassPath(Server server) { + ManagementContext mgmt = getManagementContextFromJettyServerAttributes(server); + forceUseOfDefaultCatalogWithJavaClassPath(mgmt); + } + + public static void forceUseOfDefaultCatalogWithJavaClassPath(ManagementContext manager) { + // TODO duplication with BrooklynRestApiLauncher ? + + // don't use any catalog.xml which is set + ((BrooklynProperties)manager.getConfig()).put(BrooklynServerConfig.BROOKLYN_CATALOG_URL, BrooklynRestApiLauncher.SCANNING_CATALOG_BOM_URL); + // sets URLs for a surefire + ((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forJavaClassPath()); + // this also works +// ((LocalManagementContext)manager).setBaseClassPathForScanning(ClasspathHelper.forPackage("brooklyn")); + // but this (near-default behaviour) does not +// ((LocalManagementContext)manager).setBaseClassLoader(getClass().getClassLoader()); + } + + public static void enableAnyoneLogin(Server server) { + ManagementContext mgmt = getManagementContextFromJettyServerAttributes(server); + enableAnyoneLogin(mgmt); + } + + public static void enableAnyoneLogin(ManagementContext mgmt) { + ((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, + AnyoneSecurityProvider.class.getName()); + } + + public static ManagementContext getManagementContextFromJettyServerAttributes(Server server) { + return (ManagementContext) ((ContextHandler) server.getHandler()).getAttribute(BrooklynServiceAttributes.BROOKLYN_MANAGEMENT_CONTEXT); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java new file mode 100644 index 0000000..f78e196 --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaHotCheckTest.java @@ -0,0 +1,130 @@ +/* + * 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.rest; + +import static org.testng.Assert.assertEquals; + +import javax.ws.rs.core.MediaType; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.management.ha.HighAvailabilityManager; +import brooklyn.management.ha.HighAvailabilityMode; +import brooklyn.management.ha.ManagementNodeState; +import brooklyn.management.internal.LocalManagementContext; +import brooklyn.management.internal.ManagementContextInternal; +import org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter; +import org.apache.brooklyn.rest.filter.HaMasterCheckFilter; +import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest; +import org.apache.brooklyn.rest.util.HaHotStateCheckClassResource; +import org.apache.brooklyn.rest.util.HaHotStateCheckResource; + +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource.Builder; +import com.sun.jersey.api.core.ResourceConfig; + +public class HaHotCheckTest extends BrooklynRestResourceTest { + + // setup and teardown before/after each method + + @BeforeMethod(alwaysRun = true) + public void setUp() throws Exception { super.setUp(); } + + @AfterMethod(alwaysRun = true) + public void tearDown() throws Exception { super.tearDown(); } + + @Override + protected void addBrooklynResources() { + config.getProperties().put(ResourceConfig.PROPERTY_RESOURCE_FILTER_FACTORIES, + new HaHotCheckResourceFilter(getManagementContext())); + addResource(new HaHotStateCheckResource()); + addResource(new HaHotStateCheckClassResource()); + + ((LocalManagementContext)getManagementContext()).noteStartupComplete(); + } + + @Test + public void testHaCheck() { + HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager(); + assertEquals(ha.getNodeState(), ManagementNodeState.MASTER); + testResourceFetch("/ha/method/ok", 200); + testResourceFetch("/ha/method/fail", 200); + testResourceFetch("/ha/class/fail", 200); + + getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY); + assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY); + + testResourceFetch("/ha/method/ok", 200); + testResourceFetch("/ha/method/fail", 403); + testResourceFetch("/ha/class/fail", 403); + + ((ManagementContextInternal)getManagementContext()).terminate(); + assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED); + + testResourceFetch("/ha/method/ok", 200); + testResourceFetch("/ha/method/fail", 403); + testResourceFetch("/ha/class/fail", 403); + } + + @Test + public void testHaCheckForce() { + HighAvailabilityManager ha = getManagementContext().getHighAvailabilityManager(); + assertEquals(ha.getNodeState(), ManagementNodeState.MASTER); + testResourceForcedFetch("/ha/method/ok", 200); + testResourceForcedFetch("/ha/method/fail", 200); + testResourceForcedFetch("/ha/class/fail", 200); + + getManagementContext().getHighAvailabilityManager().changeMode(HighAvailabilityMode.STANDBY); + assertEquals(ha.getNodeState(), ManagementNodeState.STANDBY); + + testResourceForcedFetch("/ha/method/ok", 200); + testResourceForcedFetch("/ha/method/fail", 200); + testResourceForcedFetch("/ha/class/fail", 200); + + ((ManagementContextInternal)getManagementContext()).terminate(); + assertEquals(ha.getNodeState(), ManagementNodeState.TERMINATED); + + testResourceForcedFetch("/ha/method/ok", 200); + testResourceForcedFetch("/ha/method/fail", 200); + testResourceForcedFetch("/ha/class/fail", 200); + } + + + private void testResourceFetch(String resourcePath, int code) { + testResourceFetch(resourcePath, false, code); + } + + private void testResourceForcedFetch(String resourcePath, int code) { + testResourceFetch(resourcePath, true, code); + } + + private void testResourceFetch(String resourcePath, boolean force, int code) { + Builder resource = client().resource(resourcePath) + .accept(MediaType.APPLICATION_JSON_TYPE); + if (force) { + resource.header(HaMasterCheckFilter.SKIP_CHECK_HEADER, "true"); + } + ClientResponse response = resource + .get(ClientResponse.class); + assertEquals(response.getStatus(), code); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java new file mode 100644 index 0000000..e2b6875 --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/HaMasterCheckFilterTest.java @@ -0,0 +1,219 @@ +/* + * 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.rest; + +import static org.testng.Assert.assertEquals; + +import java.io.File; +import java.net.URI; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeoutException; + +import org.apache.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.eclipse.jetty.server.Server; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.BasicApplication; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.rebind.RebindTestUtils; +import brooklyn.management.EntityManager; +import brooklyn.management.ManagementContext; +import brooklyn.management.ha.HighAvailabilityMode; +import brooklyn.management.ha.ManagementNodeState; +import org.apache.brooklyn.rest.security.provider.AnyoneSecurityProvider; +import brooklyn.test.Asserts; +import brooklyn.util.http.HttpTool; +import brooklyn.util.http.HttpToolResponse; +import brooklyn.util.os.Os; +import brooklyn.util.time.Duration; + +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; + +public class HaMasterCheckFilterTest extends BrooklynRestApiLauncherTestFixture { + private static final Duration TIMEOUT = Duration.THIRTY_SECONDS; + + private File mementoDir; + private ManagementContext writeMgmt; + private ManagementContext readMgmt; + private String appId; + private Server server; + private HttpClient client; + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { +System.err.println("TEAR DOWN"); + server.stop(); + Entities.destroyAll(writeMgmt); + Entities.destroyAll(readMgmt); + Os.deleteRecursively(mementoDir); + } + + @Test(groups = "Integration") + public void testEntitiesExistOnDisabledHA() throws Exception { + initHaCluster(HighAvailabilityMode.DISABLED, HighAvailabilityMode.DISABLED); + assertReadIsMaster(); + assertEntityExists(new ReturnCodeCheck()); + } + + @Test(groups = "Integration") + public void testEntitiesExistOnMasterPromotion() throws Exception { + initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.AUTO); + stopWriteNode(); + assertEntityExists(new ReturnCodeCheck()); + assertReadIsMaster(); + } + + @Test(groups = "Integration") + public void testEntitiesExistOnHotStandbyAndPromotion() throws Exception { + initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_STANDBY); + assertEntityExists(new ReturnCodeCheck()); + stopWriteNode(); + assertEntityExists(new ReturnCodeAndNodeState()); + assertReadIsMaster(); + } + + @Test(groups = "Integration") + public void testEntitiesExistOnHotBackup() throws Exception { + initHaCluster(HighAvailabilityMode.AUTO, HighAvailabilityMode.HOT_BACKUP); + Asserts.continually( + ImmutableMap.<String,Object>of( + "timeout", Duration.THIRTY_SECONDS, + "period", Duration.ZERO), + new ReturnCodeSupplier(), + Predicates.or(Predicates.equalTo(200), Predicates.equalTo(403))); + } + + private HttpClient getClient(Server server) { + HttpClient client = HttpTool.httpClientBuilder() + .uri(getBaseUri(server)) + .build(); + return client; + } + + private int getAppResponseCode() { + HttpToolResponse response = HttpTool.httpGet( + client, URI.create(getBaseUri(server) + "/v1/applications/" + appId), + ImmutableMap.<String,String>of()); + return response.getResponseCode(); + } + + private String createApp(ManagementContext mgmt) { + EntityManager entityMgr = mgmt.getEntityManager(); + Entity app = entityMgr.createEntity(EntitySpec.create(BasicApplication.class)); + entityMgr.manage(app); + return app.getId(); + } + + private ManagementContext createManagementContext(File mementoDir, HighAvailabilityMode mode) { + ManagementContext mgmt = RebindTestUtils.managementContextBuilder(mementoDir, getClass().getClassLoader()) + .persistPeriodMillis(1) + .forLive(false) + .emptyCatalog(true) + .buildUnstarted(); + + if (mode == HighAvailabilityMode.DISABLED) { + mgmt.getHighAvailabilityManager().disabled(); + } else { + mgmt.getHighAvailabilityManager().start(mode); + } + + new BrooklynCampPlatformLauncherNoServer() + .useManagementContext(mgmt) + .launch(); + + return mgmt; + } + + private void initHaCluster(HighAvailabilityMode writeMode, HighAvailabilityMode readMode) throws InterruptedException, TimeoutException { + mementoDir = Os.newTempDir(getClass()); + + writeMgmt = createManagementContext(mementoDir, writeMode); + appId = createApp(writeMgmt); + writeMgmt.getRebindManager().waitForPendingComplete(TIMEOUT, true); + + if (readMode == HighAvailabilityMode.DISABLED) { + //no HA, one node only + readMgmt = writeMgmt; + } else { + readMgmt = createManagementContext(mementoDir, readMode); + } + + server = useServerForTest(BrooklynRestApiLauncher.launcher() + .managementContext(readMgmt) + .securityProvider(AnyoneSecurityProvider.class) + .forceUseOfDefaultCatalogWithJavaClassPath(true) + .withoutJsgui() + .disableHighAvailability(false) + .start()); + client = getClient(server); + } + + private void assertEntityExists(Callable<Integer> c) { + assertEquals((int)Asserts.succeedsEventually(c), 200); + } + + private void assertReadIsMaster() { + assertEquals(readMgmt.getHighAvailabilityManager().getNodeState(), ManagementNodeState.MASTER); + } + + private void stopWriteNode() { + writeMgmt.getHighAvailabilityManager().stop(); + } + + private class ReturnCodeCheck implements Callable<Integer> { + @Override + public Integer call() { + int retCode = getAppResponseCode(); + if (retCode == 403) { + throw new RuntimeException("Not ready, retry. Response - " + retCode); + } else { + return retCode; + } + } + } + + private class ReturnCodeAndNodeState extends ReturnCodeCheck { + @Override + public Integer call() { + Integer ret = super.call(); + if (ret == HttpStatus.SC_OK) { + ManagementNodeState state = readMgmt.getHighAvailabilityManager().getNodeState(); + if (state != ManagementNodeState.MASTER) { + throw new RuntimeException("Not master yet " + state); + } + } + return ret; + } + } + + private class ReturnCodeSupplier implements Supplier<Integer> { + @Override + public Integer get() { + return getAppResponseCode(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java new file mode 100644 index 0000000..09100f5 --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/ApplicationTest.java @@ -0,0 +1,98 @@ +/* + * 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.rest.domain; + +import static brooklyn.rest.util.RestApiTestUtils.asJson; +import static brooklyn.rest.util.RestApiTestUtils.fromJson; +import static brooklyn.rest.util.RestApiTestUtils.jsonFixture; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Entities; +import brooklyn.management.ManagementContext; +import brooklyn.rest.domain.ApplicationSpec; +import brooklyn.rest.domain.ApplicationSummary; +import brooklyn.rest.domain.EntitySpec; +import brooklyn.rest.domain.Status; +import brooklyn.test.entity.TestApplicationImpl; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class ApplicationTest { + + final EntitySpec entitySpec = new EntitySpec("Vanilla Java App", "brooklyn.entity.java.VanillaJavaApp", + ImmutableMap.<String, String>of( + "initialSize", "1", + "creationScriptUrl", "http://my.brooklyn.io/storage/foo.sql")); + + final ApplicationSpec applicationSpec = ApplicationSpec.builder().name("myapp") + .entities(ImmutableSet.of(entitySpec)) + .locations(ImmutableSet.of("/v1/locations/1")) + .build(); + + final ApplicationSummary application = new ApplicationSummary(null, applicationSpec, Status.STARTING, null); + + @SuppressWarnings("serial") + @Test + public void testSerializeToJSON() throws IOException { + ApplicationSummary application1 = new ApplicationSummary("myapp_id", applicationSpec, Status.STARTING, null) { + @Override + public Map<String, URI> getLinks() { + return ImmutableMap.of( + "self", URI.create("/v1/applications/" + applicationSpec.getName()), + "entities", URI.create("fixtures/entity-summary-list.json")); + } + }; + assertEquals(asJson(application1), jsonFixture("fixtures/application.json")); + } + + @Test + public void testDeserializeFromJSON() throws IOException { + assertEquals(fromJson(jsonFixture("fixtures/application.json"), + ApplicationSummary.class), application); + } + + @Test + public void testTransitionToRunning() { + ApplicationSummary running = application.transitionTo(Status.RUNNING); + assertEquals(running.getStatus(), Status.RUNNING); + } + + @Test + public void testAppInAppTest() throws IOException { + TestApplicationImpl app = new TestApplicationImpl(); + ManagementContext mgmt = Entities.startManagement(app); + try { + Entity e2 = app.addChild(new TestApplicationImpl()); + Entities.manage(e2); + if (mgmt.getApplications().size()!=1) + Assert.fail("Apps in Apps should not be listed at top level: "+mgmt.getApplications()); + } finally { + Entities.destroyAll(mgmt); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java new file mode 100644 index 0000000..0df8fcc --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/LocationSummaryTest.java @@ -0,0 +1,57 @@ +/* + * 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.rest.domain; + +import brooklyn.rest.domain.LocationSpec; +import brooklyn.rest.domain.LocationSummary; +import static brooklyn.rest.util.RestApiTestUtils.asJson; +import static brooklyn.rest.util.RestApiTestUtils.fromJson; +import static brooklyn.rest.util.RestApiTestUtils.jsonFixture; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.codehaus.jackson.type.TypeReference; +import org.testng.annotations.Test; + +import org.apache.brooklyn.rest.transform.LocationTransformer; + +public class LocationSummaryTest { + + @SuppressWarnings("deprecation") + final LocationSummary summary = LocationTransformer.newInstance("123", LocationSpec.localhost()); + + @Test + public void testSerializeToJSON() throws IOException { + assertEquals(asJson(summary), jsonFixture("fixtures/location-summary.json")); + } + + @Test + public void testDeserializeFromJSON() throws IOException { + assertEquals(fromJson(jsonFixture("fixtures/location-summary.json"), LocationSummary.class), summary); + } + + @Test + public void testDeserializeListFromJSON() throws IOException { + assertEquals(fromJson(jsonFixture("fixtures/location-list.json"), new TypeReference<List<LocationSummary>>() {}), + Collections.singletonList(summary)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java new file mode 100644 index 0000000..24e31ec --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/domain/SensorSummaryTest.java @@ -0,0 +1,103 @@ +/* + * 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.rest.domain; + +import static brooklyn.rest.util.RestApiTestUtils.asJson; +import static brooklyn.rest.util.RestApiTestUtils.fromJson; +import static brooklyn.rest.util.RestApiTestUtils.jsonFixture; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.net.URI; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.config.render.RendererHints; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.event.AttributeSensor; +import brooklyn.event.Sensor; +import brooklyn.event.basic.Sensors; +import brooklyn.management.ManagementContext; +import brooklyn.rest.domain.SensorSummary; +import org.apache.brooklyn.rest.transform.SensorTransformer; +import brooklyn.test.entity.TestApplication; +import brooklyn.test.entity.TestEntity; + +import com.google.common.collect.ImmutableMap; + +public class SensorSummaryTest { + + private SensorSummary sensorSummary = new SensorSummary("redis.uptime", "Integer", + "Description", ImmutableMap.of( + "self", URI.create("/v1/applications/redis-app/entities/redis-ent/sensors/redis.uptime"))); + + private TestApplication app; + private TestEntity entity; + private ManagementContext mgmt; + + @BeforeMethod(alwaysRun = true) + public void setUp() throws Exception { + app = TestApplication.Factory.newManagedInstanceForTests(); + mgmt = app.getManagementContext(); + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + } + + @AfterMethod(alwaysRun = true) + public void tearDown() throws Exception { + if (mgmt != null) Entities.destroyAll(mgmt); + } + + @Test + public void testSerializeToJSON() throws IOException { + assertEquals(asJson(sensorSummary), jsonFixture("fixtures/sensor-summary.json")); + } + + @Test + public void testDeserializeFromJSON() throws IOException { + assertEquals(fromJson(jsonFixture("fixtures/sensor-summary.json"), SensorSummary.class), sensorSummary); + } + + @Test + public void testEscapesUriForSensorName() throws IOException { + Sensor<String> sensor = Sensors.newStringSensor("name with space"); + SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor); + URI selfUri = summary.getLinks().get("self"); + + String expectedUri = "/v1/applications/" + entity.getApplicationId() + "/entities/" + entity.getId() + "/sensors/" + "name%20with%20space"; + + assertEquals(selfUri, URI.create(expectedUri)); + } + + // Previously failed because immutable-map builder threw exception if put same key multiple times, + // and the NamedActionWithUrl did not have equals/hashCode + @Test + public void testSensorWithMultipleOpenUrlActionsRegistered() throws IOException { + AttributeSensor<String> sensor = Sensors.newStringSensor("sensor1"); + entity.setAttribute(sensor, "http://myval"); + RendererHints.register(sensor, RendererHints.namedActionWithUrl()); + RendererHints.register(sensor, RendererHints.namedActionWithUrl()); + + SensorSummary summary = SensorTransformer.sensorSummary(entity, sensor); + + assertEquals(summary.getLinks().get("action:open"), URI.create("http://myval")); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java new file mode 100644 index 0000000..edce6ba --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/AccessResourceTest.java @@ -0,0 +1,68 @@ +/* + * 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.rest.resources; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import brooklyn.rest.domain.AccessSummary; +import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest; + +import com.sun.jersey.api.client.ClientResponse; + +@Test(singleThreaded = true) +public class AccessResourceTest extends BrooklynRestResourceTest { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(AccessResourceTest.class); + + @Test + public void testGetAndSetAccessControl() throws Exception { + // Default is everything allowed + AccessSummary summary = client().resource("/v1/access").get(AccessSummary.class); + assertTrue(summary.isLocationProvisioningAllowed()); + + // Forbid location provisioning + ClientResponse response = client().resource( + "/v1/access/locationProvisioningAllowed") + .queryParam("allowed", "false") + .post(ClientResponse.class); + assertEquals(response.getStatus(), Response.Status.OK.getStatusCode()); + + AccessSummary summary2 = client().resource("/v1/access").get(AccessSummary.class); + assertFalse(summary2.isLocationProvisioningAllowed()); + + // Allow location provisioning + ClientResponse response2 = client().resource( + "/v1/access/locationProvisioningAllowed") + .queryParam("allowed", "true") + .post(ClientResponse.class); + assertEquals(response2.getStatus(), Response.Status.OK.getStatusCode()); + + AccessSummary summary3 = client().resource("/v1/access").get(AccessSummary.class); + assertTrue(summary3.isLocationProvisioningAllowed()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java new file mode 100644 index 0000000..29e4d22 --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApiDocResourceTest.java @@ -0,0 +1,138 @@ +/* + * 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.rest.resources; + +import static org.testng.Assert.assertEquals; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +import org.apache.brooklyn.rest.BrooklynRestApi; +import brooklyn.rest.apidoc.ApidocRoot; +import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.wordnik.swagger.core.DocumentationEndPoint; +import com.wordnik.swagger.core.DocumentationOperation; + +/** + * @author Adam Lowe + */ +@Test(singleThreaded = true) +public class ApiDocResourceTest extends BrooklynRestResourceTest { + + private static final Logger log = LoggerFactory.getLogger(ApiDocResourceTest.class); + + @Override + protected void addBrooklynResources() { + for (Object o : BrooklynRestApi.getApidocResources()) { + addResource(o); + } + super.addBrooklynResources(); + } + + @Test + public void testRootSerializesSensibly() throws Exception { + String data = client().resource("/v1/apidoc/").get(String.class); + log.info("apidoc gives: "+data); + // make sure no scala gets in + Assert.assertFalse(data.contains("$")); + Assert.assertFalse(data.contains("scala")); + } + + @Test + public void testCountRestResources() throws Exception { + ApidocRoot response = client().resource("/v1/apidoc/").get(ApidocRoot.class); + assertEquals(response.getApis().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources())); + } + + @Test + public void testEndpointSerializesSensibly() throws Exception { + String data = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.ApidocResource").get(String.class); + log.info("apidoc endpoint resource gives: "+data); + // make sure no scala gets in + Assert.assertFalse(data.contains("$")); + Assert.assertFalse(data.contains("scala")); + } + + @Test + public void testApiDocDetails() throws Exception { + ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.ApidocResource").get(ApidocRoot.class); + assertEquals(countOperations(response), 2); + } + + @Test + public void testEffectorDetails() throws Exception { + ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.EffectorResource").get(ApidocRoot.class); + assertEquals(countOperations(response), 2); + } + + @Test + public void testEntityDetails() throws Exception { + ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.EntityResource").get(ApidocRoot.class); + assertEquals(countOperations(response), 14); + } + + @Test + public void testCatalogDetails() throws Exception { + ApidocRoot response = client().resource("/v1/apidoc/org.apache.brooklyn.rest.resources.CatalogResource").get(ApidocRoot.class); + assertEquals(countOperations(response), 22, "ops="+getOperations(response)); + } + + @SuppressWarnings("rawtypes") + @Test + public void testAllAreLoadable() throws Exception { + // sometimes -- e.g. if an annotation refers to a class name with the wrong case -- the call returns a 500 and breaks apidoc; ensure we don't trigger that. + Map response = client().resource("/v1/apidoc/").get(Map.class); + // "Documenation" object does not include the links :( so traverse via map + log.debug("root doc response is: "+response); + List apis = (List)response.get("apis"); + for (Object api: apis) { + String link = (String) ((Map)api).get("link"); + try { + Map r2 = client().resource(link).get(Map.class); + log.debug("doc for "+link+" is: "+r2); + } catch (Exception e) { + log.error("Error in swagger/apidoc annotations, unparseable, at "+link+": "+e, e); + Assert.fail("Error in swagger/apidoc annotations, unparseable, at "+link+": "+e, e); + } + } + } + + /* Note in some cases we might have more than one Resource method per 'endpoint' + */ + private int countOperations(ApidocRoot doc) throws Exception { + return getOperations(doc).size(); + } + + private List<DocumentationOperation> getOperations(ApidocRoot doc) throws Exception { + List<DocumentationOperation> result = Lists.newArrayList(); + for (DocumentationEndPoint endpoint : doc.getApis()) { + result.addAll(endpoint.getOperations()); + } + return result; + } +} + http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java new file mode 100644 index 0000000..3ae6377 --- /dev/null +++ b/usage/rest-server/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java @@ -0,0 +1,134 @@ +/* + * 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.rest.resources; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.net.URI; +import java.util.Set; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import brooklyn.entity.basic.Lifecycle; +import brooklyn.rest.domain.ApplicationSpec; +import brooklyn.rest.domain.ApplicationSummary; +import brooklyn.rest.domain.EntitySpec; +import brooklyn.rest.domain.EntitySummary; +import brooklyn.rest.domain.SensorSummary; +import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest; +import brooklyn.test.Asserts; +import brooklyn.util.collections.MutableMap; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.GenericType; + +@Test(singleThreaded = true) +public class ApplicationResourceIntegrationTest extends BrooklynRestResourceTest { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(ApplicationResourceIntegrationTest.class); + + private final ApplicationSpec redisSpec = ApplicationSpec.builder().name("redis-app") + .entities(ImmutableSet.of(new EntitySpec("redis-ent", "org.apache.brooklyn.entity.nosql.redis.RedisStore"))) + .locations(ImmutableSet.of("localhost")) + .build(); + + @Test(groups="Integration") + public void testDeployRedisApplication() throws Exception { + ClientResponse response = clientDeploy(redisSpec); + + assertEquals(response.getStatus(), 201); + assertEquals(getManagementContext().getApplications().size(), 1); + assertTrue(response.getLocation().getPath().startsWith("/v1/applications/"), "path="+response.getLocation().getPath()); // path uses id, rather than app name + + waitForApplicationToBeRunning(response.getLocation()); + } + + @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication") + public void testListEntities() { + Set<EntitySummary> entities = client().resource("/v1/applications/redis-app/entities") + .get(new GenericType<Set<EntitySummary>>() {}); + + for (EntitySummary entity : entities) { + client().resource(entity.getLinks().get("self")).get(ClientResponse.class); + // TODO assertions on the above call? + + Set<EntitySummary> children = client().resource(entity.getLinks().get("children")) + .get(new GenericType<Set<EntitySummary>>() {}); + assertEquals(children.size(), 0); + } + } + + @Test(groups="Integration", dependsOnMethods = "testDeployRedisApplication") + public void testListSensorsRedis() { + Set<SensorSummary> sensors = client().resource("/v1/applications/redis-app/entities/redis-ent/sensors") + .get(new GenericType<Set<SensorSummary>>() {}); + assertTrue(sensors.size() > 0); + SensorSummary uptime = Iterables.find(sensors, new Predicate<SensorSummary>() { + @Override + public boolean apply(SensorSummary sensorSummary) { + return sensorSummary.getName().equals("redis.uptime"); + } + }); + assertEquals(uptime.getType(), "java.lang.Integer"); + } + + @Test(groups="Integration", dependsOnMethods = { "testListSensorsRedis", "testListEntities" }) + public void testTriggerRedisStopEffector() throws Exception { + ClientResponse response = client().resource("/v1/applications/redis-app/entities/redis-ent/effectors/stop") + .type(MediaType.APPLICATION_JSON_TYPE) + .post(ClientResponse.class, ImmutableMap.of()); + assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode()); + + final URI stateSensor = URI.create("/v1/applications/redis-app/entities/redis-ent/sensors/service.state"); + final String expectedStatus = Lifecycle.STOPPED.toString(); + Asserts.succeedsEventually(MutableMap.of("timeout", 60 * 1000), new Runnable() { + public void run() { + // Accept with and without quotes; if don't specify "Accepts" header, then + // might get back json or plain text (depending on compiler / java runtime + // used for SensorApi!) + String val = client().resource(stateSensor).get(String.class); + assertTrue(expectedStatus.equalsIgnoreCase(val) || ("\""+expectedStatus+"\"").equalsIgnoreCase(val), "state="+val); + } + }); + } + + @Test(groups="Integration", dependsOnMethods = "testTriggerRedisStopEffector" ) + public void testDeleteRedisApplication() throws Exception { + int size = getManagementContext().getApplications().size(); + ClientResponse response = client().resource("/v1/applications/redis-app") + .delete(ClientResponse.class); + + waitForPageNotFoundResponse("/v1/applications/redis-app", ApplicationSummary.class); + + assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode()); + assertEquals(getManagementContext().getApplications().size(), size-1); + } + +}
