You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2016/02/18 16:47:46 UTC
[28/34] brooklyn-server git commit: This closes #15
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
----------------------------------------------------------------------
diff --cc rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
index 0000000,5894700..238e277
mode 000000,100644..100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/WebResourceUtils.java
@@@ -1,0 -1,197 +1,231 @@@
+ /*
+ * 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.util;
+
+ import java.io.IOException;
+ import java.util.Map;
+
+ import javax.servlet.ServletContext;
+ import javax.servlet.http.HttpServletResponse;
+ import javax.ws.rs.WebApplicationException;
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+ import org.apache.brooklyn.rest.domain.ApiError;
+ import org.apache.brooklyn.rest.util.json.BrooklynJacksonJsonProvider;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
++import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
+ import org.apache.brooklyn.util.net.Urls;
+ import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
+
+ import com.fasterxml.jackson.databind.ObjectMapper;
+ import com.google.common.collect.ImmutableMap;
+ import javax.ws.rs.core.UriBuilder;
+
+ public class WebResourceUtils {
+
+ private static final Logger log = LoggerFactory.getLogger(WebResourceUtils.class);
+
+ /** @throws WebApplicationException with an ApiError as its body and the given status as its response code. */
+ public static WebApplicationException throwWebApplicationException(Response.Status status, String format, Object... args) {
- String msg = String.format(format, args);
++ return throwWebApplicationException(status, null, format, args);
++ }
++
++ /** @throws WebApplicationException with an ApiError as its body and the given status as its response code. */
++ public static WebApplicationException throwWebApplicationException(Response.Status status, Throwable exception) {
++ return throwWebApplicationException(status, exception, null);
++ }
++
++ /** @throws WebApplicationException with an ApiError as its body and the given status as its response code.
++ * Exception and/or format can be null, and will be filled in / prefixed as appropriate. */
++ public static WebApplicationException throwWebApplicationException(Response.Status status, Throwable exception, String format, Object... args) {
++ String suppliedMsg = format==null ? null : String.format(format, args);
++ String fullMsg = suppliedMsg;
++ if (exception!=null) {
++ if (fullMsg==null) fullMsg = Exceptions.collapseText(exception);
++ else fullMsg = suppliedMsg + ": "+Exceptions.collapseText(exception);
++ }
+ if (log.isDebugEnabled()) {
+ log.debug("responding {} {} ({})",
- new Object[]{status.getStatusCode(), status.getReasonPhrase(), msg});
++ new Object[]{status.getStatusCode(), status.getReasonPhrase(), fullMsg});
+ }
- ApiError apiError = ApiError.builder().message(msg).errorCode(status).build();
++ ApiError apiError =
++ (exception != null ? ApiError.builderFromThrowable(exception).prefixMessage(suppliedMsg)
++ : ApiError.builder().message(fullMsg==null ? "" : fullMsg))
++ .errorCode(status).build();
+ // including a Throwable is the only way to include a message with the WebApplicationException - ugly!
- throw new WebApplicationException(new Throwable(apiError.toString()), apiError.asJsonResponse());
++ throw new WebApplicationException(
++ exception==null ? new Throwable(apiError.toString()) :
++ suppliedMsg==null ? exception :
++ new PropagatedRuntimeException(suppliedMsg, exception),
++ apiError.asJsonResponse());
+ }
+
+ /** @throws WebApplicationException With code 500 internal server error */
+ public static WebApplicationException serverError(String format, Object... args) {
+ return throwWebApplicationException(Response.Status.INTERNAL_SERVER_ERROR, format, args);
+ }
+
+ /** @throws WebApplicationException With code 400 bad request */
+ public static WebApplicationException badRequest(String format, Object... args) {
+ return throwWebApplicationException(Response.Status.BAD_REQUEST, format, args);
+ }
+
++ /** @throws WebApplicationException With code 400 bad request */
++ public static WebApplicationException badRequest(Throwable t) {
++ return throwWebApplicationException(Response.Status.BAD_REQUEST, t);
++ }
++
++ /** @throws WebApplicationException With code 400 bad request */
++ public static WebApplicationException badRequest(Throwable t, String prefix, Object... prefixArgs) {
++ return throwWebApplicationException(Response.Status.BAD_REQUEST, t, prefix, prefixArgs);
++ }
++
+ /** @throws WebApplicationException With code 401 unauthorized */
+ public static WebApplicationException unauthorized(String format, Object... args) {
+ return throwWebApplicationException(Response.Status.UNAUTHORIZED, format, args);
+ }
+
+ /** @throws WebApplicationException With code 403 forbidden */
+ public static WebApplicationException forbidden(String format, Object... args) {
+ return throwWebApplicationException(Response.Status.FORBIDDEN, format, args);
+ }
+
+ /** @throws WebApplicationException With code 404 not found */
+ public static WebApplicationException notFound(String format, Object... args) {
+ return throwWebApplicationException(Response.Status.NOT_FOUND, format, args);
+ }
+
+ /** @throws WebApplicationException With code 412 precondition failed */
+ public static WebApplicationException preconditionFailed(String format, Object... args) {
+ return throwWebApplicationException(Response.Status.PRECONDITION_FAILED, format, args);
+ }
+
+ public final static Map<String,com.google.common.net.MediaType> IMAGE_FORMAT_MIME_TYPES = ImmutableMap.<String, com.google.common.net.MediaType>builder()
+ .put("jpg", com.google.common.net.MediaType.JPEG)
+ .put("jpeg", com.google.common.net.MediaType.JPEG)
+ .put("png", com.google.common.net.MediaType.PNG)
+ .put("gif", com.google.common.net.MediaType.GIF)
+ .put("svg", com.google.common.net.MediaType.SVG_UTF_8)
+ .build();
+
+ public static MediaType getImageMediaTypeFromExtension(String extension) {
+ com.google.common.net.MediaType mime = IMAGE_FORMAT_MIME_TYPES.get(extension.toLowerCase());
+ if (mime==null) return null;
+ try {
+ return MediaType.valueOf(mime.toString());
+ } catch (Exception e) {
+ log.warn("Unparseable MIME type "+mime+"; ignoring ("+e+")");
+ Exceptions.propagateIfFatal(e);
+ return null;
+ }
+ }
+
+ /** as {@link #getValueForDisplay(ObjectMapper, Object, boolean, boolean)} with no mapper
+ * (so will only handle a subset of types) */
+ public static Object getValueForDisplay(Object value, boolean preferJson, boolean isJerseyReturnValue) {
+ return getValueForDisplay(null, value, preferJson, isJerseyReturnValue);
+ }
+
+ /** returns an object which jersey will handle nicely, converting to json,
+ * sometimes wrapping in quotes if needed (for outermost json return types);
+ * if json is not preferred, this simply applies a toString-style rendering */
+ public static Object getValueForDisplay(ObjectMapper mapper, Object value, boolean preferJson, boolean isJerseyReturnValue) {
+ if (preferJson) {
+ if (value==null) return null;
+ Object result = value;
+ // no serialization checks required, with new smart-mapper which does toString
+ // (note there is more sophisticated logic in git history however)
+ result = value;
+
+ if (isJerseyReturnValue) {
+ if (result instanceof String) {
+ // Jersey does not do json encoding if the return type is a string,
+ // expecting the returner to do the json encoding himself
+ // cf discussion at https://github.com/dropwizard/dropwizard/issues/231
+ result = JavaStringEscapes.wrapJavaString((String)result);
+ }
+ }
+
+ return result;
+ } else {
+ if (value==null) return "";
+ return value.toString();
+ }
+ }
+
+ public static String getPathFromVersionedId(String versionedId) {
+ if (CatalogUtils.looksLikeVersionedId(versionedId)) {
+ String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(versionedId);
+ String version = CatalogUtils.getVersionFromVersionedId(versionedId);
+ return Urls.encode(symbolicName) + "/" + Urls.encode(version);
+ } else {
+ return Urls.encode(versionedId);
+ }
+ }
+
+ /** Sets the {@link HttpServletResponse} target (last argument) from the given source {@link Response};
+ * useful in filters where we might have a {@link Response} and need to set up an {@link HttpServletResponse}.
+ */
+ public static void applyJsonResponse(ServletContext servletContext, Response source, HttpServletResponse target) throws IOException {
+ target.setStatus(source.getStatus());
+ target.setContentType(MediaType.APPLICATION_JSON);
+ target.setCharacterEncoding("UTF-8");
+ target.getWriter().write(BrooklynJacksonJsonProvider.findAnyObjectMapper(servletContext, null).writeValueAsString(source.getEntity()));
+ }
+
+ /**
+ * Provides a builder with the REST URI of a resource.
+ * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+ * @param resourceClass The target resource class.
+ * @return A new {@link UriBuilder} that targets the specified REST resource.
+ */
+ public static UriBuilder resourceUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass) {
+ return UriBuilder.fromPath(baseUriBuilder.build().getPath())
+ .path(resourceClass);
+ }
+
+ /**
+ * Provides a builder with the REST URI of a service provided by a resource.
+ * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+ * @param resourceClass The target resource class.
+ * @param method The target service (e.g. class method).
+ * @return A new {@link UriBuilder} that targets the specified service of the REST resource.
+ */
+ public static UriBuilder serviceUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass, String method) {
+ return resourceUriBuilder(baseUriBuilder, resourceClass).path(resourceClass, method);
+ }
+
+ /**
+ * Provides a builder with the absolute REST URI of a service provided by a resource.
+ * @param baseUriBuilder An {@link UriBuilder} pointing at the base of the REST API.
+ * @param resourceClass The target resource class.
+ * @param method The target service (e.g. class method).
+ * @return A new {@link UriBuilder} that targets the specified service of the REST resource.
+ */
+ public static UriBuilder serviceAbsoluteUriBuilder(UriBuilder baseUriBuilder, Class<?> resourceClass, String method) {
+ return baseUriBuilder
+ .path(resourceClass)
+ .path(resourceClass, method);
+ }
+
+ }
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
----------------------------------------------------------------------
diff --cc rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
index 0000000,739d63f..2064508
mode 000000,100644..100644
--- a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApidocResourceTest.java
@@@ -1,0 -1,177 +1,173 @@@
+ /*
+ * 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 io.swagger.annotations.Api;
++import io.swagger.models.Operation;
++import io.swagger.models.Path;
++import io.swagger.models.Swagger;
++
++import java.util.Collection;
++
++import org.apache.brooklyn.rest.BrooklynRestApi;
++import org.apache.brooklyn.rest.api.CatalogApi;
++import org.apache.brooklyn.rest.api.EffectorApi;
++import org.apache.brooklyn.rest.api.EntityApi;
++import org.apache.brooklyn.rest.filter.SwaggerFilter;
++import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++import org.testng.annotations.Test;
+
+ import com.google.common.base.Function;
+ import com.google.common.base.Joiner;
+ import com.google.common.base.Predicate;
+ import com.google.common.collect.Collections2;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Iterables;
+ import com.sun.jersey.api.core.ClassNamesResourceConfig;
+ import com.sun.jersey.spi.container.servlet.ServletContainer;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import org.apache.brooklyn.rest.BrooklynRestApi;
-import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
-
+ import com.sun.jersey.test.framework.AppDescriptor;
+ import com.sun.jersey.test.framework.JerseyTest;
+ import com.sun.jersey.test.framework.WebAppDescriptor;
+ import com.sun.jersey.test.framework.spi.container.TestContainerException;
+ import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
+ import com.sun.jersey.test.framework.spi.container.grizzly2.web.GrizzlyWebTestContainerFactory;
-import io.swagger.annotations.Api;
-import io.swagger.models.Info;
-import io.swagger.models.Operation;
-import io.swagger.models.Path;
-import io.swagger.models.Swagger;
-import java.util.Collection;
-import org.apache.brooklyn.rest.api.CatalogApi;
-import org.apache.brooklyn.rest.api.EffectorApi;
-import org.apache.brooklyn.rest.api.EntityApi;
-import org.apache.brooklyn.rest.filter.SwaggerFilter;
-import org.apache.brooklyn.rest.util.ShutdownHandlerProvider;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
+
+ /**
+ * @author Adam Lowe
+ */
+ @Test(singleThreaded = true)
+ public class ApidocResourceTest extends BrooklynRestResourceTest {
+
+ private static final Logger log = LoggerFactory.getLogger(ApidocResourceTest.class);
+
+ @Override
+ protected JerseyTest createJerseyTest() {
+ return new JerseyTest() {
+ @Override
+ protected AppDescriptor configure() {
+ return new WebAppDescriptor.Builder(
+ ImmutableMap.of(
+ ServletContainer.RESOURCE_CONFIG_CLASS, ClassNamesResourceConfig.class.getName(),
+ ClassNamesResourceConfig.PROPERTY_CLASSNAMES, getResourceClassnames()))
+ .addFilter(SwaggerFilter.class, "SwaggerFilter").build();
+ }
+
+ @Override
+ protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+ return new GrizzlyWebTestContainerFactory();
+ }
+
+ private String getResourceClassnames() {
- Iterable<String> classnames = Collections2.transform(config.getClasses(), new Function<Class, String>() {
++ Iterable<String> classnames = Collections2.transform(config.getClasses(), new Function<Class<?>, String>() {
+ @Override
- public String apply(Class clazz) {
++ public String apply(Class<?> clazz) {
+ return clazz.getName();
+ }
+ });
+ classnames = Iterables.concat(classnames, Collections2.transform(config.getSingletons(), new Function<Object, String>() {
+ @Override
+ public String apply(Object singleton) {
+ return singleton.getClass().getName();
+ }
+ }));
+ return Joiner.on(';').join(classnames);
+ }
+ };
+ }
+
+ @Override
+ protected void addBrooklynResources() {
+ for (Object o : BrooklynRestApi.getApidocResources()) {
+ addResource(o);
+ }
+ super.addBrooklynResources();
+ }
+
+ @Test(enabled = false)
+ public void testRootSerializesSensibly() throws Exception {
+ String data = resource("/v1/apidoc/swagger.json").get(String.class);
+ log.info("apidoc gives: "+data);
+ // make sure no scala gets in
+ assertFalse(data.contains("$"));
+ assertFalse(data.contains("scala"));
+ // make sure it's an appropriate swagger 2.0 json
+ Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+ assertEquals(swagger.getSwagger(), "2.0");
+ }
+
+ @Test(enabled = false)
+ public void testCountRestResources() throws Exception {
+ Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+ assertEquals(swagger.getTags().size(), 1 + Iterables.size(BrooklynRestApi.getBrooklynRestResources()));
+ }
+
+ @Test(enabled = false)
+ public void testApiDocDetails() throws Exception {
+ Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+ Collection<Operation> operations = getTaggedOperations(swagger, ApidocResource.class.getAnnotation(Api.class).value());
+ assertEquals(operations.size(), 2, "ops="+operations);
+ }
+
+ @Test(enabled = false)
+ public void testEffectorDetails() throws Exception {
+ Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+ Collection<Operation> operations = getTaggedOperations(swagger, EffectorApi.class.getAnnotation(Api.class).value());
+ assertEquals(operations.size(), 2, "ops="+operations);
+ }
+
+ @Test(enabled = false)
+ public void testEntityDetails() throws Exception {
+ Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+ Collection<Operation> operations = getTaggedOperations(swagger, EntityApi.class.getAnnotation(Api.class).value());
+ assertEquals(operations.size(), 14, "ops="+operations);
+ }
+
+ @Test(enabled = false)
+ public void testCatalogDetails() throws Exception {
+ Swagger swagger = resource("/v1/apidoc/swagger.json").get(Swagger.class);
+ Collection<Operation> operations = getTaggedOperations(swagger, CatalogApi.class.getAnnotation(Api.class).value());
+ assertEquals(operations.size(), 22, "ops="+operations);
+ }
+
+ /**
+ * Retrieves all operations tagged the given tag from the given swagger spec.
+ */
+ private Collection<Operation> getTaggedOperations(Swagger swagger, final String requiredTag) {
+ Iterable<Operation> allOperations = Iterables.concat(Collections2.transform(swagger.getPaths().values(),
+ new Function<Path, Collection<Operation>>() {
+ @Override
+ public Collection<Operation> apply(Path path) {
+ return path.getOperations();
+ }
+ }));
+
+ return Collections2.filter(ImmutableList.copyOf(allOperations), new Predicate<Operation>() {
+ @Override
+ public boolean apply(Operation operation) {
+ return operation.getTags().contains(requiredTag);
+ }
+ });
+ }
+ }
+
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --cc rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index 0000000,8b49763..ada548b
mode 000000,100644..100644
--- a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@@ -1,0 -1,701 +1,701 @@@
+ /*
+ * 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 com.google.common.collect.Iterables.find;
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertNotNull;
+ import static org.testng.Assert.assertTrue;
+
+ import java.io.IOException;
+ import java.net.URI;
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.concurrent.TimeoutException;
+
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.MultivaluedMap;
+ import javax.ws.rs.core.Response;
+
+ import org.apache.brooklyn.api.entity.Application;
+ import org.apache.brooklyn.core.entity.Attributes;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.entity.EntityFunctions;
+ import org.apache.brooklyn.core.entity.EntityPredicates;
+ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+ import org.apache.brooklyn.core.location.AbstractLocation;
+ import org.apache.brooklyn.core.location.LocationConfigKeys;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.core.location.internal.LocationInternal;
+ import org.apache.brooklyn.entity.stock.BasicApplication;
+ import org.apache.brooklyn.entity.stock.BasicEntity;
+ import org.apache.brooklyn.rest.domain.ApiError;
+ import org.apache.brooklyn.rest.domain.ApplicationSpec;
+ import org.apache.brooklyn.rest.domain.ApplicationSummary;
+ import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
+ import org.apache.brooklyn.rest.domain.CatalogItemSummary;
+ import org.apache.brooklyn.rest.domain.EffectorSummary;
+ import org.apache.brooklyn.rest.domain.EntityConfigSummary;
+ import org.apache.brooklyn.rest.domain.EntitySpec;
+ import org.apache.brooklyn.rest.domain.EntitySummary;
+ import org.apache.brooklyn.rest.domain.PolicySummary;
+ import org.apache.brooklyn.rest.domain.SensorSummary;
+ import org.apache.brooklyn.rest.domain.Status;
+ import org.apache.brooklyn.rest.domain.TaskSummary;
+ import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+ import org.apache.brooklyn.rest.testing.mocks.CapitalizePolicy;
+ import org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup;
+ import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
+ import org.apache.brooklyn.rest.testing.mocks.RestMockAppBuilder;
+ import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+ import org.apache.brooklyn.test.Asserts;
+ import org.apache.brooklyn.util.collections.CollectionFunctionals;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.http.HttpAsserts;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.apache.brooklyn.util.time.Duration;
+ import org.apache.http.HttpHeaders;
+ import org.apache.http.entity.ContentType;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.Test;
+
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Maps;
+ import com.sun.jersey.api.client.ClientResponse;
+ import com.sun.jersey.api.client.GenericType;
+ import com.sun.jersey.api.client.UniformInterfaceException;
+ import com.sun.jersey.api.client.WebResource;
+ import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+ @Test(singleThreaded = true)
+ public class ApplicationResourceTest extends BrooklynRestResourceTest {
+
+ /*
+ * In simpleSpec, not using EverythingGroup because caused problems! The group is a child of the
+ * app, and the app is a member of the group. It failed in jenkins with:
+ * BasicApplicationImpl{id=GSPjBCe4} GSPjBCe4
+ * service.isUp: true
+ * service.problems: {service-lifecycle-indicators-from-children-and-members=Required entity not healthy: EverythingGroupImpl{id=KQ4mSEOJ}}
+ * service.state: on-fire
+ * service.state.expected: running @ 1412003485617 / Mon Sep 29 15:11:25 UTC 2014
+ * EverythingGroupImpl{id=KQ4mSEOJ} KQ4mSEOJ
+ * service.isUp: true
+ * service.problems: {service-lifecycle-indicators-from-children-and-members=Required entities not healthy: BasicApplicationImpl{id=GSPjBCe4}, EverythingGroupImpl{id=KQ4mSEOJ}}
+ * service.state: on-fire
+ * I'm guessing there's a race: the app was not yet healthy because EverythingGroup hadn't set itself to running;
+ * but then the EverythingGroup would never transition to healthy because one of its members was not healthy.
+ */
+
+ private static final Logger log = LoggerFactory.getLogger(ApplicationResourceTest.class);
+
+ private final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app")
+ .entities(ImmutableSet.of(
+ new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()),
+ new EntitySpec("simple-group", NameMatcherGroup.class.getName(), ImmutableMap.of("namematchergroup.regex", "simple-ent"))
+ ))
+ .locations(ImmutableSet.of("localhost"))
+ .build();
+
+ // Convenience for finding an EntitySummary within a collection, based on its name
+ private static Predicate<EntitySummary> withName(final String name) {
+ return new Predicate<EntitySummary>() {
+ public boolean apply(EntitySummary input) {
+ return name.equals(input.getName());
+ }
+ };
+ }
+
+ // Convenience for finding a Map within a collection, based on the value of one of its keys
+ private static Predicate<? super Map<?,?>> withValueForKey(final Object key, final Object value) {
+ return new Predicate<Object>() {
+ public boolean apply(Object input) {
+ if (!(input instanceof Map)) return false;
+ return value.equals(((Map<?, ?>) input).get(key));
+ }
+ };
+ }
+
+ @Test
+ public void testGetUndefinedApplication() {
+ try {
+ client().resource("/v1/applications/dummy-not-found").get(ApplicationSummary.class);
+ } catch (UniformInterfaceException e) {
+ assertEquals(e.getResponse().getStatus(), 404);
+ }
+ }
+
+ private static void assertRegexMatches(String actual, String patternExpected) {
+ if (actual==null) Assert.fail("Actual value is null; expected "+patternExpected);
+ if (!actual.matches(patternExpected)) {
+ Assert.fail("Text '"+actual+"' does not match expected pattern "+patternExpected);
+ }
+ }
+
+ @Test
+ public void testDeployApplication() throws Exception {
+ ClientResponse response = clientDeploy(simpleSpec);
+
+ HttpAsserts.assertHealthyStatusCode(response.getStatus());
+ assertEquals(getManagementContext().getApplications().size(), 1);
+ assertRegexMatches(response.getLocation().getPath(), "/v1/applications/.*");
+ // Object taskO = response.getEntity(Object.class);
+ TaskSummary task = response.getEntity(TaskSummary.class);
+ log.info("deployed, got " + task);
+ assertEquals(task.getEntityId(), getManagementContext().getApplications().iterator().next().getApplicationId());
+
+ waitForApplicationToBeRunning(response.getLocation());
+ }
+
+ @Test(dependsOnMethods = { "testDeleteApplication" })
+ // this must happen after we've deleted the main application, as testLocatedLocations assumes a single location
+ public void testDeployApplicationImpl() throws Exception {
+ ApplicationSpec spec = ApplicationSpec.builder()
+ .type(RestMockApp.class.getCanonicalName())
+ .name("simple-app-impl")
+ .locations(ImmutableSet.of("localhost"))
+ .build();
+
+ ClientResponse response = clientDeploy(spec);
+ assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+ // Expect app to be running
+ URI appUri = response.getLocation();
+ waitForApplicationToBeRunning(response.getLocation());
+ assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-impl");
+ }
+
+ @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+ public void testDeployApplicationFromInterface() throws Exception {
+ ApplicationSpec spec = ApplicationSpec.builder()
+ .type(BasicApplication.class.getCanonicalName())
+ .name("simple-app-interface")
+ .locations(ImmutableSet.of("localhost"))
+ .build();
+
+ ClientResponse response = clientDeploy(spec);
+ assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+ // Expect app to be running
+ URI appUri = response.getLocation();
+ waitForApplicationToBeRunning(response.getLocation());
+ assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-interface");
+ }
+
+ @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+ public void testDeployApplicationFromBuilder() throws Exception {
+ ApplicationSpec spec = ApplicationSpec.builder()
+ .type(RestMockAppBuilder.class.getCanonicalName())
+ .name("simple-app-builder")
+ .locations(ImmutableSet.of("localhost"))
+ .build();
+
+ ClientResponse response = clientDeploy(spec);
+ assertTrue(response.getStatus() / 100 == 2, "response is " + response);
+
+ // Expect app to be running
+ URI appUri = response.getLocation();
+ waitForApplicationToBeRunning(response.getLocation(), Duration.TEN_SECONDS);
+ assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-builder");
+
+ // Expect app to have the child-entity
+ Set<EntitySummary> entities = client().resource(appUri.toString() + "/entities")
+ .get(new GenericType<Set<EntitySummary>>() {});
+ assertEquals(entities.size(), 1);
+ assertEquals(Iterables.getOnlyElement(entities).getName(), "child1");
+ assertEquals(Iterables.getOnlyElement(entities).getType(), RestMockSimpleEntity.class.getCanonicalName());
+ }
+
+ @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+ public void testDeployApplicationYaml() throws Exception {
+ String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { serviceType: "+BasicApplication.class.getCanonicalName()+" } ] }";
+
+ ClientResponse response = client().resource("/v1/applications")
+ .entity(yaml, "application/x-yaml")
+ .post(ClientResponse.class);
+ assertTrue(response.getStatus()/100 == 2, "response is "+response);
+
+ // Expect app to be running
+ URI appUri = response.getLocation();
+ waitForApplicationToBeRunning(response.getLocation());
+ assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+ }
+
+ @Test
+ public void testReferenceCatalogEntity() throws Exception {
+ getManagementContext().getCatalog().addItems("{ name: "+BasicEntity.class.getName()+", "
+ + "services: [ { type: "+BasicEntity.class.getName()+" } ] }");
+
+ String yaml = "{ name: simple-app-yaml, location: localhost, services: [ { type: " + BasicEntity.class.getName() + " } ] }";
+
+ ClientResponse response = client().resource("/v1/applications")
+ .entity(yaml, "application/x-yaml")
+ .post(ClientResponse.class);
+ assertTrue(response.getStatus()/100 == 2, "response is "+response);
+
+ // Expect app to be running
+ URI appUri = response.getLocation();
+ waitForApplicationToBeRunning(response.getLocation());
+ assertEquals(client().resource(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
+
+ ClientResponse response2 = client().resource(appUri.getPath())
+ .delete(ClientResponse.class);
+ assertEquals(response2.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+ }
+
+ @Test
+ public void testDeployWithInvalidEntityType() {
+ try {
+ clientDeploy(ApplicationSpec.builder()
+ .name("invalid-app")
+ .entities(ImmutableSet.of(new EntitySpec("invalid-ent", "not.existing.entity")))
+ .locations(ImmutableSet.of("localhost"))
+ .build());
+
+ } catch (UniformInterfaceException e) {
+ ApiError error = e.getResponse().getEntity(ApiError.class);
+ assertEquals(error.getMessage(), "Undefined type 'not.existing.entity'");
+ }
+ }
+
+ @Test
+ public void testDeployWithInvalidLocation() {
+ try {
+ clientDeploy(ApplicationSpec.builder()
+ .name("invalid-app")
+ .entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName())))
+ .locations(ImmutableSet.of("3423"))
+ .build());
+
+ } catch (UniformInterfaceException e) {
+ ApiError error = e.getResponse().getEntity(ApiError.class);
+ assertEquals(error.getMessage(), "Undefined location '3423'");
+ }
+ }
+
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testListEntities() {
+ Set<EntitySummary> entities = client().resource("/v1/applications/simple-app/entities")
+ .get(new GenericType<Set<EntitySummary>>() {});
+
+ assertEquals(entities.size(), 2);
+
+ EntitySummary entity = Iterables.find(entities, withName("simple-ent"), null);
+ EntitySummary group = Iterables.find(entities, withName("simple-group"), null);
+ Assert.assertNotNull(entity);
+ Assert.assertNotNull(group);
+
+ client().resource(entity.getLinks().get("self")).get(ClientResponse.class);
+
+ Set<EntitySummary> children = client().resource(entity.getLinks().get("children"))
+ .get(new GenericType<Set<EntitySummary>>() {});
+ assertEquals(children.size(), 0);
+ }
+
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testListApplications() {
+ Set<ApplicationSummary> applications = client().resource("/v1/applications")
+ .get(new GenericType<Set<ApplicationSummary>>() { });
+ log.info("Applications listed are: " + applications);
+ for (ApplicationSummary app : applications) {
+ if (simpleSpec.getName().equals(app.getSpec().getName())) return;
+ }
+ Assert.fail("simple-app not found in list of applications: "+applications);
+ }
+
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testGetApplicationOnFire() {
+ Application app = Iterables.find(manager.getApplications(), EntityPredicates.displayNameEqualTo(simpleSpec.getName()));
+ Lifecycle origState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+
+ ApplicationSummary summary = client().resource("/v1/applications/"+app.getId())
+ .get(ApplicationSummary.class);
+ assertEquals(summary.getStatus(), Status.RUNNING);
+
+ app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
+ try {
+ ApplicationSummary summary2 = client().resource("/v1/applications/"+app.getId())
+ .get(ApplicationSummary.class);
+ log.info("Application: " + summary2);
+ assertEquals(summary2.getStatus(), Status.ERROR);
+
+ } finally {
+ app.sensors().set(Attributes.SERVICE_STATE_ACTUAL, origState);
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testFetchApplicationsAndEntity() {
+ Collection apps = client().resource("/v1/applications/fetch").get(Collection.class);
+ log.info("Applications fetched are: " + apps);
+
+ Map app = null;
+ for (Object appI : apps) {
+ Object name = ((Map) appI).get("name");
+ if ("simple-app".equals(name)) {
+ app = (Map) appI;
+ }
+ if (ImmutableSet.of("simple-ent", "simple-group").contains(name))
+ Assert.fail(name + " should not be listed at high level: " + apps);
+ }
+
+ Assert.assertNotNull(app);
+ Assert.assertFalse(Strings.isBlank((String) app.get("applicationId")),
+ "expected value for applicationId, was: " + app.get("applicationId"));
+ Assert.assertFalse(Strings.isBlank((String) app.get("serviceState")),
+ "expected value for serviceState, was: " + app.get("serviceState"));
+ Assert.assertNotNull(app.get("serviceUp"), "expected non-null value for serviceUp");
+
+ Collection children = (Collection) app.get("children");
+ Assert.assertEquals(children.size(), 2);
+
+ Map entitySummary = (Map) Iterables.find(children, withValueForKey("name", "simple-ent"), null);
+ Map groupSummary = (Map) Iterables.find(children, withValueForKey("name", "simple-group"), null);
+ Assert.assertNotNull(entitySummary);
+ Assert.assertNotNull(groupSummary);
+
+ String itemIds = app.get("id") + "," + entitySummary.get("id") + "," + groupSummary.get("id");
+ Collection entities = client().resource("/v1/applications/fetch?items="+itemIds)
+ .get(Collection.class);
+ log.info("Applications+Entities fetched are: " + entities);
+
+ Assert.assertEquals(entities.size(), apps.size() + 2);
+ Map entityDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-ent"), null);
+ Map groupDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-group"), null);
+ Assert.assertNotNull(entityDetails);
+ Assert.assertNotNull(groupDetails);
+
+ Assert.assertEquals(entityDetails.get("parentId"), app.get("id"));
+ Assert.assertNull(entityDetails.get("children"));
+ Assert.assertEquals(groupDetails.get("parentId"), app.get("id"));
+ Assert.assertNull(groupDetails.get("children"));
+
+ Collection entityGroupIds = (Collection) entityDetails.get("groupIds");
+ Assert.assertNotNull(entityGroupIds);
+ Assert.assertEquals(entityGroupIds.size(), 1);
+ Assert.assertEquals(entityGroupIds.iterator().next(), groupDetails.get("id"));
+
+ Collection groupMembers = (Collection) groupDetails.get("members");
+ Assert.assertNotNull(groupMembers);
+
+ for (Application appi : getManagementContext().getApplications()) {
+ Entities.dumpInfo(appi);
+ }
+ log.info("MEMBERS: " + groupMembers);
+
+ Assert.assertEquals(groupMembers.size(), 1);
+ Map entityMemberDetails = (Map) Iterables.find(groupMembers, withValueForKey("name", "simple-ent"), null);
+ Assert.assertNotNull(entityMemberDetails);
+ Assert.assertEquals(entityMemberDetails.get("id"), entityDetails.get("id"));
+ }
+
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testListSensors() {
+ Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
+ .get(new GenericType<Set<SensorSummary>>() { });
+ assertTrue(sensors.size() > 0);
+ SensorSummary sample = Iterables.find(sensors, new Predicate<SensorSummary>() {
+ @Override
+ public boolean apply(SensorSummary sensorSummary) {
+ return sensorSummary.getName().equals(RestMockSimpleEntity.SAMPLE_SENSOR.getName());
+ }
+ });
+ assertEquals(sample.getType(), "java.lang.String");
+ }
+
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testListConfig() {
+ Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
+ .get(new GenericType<Set<EntityConfigSummary>>() { });
+ assertTrue(config.size() > 0);
+ System.out.println(("CONFIG: " + config));
+ }
+
+ @Test(dependsOnMethods = "testListConfig")
+ public void testListConfig2() {
+ Set<EntityConfigSummary> config = client().resource("/v1/applications/simple-app/entities/simple-ent/config")
+ .get(new GenericType<Set<EntityConfigSummary>>() {});
+ assertTrue(config.size() > 0);
+ System.out.println(("CONFIG: " + config));
+ }
+
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testListEffectors() {
+ Set<EffectorSummary> effectors = client().resource("/v1/applications/simple-app/entities/simple-ent/effectors")
+ .get(new GenericType<Set<EffectorSummary>>() {});
+
+ assertTrue(effectors.size() > 0);
+
+ EffectorSummary sampleEffector = find(effectors, new Predicate<EffectorSummary>() {
+ @Override
+ public boolean apply(EffectorSummary input) {
+ return input.getName().equals("sampleEffector");
+ }
+ });
+ assertEquals(sampleEffector.getReturnType(), "java.lang.String");
+ }
+
+ @Test(dependsOnMethods = "testListSensors")
+ public void testTriggerSampleEffector() throws InterruptedException, IOException {
+ ClientResponse response = client()
+ .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+ .type(MediaType.APPLICATION_JSON_TYPE)
+ .post(ClientResponse.class, ImmutableMap.of("param1", "foo", "param2", 4));
+
+ assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+ String result = response.getEntity(String.class);
+ assertEquals(result, "foo4");
+ }
+
+ @Test(dependsOnMethods = "testListSensors")
+ public void testTriggerSampleEffectorWithFormData() throws InterruptedException, IOException {
+ MultivaluedMap<String, String> data = new MultivaluedMapImpl();
+ data.add("param1", "foo");
+ data.add("param2", "4");
+ ClientResponse response = client()
+ .resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+RestMockSimpleEntity.SAMPLE_EFFECTOR.getName())
+ .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
+ .post(ClientResponse.class, data);
+
+ assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+
+ String result = response.getEntity(String.class);
+ assertEquals(result, "foo4");
+ }
+
+ @Test(dependsOnMethods = "testTriggerSampleEffector")
+ public void testBatchSensorValues() {
+ WebResource resource = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state");
+ Map<String, Object> sensors = resource.get(new GenericType<Map<String, Object>>() {});
+ assertTrue(sensors.size() > 0);
+ assertEquals(sensors.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+ }
+
+ @Test(dependsOnMethods = "testBatchSensorValues")
+ public void testReadEachSensor() {
+ Set<SensorSummary> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors")
+ .get(new GenericType<Set<SensorSummary>>() {});
+
+ Map<String, String> readings = Maps.newHashMap();
+ for (SensorSummary sensor : sensors) {
+ try {
+ readings.put(sensor.getName(), client().resource(sensor.getLinks().get("self")).accept(MediaType.TEXT_PLAIN).get(String.class));
+ } catch (UniformInterfaceException uie) {
+ if (uie.getResponse().getStatus() == 204) { // no content
+ readings.put(sensor.getName(), null);
+ } else {
+ Exceptions.propagate(uie);
+ }
+ }
+ }
+
+ assertEquals(readings.get(RestMockSimpleEntity.SAMPLE_SENSOR.getName()), "foo4");
+ }
+
+ @Test(dependsOnMethods = "testTriggerSampleEffector")
+ public void testPolicyWhichCapitalizes() {
+ String policiesEndpoint = "/v1/applications/simple-app/entities/simple-ent/policies";
+ Set<PolicySummary> policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>(){});
+ assertEquals(policies.size(), 0);
+
+ ClientResponse response = client().resource(policiesEndpoint)
+ .queryParam("type", CapitalizePolicy.class.getCanonicalName())
+ .type(MediaType.APPLICATION_JSON_TYPE)
+ .post(ClientResponse.class, Maps.newHashMap());
+ assertEquals(response.getStatus(), 200);
+ PolicySummary policy = response.getEntity(PolicySummary.class);
+ assertNotNull(policy.getId());
+ String newPolicyId = policy.getId();
+ log.info("POLICY CREATED: " + newPolicyId);
+ policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+ assertEquals(policies.size(), 1);
+
+ Lifecycle status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+ log.info("POLICY STATUS: " + status);
+
+ response = client().resource(policiesEndpoint+"/"+newPolicyId+"/start")
+ .post(ClientResponse.class);
+ assertEquals(response.getStatus(), 204);
+ status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+ assertEquals(status, Lifecycle.RUNNING);
+
+ response = client().resource(policiesEndpoint+"/"+newPolicyId+"/stop")
+ .post(ClientResponse.class);
+ assertEquals(response.getStatus(), 204);
+ status = client().resource(policiesEndpoint + "/" + newPolicyId).get(Lifecycle.class);
+ assertEquals(status, Lifecycle.STOPPED);
+
+ response = client().resource(policiesEndpoint+"/"+newPolicyId+"/destroy")
+ .post(ClientResponse.class);
+ assertEquals(response.getStatus(), 204);
+
+ response = client().resource(policiesEndpoint+"/"+newPolicyId).get(ClientResponse.class);
+ log.info("POLICY STATUS RESPONSE AFTER DESTROY: " + response.getStatus());
+ assertEquals(response.getStatus(), 404);
+
+ policies = client().resource(policiesEndpoint).get(new GenericType<Set<PolicySummary>>() {});
+ assertEquals(0, policies.size());
+ }
+
+ @SuppressWarnings({ "rawtypes" })
+ @Test(dependsOnMethods = "testDeployApplication")
+ public void testLocatedLocation() {
+ log.info("starting testLocatedLocations");
+ testListApplications();
+
+ LocationInternal l = (LocationInternal) getManagementContext().getApplications().iterator().next().getLocations().iterator().next();
+ if (l.config().getLocalRaw(LocationConfigKeys.LATITUDE).isAbsent()) {
+ log.info("Supplying fake locations for localhost because could not be autodetected");
+ ((AbstractLocation) l).setHostGeoInfo(new HostGeoInfo("localhost", "localhost", 50, 0));
+ }
+ Map result = client().resource("/v1/locations/usage/LocatedLocations")
+ .get(Map.class);
+ log.info("LOCATIONS: " + result);
+ Assert.assertEquals(result.size(), 1);
+ Map details = (Map) result.values().iterator().next();
+ assertEquals(details.get("leafEntityCount"), 2);
+ }
+
+ @Test(dependsOnMethods = {"testListEffectors", "testFetchApplicationsAndEntity", "testTriggerSampleEffector", "testListApplications","testReadEachSensor","testPolicyWhichCapitalizes","testLocatedLocation"})
+ public void testDeleteApplication() throws TimeoutException, InterruptedException {
+ waitForPageFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
+ Collection<Application> apps = getManagementContext().getApplications();
+ log.info("Deleting simple-app from " + apps);
+ int size = apps.size();
+
+ ClientResponse response = client().resource("/v1/applications/simple-app")
+ .delete(ClientResponse.class);
+
+ assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode());
+ TaskSummary task = response.getEntity(TaskSummary.class);
+ assertTrue(task.getDescription().toLowerCase().contains("destroy"), task.getDescription());
+ assertTrue(task.getDescription().toLowerCase().contains("simple-app"), task.getDescription());
+
+ waitForPageNotFoundResponse("/v1/applications/simple-app", ApplicationSummary.class);
+
+ log.info("App appears gone, apps are: " + getManagementContext().getApplications());
+ // more logging above, for failure in the check below
+
+ Asserts.eventually(
+ EntityFunctions.applications(getManagementContext()),
+ Predicates.compose(Predicates.equalTo(size-1), CollectionFunctionals.sizeFunction()) );
+ }
+
+ @Test
+ public void testDisabledApplicationCatalog() throws TimeoutException, InterruptedException {
+ String itemSymbolicName = "my.catalog.item.id.for.disabling";
+ String itemVersion = "1.0";
+ String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+
+ // Deploy the catalog item
+ addTestCatalogItem(itemSymbolicName, "template", itemVersion, serviceType);
+ List<CatalogEntitySummary> itemSummaries = client().resource("/v1/catalog/applications")
+ .queryParam("fragment", itemSymbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+ CatalogItemSummary itemSummary = Iterables.getOnlyElement(itemSummaries);
+ String itemVersionedId = String.format("%s:%s", itemSummary.getSymbolicName(), itemSummary.getVersion());
+ assertEquals(itemSummary.getId(), itemVersionedId);
+
+ try {
+ // Create an app before disabling: this should work
+ String yaml = "{ name: my-app, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+ ClientResponse response = client().resource("/v1/applications")
+ .entity(yaml, "application/x-yaml")
+ .post(ClientResponse.class);
+ HttpAsserts.assertHealthyStatusCode(response.getStatus());
+ waitForPageFoundResponse("/v1/applications/my-app", ApplicationSummary.class);
+
+ // Deprecate
+ deprecateCatalogItem(itemSymbolicName, itemVersion, true);
+
+ // Create an app when deprecated: this should work
+ String yaml2 = "{ name: my-app2, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+ ClientResponse response2 = client().resource("/v1/applications")
+ .entity(yaml2, "application/x-yaml")
+ .post(ClientResponse.class);
+ HttpAsserts.assertHealthyStatusCode(response2.getStatus());
+ waitForPageFoundResponse("/v1/applications/my-app2", ApplicationSummary.class);
+
+ // Disable
+ disableCatalogItem(itemSymbolicName, itemVersion, true);
+
+ // Now try creating an app; this should fail because app is disabled
+ String yaml3 = "{ name: my-app3, location: localhost, services: [ { type: \""+itemVersionedId+"\" } ] }";
+ ClientResponse response3 = client().resource("/v1/applications")
+ .entity(yaml3, "application/x-yaml")
+ .post(ClientResponse.class);
+ HttpAsserts.assertClientErrorStatusCode(response3.getStatus());
- assertTrue(response3.getEntity(String.class).contains("cannot be matched"));
++ assertTrue(response3.getEntity(String.class).toLowerCase().contains("unable to match"));
+ waitForPageNotFoundResponse("/v1/applications/my-app3", ApplicationSummary.class);
+
+ } finally {
+ client().resource("/v1/applications/my-app")
+ .delete(ClientResponse.class);
+
+ client().resource("/v1/applications/my-app2")
+ .delete(ClientResponse.class);
+
+ client().resource("/v1/applications/my-app3")
+ .delete(ClientResponse.class);
+
+ client().resource("/v1/catalog/entities/"+itemVersionedId+"/"+itemVersion)
+ .delete(ClientResponse.class);
+ }
+ }
+
+ private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated) {
+ String id = String.format("%s:%s", symbolicName, version);
+ ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(ClientResponse.class, deprecated);
+ assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+ }
+
+ private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+ String id = String.format("%s:%s", symbolicName, version);
+ ClientResponse response = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(ClientResponse.class, disabled);
+ assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+ }
+
+ private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+ String yaml =
+ "brooklyn.catalog:\n"+
+ " id: " + catalogItemId + "\n"+
+ " name: My Catalog App\n"+
+ (itemType!=null ? " item_type: "+itemType+"\n" : "")+
+ " description: My description\n"+
+ " icon_url: classpath:///redis-logo.png\n"+
+ " version: " + version + "\n"+
+ "\n"+
+ "services:\n"+
+ "- type: " + service + "\n";
+
+ client().resource("/v1/catalog").post(yaml);
+ }
+ }
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/79b98c67/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --cc rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
index 0000000,4e4d79e..a810e6d
mode 000000,100644..100644
--- a/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
+++ b/rest/rest-server-jersey/src/test/java/org/apache/brooklyn/rest/resources/CatalogResourceTest.java
@@@ -1,0 -1,513 +1,515 @@@
+ /*
+ * 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.awt.*;
+ import java.io.IOException;
+ import java.net.URI;
+ import java.net.URL;
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+
+ import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+ import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+ import org.apache.brooklyn.core.test.entity.TestEntity;
+ import org.apache.brooklyn.policy.autoscaling.AutoScalerPolicy;
+ import org.apache.brooklyn.rest.domain.CatalogEntitySummary;
+ import org.apache.brooklyn.rest.domain.CatalogItemSummary;
+ import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
+ import org.apache.brooklyn.rest.domain.CatalogPolicySummary;
+ import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+ import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+ import org.apache.brooklyn.util.javalang.Reflections;
+ import org.apache.http.HttpHeaders;
+ import org.apache.http.entity.ContentType;
+ import org.eclipse.jetty.http.HttpStatus;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.BeforeClass;
+ import org.testng.annotations.Test;
+ import org.testng.reporters.Files;
+
+ import com.google.common.base.Joiner;
+ import com.google.common.collect.Iterables;
+ import com.sun.jersey.api.client.ClientResponse;
+ import com.sun.jersey.api.client.GenericType;
+
+ public class CatalogResourceTest extends BrooklynRestResourceTest {
+
+ private static final Logger log = LoggerFactory.getLogger(CatalogResourceTest.class);
+
+ private static String TEST_VERSION = "0.1.2";
+
+ @BeforeClass(alwaysRun=true)
+ @Override
+ public void setUp() throws Exception {
+ useLocalScannedCatalog();
+ super.setUp();
+ }
+
+ @Override
+ protected void addBrooklynResources() {
+ addResource(new CatalogResource());
+ }
+
+ @Test
+ /** based on CampYamlLiteTest */
+ public void testRegisterCustomEntityTopLevelSyntaxWithBundleWhereEntityIsFromCoreAndIconFromBundle() {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+ String symbolicName = "my.catalog.entity.id";
+ String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+ String yaml =
+ "brooklyn.catalog:\n"+
+ " id: " + symbolicName + "\n"+
+ " name: My Catalog App\n"+
+ " description: My description\n"+
+ " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+ " version: " + TEST_VERSION + "\n"+
+ " libraries:\n"+
+ " - url: " + bundleUrl + "\n"+
+ "\n"+
+ "services:\n"+
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+ ClientResponse response = client().resource("/v1/catalog")
+ .post(ClientResponse.class, yaml);
+
+ assertEquals(response.getStatus(), Response.Status.CREATED.getStatusCode());
+
+ CatalogEntitySummary entityItem = client().resource("/v1/catalog/entities/"+symbolicName + "/" + TEST_VERSION)
+ .get(CatalogEntitySummary.class);
+
+ Assert.assertNotNull(entityItem.getPlanYaml());
+ Assert.assertTrue(entityItem.getPlanYaml().contains("org.apache.brooklyn.core.test.entity.TestEntity"));
+
+ assertEquals(entityItem.getId(), ver(symbolicName));
+ assertEquals(entityItem.getSymbolicName(), symbolicName);
+ assertEquals(entityItem.getVersion(), TEST_VERSION);
+
+ // and internally let's check we have libraries
+ RegisteredType item = getManagementContext().getTypeRegistry().get(symbolicName, TEST_VERSION);
+ Assert.assertNotNull(item);
+ Collection<OsgiBundleWithUrl> libs = item.getLibraries();
+ assertEquals(libs.size(), 1);
+ assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl);
+
+ // now let's check other things on the item
+ assertEquals(entityItem.getName(), "My Catalog App");
+ assertEquals(entityItem.getDescription(), "My description");
+ assertEquals(URI.create(entityItem.getIconUrl()).getPath(), "/v1/catalog/icon/" + symbolicName + "/" + entityItem.getVersion());
+ assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
+
+ // an InterfacesTag should be created for every catalog item
+ assertEquals(entityItem.getTags().size(), 1);
+ Object tag = entityItem.getTags().iterator().next();
++ @SuppressWarnings("unchecked")
+ List<String> actualInterfaces = ((Map<String, List<String>>) tag).get("traits");
+ List<Class<?>> expectedInterfaces = Reflections.getAllInterfaces(TestEntity.class);
+ assertEquals(actualInterfaces.size(), expectedInterfaces.size());
+ for (Class<?> expectedInterface : expectedInterfaces) {
+ assertTrue(actualInterfaces.contains(expectedInterface.getName()));
+ }
+
+ byte[] iconData = client().resource("/v1/catalog/icon/" + symbolicName + "/" + TEST_VERSION).get(byte[].class);
+ assertEquals(iconData.length, 43);
+ }
+
+ @Test
++ // osgi fails in IDE, should work on CLI though
+ public void testRegisterOsgiPolicyTopLevelSyntax() {
+ TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+
+ String symbolicName = "my.catalog.policy.id";
+ String policyType = "org.apache.brooklyn.test.osgi.entities.SimplePolicy";
+ String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+
+ String yaml =
+ "brooklyn.catalog:\n"+
+ " id: " + symbolicName + "\n"+
+ " name: My Catalog App\n"+
+ " description: My description\n"+
+ " version: " + TEST_VERSION + "\n" +
+ " libraries:\n"+
+ " - url: " + bundleUrl + "\n"+
+ "\n"+
+ "brooklyn.policies:\n"+
+ "- type: " + policyType;
+
+ CatalogPolicySummary entityItem = Iterables.getOnlyElement( client().resource("/v1/catalog")
+ .post(new GenericType<Map<String,CatalogPolicySummary>>() {}, yaml).values() );
+
+ Assert.assertNotNull(entityItem.getPlanYaml());
+ Assert.assertTrue(entityItem.getPlanYaml().contains(policyType));
+ assertEquals(entityItem.getId(), ver(symbolicName));
+ assertEquals(entityItem.getSymbolicName(), symbolicName);
+ assertEquals(entityItem.getVersion(), TEST_VERSION);
+ }
+
+ @Test
+ public void testListAllEntities() {
+ List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+ .get(new GenericType<List<CatalogEntitySummary>>() {});
+ assertTrue(entities.size() > 0);
+ }
+
+ @Test
+ public void testListAllEntitiesAsItem() {
+ // ensure things are happily downcasted and unknown properties ignored (e.g. sensors, effectors)
+ List<CatalogItemSummary> entities = client().resource("/v1/catalog/entities")
+ .get(new GenericType<List<CatalogItemSummary>>() {});
+ assertTrue(entities.size() > 0);
+ }
+
+ @Test
+ public void testFilterListOfEntitiesByName() {
+ List<CatalogEntitySummary> entities = client().resource("/v1/catalog/entities")
+ .queryParam("fragment", "brOOkLynENTITYmiRrOr").get(new GenericType<List<CatalogEntitySummary>>() {});
+ assertEquals(entities.size(), 1);
+
+ log.info("BrooklynEntityMirror-like entities are: " + entities);
+
+ List<CatalogEntitySummary> entities2 = client().resource("/v1/catalog/entities")
+ .queryParam("regex", "[Bb]ro+klynEntityMi[ro]+").get(new GenericType<List<CatalogEntitySummary>>() {});
+ assertEquals(entities2.size(), 1);
+
+ assertEquals(entities, entities2);
+
+ List<CatalogEntitySummary> entities3 = client().resource("/v1/catalog/entities")
+ .queryParam("fragment", "bweqQzZ").get(new GenericType<List<CatalogEntitySummary>>() {});
+ assertEquals(entities3.size(), 0);
+
+ List<CatalogEntitySummary> entities4 = client().resource("/v1/catalog/entities")
+ .queryParam("regex", "bweq+z+").get(new GenericType<List<CatalogEntitySummary>>() {});
+ assertEquals(entities4.size(), 0);
+ }
+
+ @Test
+ @Deprecated
+ // If we move to using a yaml catalog item, the details will be of the wrapping app,
+ // not of the entity itself, so the test won't make sense any more.
+ public void testGetCatalogEntityDetails() {
+ CatalogEntitySummary details = client()
+ .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode"))
+ .get(CatalogEntitySummary.class);
+ assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
+ }
+
+ @Test
+ @Deprecated
+ // If we move to using a yaml catalog item, the details will be of the wrapping app,
+ // not of the entity itself, so the test won't make sense any more.
+ public void testGetCatalogEntityPlusVersionDetails() {
+ CatalogEntitySummary details = client()
+ .resource(URI.create("/v1/catalog/entities/org.apache.brooklyn.entity.brooklynnode.BrooklynNode:0.0.0.SNAPSHOT"))
+ .get(CatalogEntitySummary.class);
+ assertTrue(details.toString().contains("download.url"), "expected more config, only got: "+details);
+ }
+
+ @Test
+ public void testGetCatalogEntityIconDetails() throws IOException {
+ String catalogItemId = "testGetCatalogEntityIconDetails";
+ addTestCatalogItemBrooklynNodeAsEntity(catalogItemId);
+ ClientResponse response = client().resource(URI.create("/v1/catalog/icon/" + catalogItemId + "/" + TEST_VERSION))
+ .get(ClientResponse.class);
+ response.bufferEntity();
+ Assert.assertEquals(response.getStatus(), 200);
+ Assert.assertEquals(response.getType(), MediaType.valueOf("image/jpeg"));
+ Image image = Toolkit.getDefaultToolkit().createImage(Files.readFile(response.getEntityInputStream()));
+ Assert.assertNotNull(image);
+ }
+
+ private void addTestCatalogItemBrooklynNodeAsEntity(String catalogItemId) {
+ addTestCatalogItem(catalogItemId, null, TEST_VERSION, "org.apache.brooklyn.entity.brooklynnode.BrooklynNode");
+ }
+
+ private void addTestCatalogItem(String catalogItemId, String itemType, String version, String service) {
+ String yaml =
+ "brooklyn.catalog:\n"+
+ " id: " + catalogItemId + "\n"+
+ " name: My Catalog App\n"+
+ (itemType!=null ? " item_type: "+itemType+"\n" : "")+
+ " description: My description\n"+
+ " icon_url: classpath:///brooklyn-test-logo.jpg\n"+
+ " version: " + version + "\n"+
+ "\n"+
+ "services:\n"+
+ "- type: " + service + "\n";
+
+ client().resource("/v1/catalog").post(yaml);
+ }
+
+ private enum DeprecateStyle {
+ NEW_STYLE,
+ LEGACY_STYLE
+ }
+ private void deprecateCatalogItem(DeprecateStyle style, String symbolicName, String version, boolean deprecated) {
+ String id = String.format("%s:%s", symbolicName, version);
+ ClientResponse response;
+ if (style == DeprecateStyle.NEW_STYLE) {
+ response = client().resource(String.format("/v1/catalog/entities/%s/deprecated", id))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(ClientResponse.class, deprecated);
+ } else {
+ response = client().resource(String.format("/v1/catalog/entities/%s/deprecated/%s", id, deprecated))
+ .post(ClientResponse.class);
+ }
+ assertEquals(response.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+ }
+
+ private void disableCatalogItem(String symbolicName, String version, boolean disabled) {
+ String id = String.format("%s:%s", symbolicName, version);
+ ClientResponse getDisableResponse = client().resource(String.format("/v1/catalog/entities/%s/disabled", id))
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .post(ClientResponse.class, disabled);
+ assertEquals(getDisableResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+ }
+
+ @Test
+ public void testListPolicies() {
+ Set<CatalogPolicySummary> policies = client().resource("/v1/catalog/policies")
+ .get(new GenericType<Set<CatalogPolicySummary>>() {});
+
+ assertTrue(policies.size() > 0);
+ CatalogItemSummary asp = null;
+ for (CatalogItemSummary p : policies) {
+ if (AutoScalerPolicy.class.getName().equals(p.getType()))
+ asp = p;
+ }
+ Assert.assertNotNull(asp, "didn't find AutoScalerPolicy");
+ }
+
+ @Test
+ public void testLocationAddGetAndRemove() {
+ String symbolicName = "my.catalog.location.id";
+ String locationType = "localhost";
+ String yaml = Joiner.on("\n").join(
+ "brooklyn.catalog:",
+ " id: " + symbolicName,
+ " name: My Catalog Location",
+ " description: My description",
+ " version: " + TEST_VERSION,
+ "",
+ "brooklyn.locations:",
+ "- type: " + locationType);
+
+ // Create location item
+ Map<String, CatalogLocationSummary> items = client().resource("/v1/catalog")
+ .post(new GenericType<Map<String,CatalogLocationSummary>>() {}, yaml);
+ CatalogLocationSummary locationItem = Iterables.getOnlyElement(items.values());
+
+ Assert.assertNotNull(locationItem.getPlanYaml());
+ Assert.assertTrue(locationItem.getPlanYaml().contains(locationType));
+ assertEquals(locationItem.getId(), ver(symbolicName));
+ assertEquals(locationItem.getSymbolicName(), symbolicName);
+ assertEquals(locationItem.getVersion(), TEST_VERSION);
+
+ // Retrieve location item
+ CatalogLocationSummary location = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+ .get(CatalogLocationSummary.class);
+ assertEquals(location.getSymbolicName(), symbolicName);
+
+ // Retrieve all locations
+ Set<CatalogLocationSummary> locations = client().resource("/v1/catalog/locations")
+ .get(new GenericType<Set<CatalogLocationSummary>>() {});
+ boolean found = false;
+ for (CatalogLocationSummary contender : locations) {
+ if (contender.getSymbolicName().equals(symbolicName)) {
+ found = true;
+ break;
+ }
+ }
+ Assert.assertTrue(found, "contenders="+locations);
+
+ // Delete
+ ClientResponse deleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+ .delete(ClientResponse.class);
+ assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+ ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/locations/"+symbolicName+"/"+TEST_VERSION)
+ .get(ClientResponse.class);
+ assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ public void testDeleteCustomEntityFromCatalog() {
+ String symbolicName = "my.catalog.app.id.to.subsequently.delete";
+ String yaml =
+ "name: "+symbolicName+"\n"+
+ // FIXME name above should be unnecessary when brooklyn.catalog below is working
+ "brooklyn.catalog:\n"+
+ " id: " + symbolicName + "\n"+
+ " name: My Catalog App To Be Deleted\n"+
+ " description: My description\n"+
+ " version: " + TEST_VERSION + "\n"+
+ "\n"+
+ "services:\n"+
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+ client().resource("/v1/catalog")
+ .post(ClientResponse.class, yaml);
+
+ ClientResponse deleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+ .delete(ClientResponse.class);
+
+ assertEquals(deleteResponse.getStatus(), Response.Status.NO_CONTENT.getStatusCode());
+
+ ClientResponse getPostDeleteResponse = client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+ .get(ClientResponse.class);
+ assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
+ }
+
+ @Test
+ public void testSetDeprecated() {
+ runSetDeprecated(DeprecateStyle.NEW_STYLE);
+ }
+
+ // Uses old-style "/v1/catalog/{itemId}/deprecated/true", rather than the "true" in the request body.
+ @Test
+ @Deprecated
+ public void testSetDeprecatedLegacy() {
+ runSetDeprecated(DeprecateStyle.LEGACY_STYLE);
+ }
+
+ protected void runSetDeprecated(DeprecateStyle style) {
+ String symbolicName = "my.catalog.item.id.for.deprecation";
+ String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+ addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+ addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+ try {
+ List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
+ .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+ assertEquals(applications.size(), 2);
+ CatalogItemSummary summary0 = applications.get(0);
+ CatalogItemSummary summary1 = applications.get(1);
+
+ // Deprecate: that app should be excluded
+ deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), true);
+
+ List<CatalogEntitySummary> applicationsAfterDeprecation = client().resource("/v1/catalog/applications")
+ .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+
+ assertEquals(applicationsAfterDeprecation.size(), 1);
+ assertTrue(applicationsAfterDeprecation.contains(summary1));
+
+ // Un-deprecate: that app should be included again
+ deprecateCatalogItem(style, summary0.getSymbolicName(), summary0.getVersion(), false);
+
+ List<CatalogEntitySummary> applicationsAfterUnDeprecation = client().resource("/v1/catalog/applications")
+ .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+
+ assertEquals(applications, applicationsAfterUnDeprecation);
+ } finally {
+ client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+ .delete(ClientResponse.class);
+ client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
+ .delete(ClientResponse.class);
+ }
+ }
+
+ @Test
+ public void testSetDisabled() {
+ String symbolicName = "my.catalog.item.id.for.disabling";
+ String serviceType = "org.apache.brooklyn.entity.stock.BasicApplication";
+ addTestCatalogItem(symbolicName, "template", TEST_VERSION, serviceType);
+ addTestCatalogItem(symbolicName, "template", "2.0", serviceType);
+ try {
+ List<CatalogEntitySummary> applications = client().resource("/v1/catalog/applications")
+ .queryParam("fragment", symbolicName).queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+ assertEquals(applications.size(), 2);
+ CatalogItemSummary summary0 = applications.get(0);
+ CatalogItemSummary summary1 = applications.get(1);
+
+ // Disable: that app should be excluded
+ disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), true);
+
+ List<CatalogEntitySummary> applicationsAfterDisabled = client().resource("/v1/catalog/applications")
+ .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+
+ assertEquals(applicationsAfterDisabled.size(), 1);
+ assertTrue(applicationsAfterDisabled.contains(summary1));
+
+ // Un-disable: that app should be included again
+ disableCatalogItem(summary0.getSymbolicName(), summary0.getVersion(), false);
+
+ List<CatalogEntitySummary> applicationsAfterUnDisabled = client().resource("/v1/catalog/applications")
+ .queryParam("fragment", "basicapp").queryParam("allVersions", "true").get(new GenericType<List<CatalogEntitySummary>>() {});
+
+ assertEquals(applications, applicationsAfterUnDisabled);
+ } finally {
+ client().resource("/v1/catalog/entities/"+symbolicName+"/"+TEST_VERSION)
+ .delete(ClientResponse.class);
+ client().resource("/v1/catalog/entities/"+symbolicName+"/"+"2.0")
+ .delete(ClientResponse.class);
+ }
+ }
+
+ @Test
+ public void testAddUnreachableItem() {
+ addInvalidCatalogItem("http://0.0.0.0/can-not-connect");
+ }
+
+ @Test
+ public void testAddInvalidItem() {
+ //equivalent to HTTP response 200 text/html
+ addInvalidCatalogItem("classpath://not-a-jar-file.txt");
+ }
+
+ @Test
+ public void testAddMissingItem() {
+ //equivalent to HTTP response 404 text/html
+ addInvalidCatalogItem("classpath://missing-jar-file.txt");
+ }
+
+ private void addInvalidCatalogItem(String bundleUrl) {
+ String symbolicName = "my.catalog.entity.id";
+ String yaml =
+ "brooklyn.catalog:\n"+
+ " id: " + symbolicName + "\n"+
+ " name: My Catalog App\n"+
+ " description: My description\n"+
+ " icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n"+
+ " version: " + TEST_VERSION + "\n"+
+ " libraries:\n"+
+ " - url: " + bundleUrl + "\n"+
+ "\n"+
+ "services:\n"+
+ "- type: org.apache.brooklyn.core.test.entity.TestEntity\n";
+
+ ClientResponse response = client().resource("/v1/catalog")
+ .post(ClientResponse.class, yaml);
+
- assertEquals(response.getStatus(), HttpStatus.INTERNAL_SERVER_ERROR_500);
++ assertEquals(response.getStatus(), HttpStatus.BAD_REQUEST_400);
+ }
+
+ private static String ver(String id) {
+ return CatalogUtils.getVersionedId(id, TEST_VERSION);
+ }
+ }