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);
     }

Reply via email to