Repository: incubator-brooklyn Updated Branches: refs/heads/master c206ec907 -> 7aa53e0bf
Flag to ignore missing bundles errors when resetting the catalog Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/8a81071b Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/8a81071b Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/8a81071b Branch: refs/heads/master Commit: 8a81071ba81b870b08af0ef122992853d8fdba0c Parents: 776ad43 Author: Svetoslav Neykov <[email protected]> Authored: Tue May 26 11:19:13 2015 +0300 Committer: Svetoslav Neykov <[email protected]> Committed: Tue May 26 12:27:29 2015 +0300 ---------------------------------------------------------------------- .../catalog/internal/BasicBrooklynCatalog.java | 6 +- .../brooklyn/catalog/internal/CatalogDo.java | 38 ++-- .../main/java/brooklyn/rest/api/CatalogApi.java | 5 +- .../rest/resources/CatalogResource.java | 4 +- .../src/main/resources/not-a-jar-file.txt | 18 ++ .../src/main/resources/reset-catalog.xml | 37 ++++ .../rest/resources/CatalogResetTest.java | 180 +++++++++++++++++++ .../rest/resources/CatalogResourceTest.java | 39 ++++ 8 files changed, 311 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java index b49a240..15f4026 100644 --- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java @@ -128,6 +128,10 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { } public void reset(CatalogDto dto) { + reset(dto, true); + } + + public void reset(CatalogDto dto, boolean failOnLoadError) { // Unregister all existing persisted items. for (CatalogItem<?, ?> toRemove : getCatalogItems()) { if (log.isTraceEnabled()) { @@ -137,7 +141,7 @@ public class BasicBrooklynCatalog implements BrooklynCatalog { } CatalogDo catalog = new CatalogDo(mgmt, dto); CatalogUtils.logDebugOrTraceIfRebinding(log, "Resetting "+this+" catalog to "+dto); - catalog.load(mgmt, null); + catalog.load(mgmt, null, failOnLoadError); CatalogUtils.logDebugOrTraceIfRebinding(log, "Reloaded catalog for "+this+", now switching"); this.catalog = catalog; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/core/src/main/java/brooklyn/catalog/internal/CatalogDo.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogDo.java b/core/src/main/java/brooklyn/catalog/internal/CatalogDo.java index 0efe85c..72d597b 100644 --- a/core/src/main/java/brooklyn/catalog/internal/CatalogDo.java +++ b/core/src/main/java/brooklyn/catalog/internal/CatalogDo.java @@ -79,11 +79,16 @@ public class CatalogDo { return load(mgmt, parent); } + /** Calls {@link #load(ManagementContext, CatalogDo, boolean)} failing on load errors. */ + public synchronized CatalogDo load(ManagementContext mgmt, CatalogDo parent) { + return load(mgmt, parent, true); + } + /** causes all URL-based catalogs to have their manifests loaded, * and all scanning-based classpaths to scan the classpaths * (but does not load all JARs) */ - public synchronized CatalogDo load(ManagementContext mgmt, CatalogDo parent) { + public synchronized CatalogDo load(ManagementContext mgmt, CatalogDo parent, boolean failOnLoadError) { if (isLoaded()) { if (mgmt!=null && !Objects.equal(mgmt, this.mgmt)) { throw new IllegalStateException("Cannot set mgmt "+mgmt+" on "+this+" after catalog is loaded"); @@ -91,13 +96,13 @@ public class CatalogDo { log.debug("Catalog "+this+" is already loaded"); return this; } - loadThisCatalog(mgmt, parent); - loadChildrenCatalogs(); + loadThisCatalog(mgmt, parent, failOnLoadError); + loadChildrenCatalogs(failOnLoadError); buildCaches(); return this; } - protected synchronized void loadThisCatalog(ManagementContext mgmt, CatalogDo parent) { + protected synchronized void loadThisCatalog(ManagementContext mgmt, CatalogDo parent, boolean failOnLoadError) { if (isLoaded()) return; CatalogUtils.logDebugOrTraceIfRebinding(log, "Loading catalog {} into {}", this, parent); if (this.parent!=null && !this.parent.equals(parent)) @@ -108,7 +113,7 @@ public class CatalogDo { this.mgmt = mgmt; dto.populate(); loadCatalogClasspath(); - loadCatalogItems(); + loadCatalogItems(failOnLoadError); isLoaded = true; synchronized (this) { notifyAll(); @@ -126,11 +131,20 @@ public class CatalogDo { } } - private void loadCatalogItems() { + private void loadCatalogItems(boolean failOnLoadError) { Iterable<CatalogItemDtoAbstract<?, ?>> entries = dto.getUniqueEntries(); if (entries!=null) { for (CatalogItemDtoAbstract<?,?> entry : entries) { - CatalogUtils.installLibraries(mgmt, entry.getLibraries()); + try { + CatalogUtils.installLibraries(mgmt, entry.getLibraries()); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + if (failOnLoadError) { + Exceptions.propagate(e); + } else { + log.error("Loading bundles for catalog item " + entry + " failed: " + e.getMessage(), e); + } + } } } } @@ -147,18 +161,18 @@ public class CatalogDo { } } - protected void loadChildrenCatalogs() { + protected void loadChildrenCatalogs(boolean failOnLoadError) { if (dto.catalogs!=null) { for (CatalogDto child: dto.catalogs) { - loadCatalog(child); + loadCatalog(child, failOnLoadError); } } } - CatalogDo loadCatalog(CatalogDto child) { + CatalogDo loadCatalog(CatalogDto child, boolean failOnLoadError) { CatalogDo childL = new CatalogDo(child); childrenCatalogs.add(childL); - childL.load(mgmt, this); + childL.load(mgmt, this, failOnLoadError); childrenClassLoader.addFirst(childL.getRecursiveClassLoader()); clearCache(false); return childL; @@ -248,7 +262,7 @@ public class CatalogDo { dto.catalogs.add(child); if (!isLoaded()) return null; - return loadCatalog(child); + return loadCatalog(child, true); } public synchronized void addToClasspath(String ...urls) { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java ---------------------------------------------------------------------- diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java index 49549fd..d96de81 100644 --- a/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java +++ b/usage/rest-api/src/main/java/brooklyn/rest/api/CatalogApi.java @@ -76,7 +76,10 @@ public interface CatalogApi { @ApiOperation(value = "Resets the catalog to the given (XML) format") public Response resetXml( @ApiParam(name = "xml", value = "XML descriptor of the entire catalog to install", required = true) - @Valid String xml); + @Valid String xml, + @ApiParam(name ="ignoreErrors", value ="Don't fail on invalid bundles, log the errors only") + @QueryParam("ignoreErrors") @DefaultValue("false") + boolean ignoreErrors); /** @deprecated since 0.7.0 use {@link #deleteEntity(String, String)} */ @Deprecated http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java index a93f9a0..5332bb0 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java @@ -127,14 +127,14 @@ public class CatalogResource extends AbstractBrooklynRestResource implements Cat } @Override - public Response resetXml(String xml) { + public Response resetXml(String xml, boolean ignoreErrors) { if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_CATALOG_ITEM, null) || !Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.ADD_CATALOG_ITEM, null)) { throw WebResourceUtils.unauthorized("User '%s' is not authorized to modify catalog", Entitlements.getEntitlementContext().user()); } - ((BasicBrooklynCatalog)mgmt().getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "REST reset")); + ((BasicBrooklynCatalog)mgmt().getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "REST reset"), !ignoreErrors); return Response.ok().build(); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/usage/rest-server/src/main/resources/not-a-jar-file.txt ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/resources/not-a-jar-file.txt b/usage/rest-server/src/main/resources/not-a-jar-file.txt new file mode 100644 index 0000000..fbc22fe --- /dev/null +++ b/usage/rest-server/src/main/resources/not-a-jar-file.txt @@ -0,0 +1,18 @@ +# 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. + +Test loading of malformed jar file \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/usage/rest-server/src/main/resources/reset-catalog.xml ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/resources/reset-catalog.xml b/usage/rest-server/src/main/resources/reset-catalog.xml new file mode 100644 index 0000000..ce43857 --- /dev/null +++ b/usage/rest-server/src/main/resources/reset-catalog.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<catalog> + <name>Brooklyn Demos</name> + + <template type="brooklyn.entity.basic.BasicApplication" name="Basic application" /> + <template type="brooklyn.osgi.tests.SimpleApplication" name="Simple OSGi application"> + <libraries> + <bundle>${bundle-location}</bundle> + </libraries> + </template> + <catalog> + <name>Nested catalog</name> + <template type="brooklyn.osgi.tests.SimpleApplication" name="Simple OSGi application"> + <libraries> + <bundle>${bundle-location}</bundle> + </libraries> + </template> + </catalog> +</catalog> http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResetTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResetTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResetTest.java new file mode 100644 index 0000000..1c4f6ff --- /dev/null +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResetTest.java @@ -0,0 +1,180 @@ +/* + * 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 brooklyn.rest.resources; + +import static org.testng.Assert.assertNotNull; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; + +import javax.ws.rs.core.MediaType; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.bootstrap.HttpServer; +import org.apache.http.impl.bootstrap.ServerBootstrap; +import org.apache.http.message.BasicHeader; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpProcessor; +import org.apache.http.protocol.HttpRequestHandler; +import org.apache.http.protocol.ImmutableHttpProcessor; +import org.apache.http.protocol.ResponseConnControl; +import org.apache.http.protocol.ResponseContent; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import brooklyn.catalog.BrooklynCatalog; +import brooklyn.rest.testing.BrooklynRestResourceTest; +import brooklyn.util.ResourceUtils; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.net.Networking; + +import com.sun.jersey.api.client.UniformInterfaceException; + +public class CatalogResetTest extends BrooklynRestResourceTest { + + private HttpServer server; + private String serverUrl; + + @BeforeClass(alwaysRun=true) + @Override + public void setUp() throws Exception { + useLocalScannedCatalog(); + super.setUp(); + HttpProcessor httpProcessor = new ImmutableHttpProcessor( + new ResponseContent(), + new ResponseConnControl()); + + int port = Networking.nextAvailablePort(50505); + server = ServerBootstrap.bootstrap() + .setListenerPort(port) + .setLocalAddress(InetAddress.getLocalHost()) + .setHttpProcessor(httpProcessor) + .registerHandler("/404", new ResponseHandler().code(HttpStatus.SC_NOT_FOUND).response("Not Found")) + .registerHandler("/200", new ResponseHandler().response("OK")) + .create(); + server.start(); + serverUrl = new URL("http", server.getInetAddress().getHostAddress(), server.getLocalPort(), "").toExternalForm(); + } + + @Override + protected void addBrooklynResources() { + addResource(new CatalogResource()); + } + + @AfterClass(alwaysRun=true) + @Override + public void tearDown() throws Exception { + super.tearDown(); + server.stop(); + } + + @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500") + public void testConnectionError() throws Exception { + reset("http://0.0.0.0/can-not-connect", false); + } + + @Test + public void testConnectionErrorIgnore() throws Exception { + reset("http://0.0.0.0/can-not-connect", true); + } + + @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500") + public void testResourceMissingError() throws Exception { + reset(serverUrl + "/404", false); + } + + @Test + public void testResourceMissingIgnore() throws Exception { + reset(serverUrl + "/404", true); + } + + @Test(expectedExceptions=UniformInterfaceException.class, expectedExceptionsMessageRegExp="Client response status: 500") + public void testResourceInvalidError() throws Exception { + reset(serverUrl + "/200", false); + } + + @Test + public void testResourceInvalidIgnore() throws Exception { + reset(serverUrl + "/200", true); + } + + private void reset(String bundleLocation, boolean ignoreErrors) throws Exception { + String xml = ResourceUtils.create(this).getResourceAsString("classpath://reset-catalog.xml"); + client().resource("/v1/catalog/reset") + .queryParam("ignoreErrors", Boolean.toString(ignoreErrors)) + .header("Content-type", MediaType.APPLICATION_XML) + .post(xml.replace("${bundle-location}", bundleLocation)); + //if above succeeds assert catalog contents + assertItems(); + } + + private void assertItems() { + BrooklynCatalog catalog = getManagementContext().getCatalog(); + assertNotNull(catalog.getCatalogItem("brooklyn.entity.basic.BasicApplication", BrooklynCatalog.DEFAULT_VERSION)); + assertNotNull(catalog.getCatalogItem("brooklyn.osgi.tests.SimpleApplication", BrooklynCatalog.DEFAULT_VERSION)); + } + + private static class ResponseHandler implements HttpRequestHandler { + private HttpEntity entity; + private int responseCode = HttpStatus.SC_OK; + private Collection<Header> headers = new ArrayList<Header>(); + + public ResponseHandler response(String response) { + try { + this.entity = new StringEntity(response); + } catch (UnsupportedEncodingException e) { + throw Exceptions.propagate(e); + } + return this; + } + + public ResponseHandler code(int responseCode) { + this.responseCode = responseCode; + return this; + } + + @SuppressWarnings("unused") + public ResponseHandler header(String name, String value) { + headers.add(new BasicHeader(name, value)); + return this; + } + + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { + for (Header h : headers) { + response.setHeader(h); + } + + response.setStatusCode(responseCode); + response.setEntity(entity); + } + + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a81071b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java index 69dc58d..2255d7d 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java @@ -33,6 +33,7 @@ import java.util.Set; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.eclipse.jetty.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -387,6 +388,44 @@ public class CatalogResourceTest extends BrooklynRestResourceTest { assertEquals(applications, applicationsAfterUnDeprecation); } + @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:/brooklyn/osgi/tests/icon.gif\n"+ + " version: " + TEST_VERSION + "\n"+ + " libraries:\n"+ + " - url: " + bundleUrl + "\n"+ + "\n"+ + "services:\n"+ + "- type: brooklyn.test.entity.TestEntity\n"; + + ClientResponse response = client().resource("/v1/catalog") + .post(ClientResponse.class, yaml); + + assertEquals(response.getStatus(), HttpStatus.INTERNAL_SERVER_ERROR_500); + } + private static String ver(String id) { return CatalogUtils.getVersionedId(id, TEST_VERSION); }
