brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From m4rkmcke...@apache.org
Subject [1/3] brooklyn-server git commit: Adds `PUT /applications/{appid}`
Date Tue, 01 Aug 2017 15:25:13 GMT
Repository: brooklyn-server
Updated Branches:
  refs/heads/master 2bad5261a -> 092edf1eb


Adds `PUT /applications/{appid}`

For deploying an app with a pre-defined app-id.
This feature is marked as beta.


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/3cd14917
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/3cd14917
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/3cd14917

Branch: refs/heads/master
Commit: 3cd14917cfbb83c90084b50086fe5b8e61ad5cc8
Parents: d73a91d
Author: Aled Sage <aled.sage@gmail.com>
Authored: Tue Jul 25 17:25:31 2017 +0100
Committer: Aled Sage <aled.sage@gmail.com>
Committed: Fri Jul 28 10:24:28 2017 +0100

----------------------------------------------------------------------
 .../core/mgmt/EntityManagementUtils.java        |  13 ++-
 .../mgmt/internal/EntityManagerInternal.java    |  10 ++
 .../mgmt/internal/IdAlreadyExistsException.java |  36 +++++++
 .../core/mgmt/internal/LocalEntityManager.java  |  34 ++++--
 .../internal/NonDeploymentEntityManager.java    |  10 ++
 .../core/objs/proxy/InternalEntityFactory.java  |  28 ++---
 .../core/entity/proxying/EntityManagerTest.java |  23 ++++
 .../proxying/InternalEntityFactoryTest.java     |  17 ++-
 .../mgmt/osgi/OsgiVersionMoreEntityTest.java    |   3 +-
 .../brooklyn/rest/api/ApplicationApi.java       |  28 ++++-
 .../rest/resources/ApplicationResource.java     |  39 +++++--
 .../rest/resources/ApplicationResourceTest.java | 105 +++++++++++++++++++
 .../rest/testing/BrooklynRestResourceTest.java  |  11 ++
 13 files changed, 319 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
index 8fb6345..a036622 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
@@ -40,6 +40,7 @@ import org.apache.brooklyn.core.effector.Effectors;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityFunctions;
 import org.apache.brooklyn.core.entity.trait.Startable;
+import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal;
 import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
 import org.apache.brooklyn.entity.stock.BasicApplication;
 import org.apache.brooklyn.util.collections.MutableList;
@@ -51,6 +52,8 @@ import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -80,7 +83,15 @@ public class EntityManagementUtils {
 
     /** creates an application from the given app spec, managed by the given management context
*/
     public static <T extends Application> T createUnstarted(ManagementContext mgmt,
EntitySpec<T> spec) {
-        T app = mgmt.getEntityManager().createEntity(spec);
+        return createUnstarted(mgmt, spec, Optional.absent());
+    }
+
+    /**
+     * As {@link #createUnstarted(ManagementContext, EntitySpec)}, but uses the given entity
id (if present).
+     */
+    @Beta
+    public static <T extends Application> T createUnstarted(ManagementContext mgmt,
EntitySpec<T> spec, Optional<String> entityId) {
+        T app = ((EntityManagerInternal)mgmt.getEntityManager()).createEntity(spec, entityId);
         return app;
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java
index 7bad213..9d0ab49 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/EntityManagerInternal.java
@@ -20,8 +20,12 @@ package org.apache.brooklyn.core.mgmt.internal;
 
 import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.mgmt.EntityManager;
 
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+
 public interface EntityManagerInternal extends EntityManager, BrooklynObjectManagerInternal<Entity>
{
 
     /** gets all entities currently known to the application, including entities that are
not yet managed */
@@ -29,4 +33,10 @@ public interface EntityManagerInternal extends EntityManager, BrooklynObjectMana
 
     public Iterable<String> getEntityIds();
     
+    /**
+     * Same as {@link #createEntity(EntitySpec)}, but takes an optional entity id that will
be 
+     * used for the entity.
+     */
+    @Beta
+    <T extends Entity> T createEntity(EntitySpec<T> spec, Optional<String>
entityId);
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/IdAlreadyExistsException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/IdAlreadyExistsException.java
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/IdAlreadyExistsException.java
new file mode 100644
index 0000000..b2c23bd
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/IdAlreadyExistsException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.core.mgmt.internal;
+
+/**
+ * Indicates that there is an id conflict (e.g. when creating an app using a pre-defined
id,
+ * using {@code PUT /applications/abcdefghijklm}).
+ */
+public class IdAlreadyExistsException extends RuntimeException {
+
+    private static final long serialVersionUID = -602477310528752776L;
+
+    public IdAlreadyExistsException(String msg) {
+        super(msg);
+    }
+    
+    public IdAlreadyExistsException(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
index 155286b..8d5ddf6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/LocalEntityManager.java
@@ -69,6 +69,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
@@ -143,12 +144,18 @@ public class LocalEntityManager implements EntityManagerInternal {
         if (!isRunning()) throw new IllegalStateException("Management context no longer running");
         return entityTypeRegistry;
     }
+
+    @Override
+    public <T extends Entity> T createEntity(EntitySpec<T> spec) {
+        return createEntity(spec, Optional.absent());
+    }
     
+    @Beta
     @SuppressWarnings("unchecked")
     @Override
-    public <T extends Entity> T createEntity(EntitySpec<T> spec) {
+    public <T extends Entity> T createEntity(EntitySpec<T> spec, Optional<String>
entityId) {
         try {
-            T entity = entityFactory.createEntity(spec);
+            T entity = entityFactory.createEntity(spec, entityId);
             Entity proxy = ((AbstractEntity)entity).getProxy();
             checkNotNull(proxy, "proxy for entity %s, spec %s", entity, spec);
             
@@ -263,7 +270,9 @@ public class LocalEntityManager implements EntityManagerInternal {
     
     @Override
     public boolean isManaged(Entity e) {
-        return (isRunning() && getEntity(e.getId()) != null);
+        // Confirm we know about this entity (by id), and that it is the same entity instance
+        // (rather than just a different unmanaged entity with the same id).
+        return (isRunning() && getEntity(e.getId()) != null) && (entitiesById.get(e.getId())
== deproxyIfNecessary(e));
     }
     
     boolean isPreRegistered(Entity e) {
@@ -670,23 +679,26 @@ public class LocalEntityManager implements EntityManagerInternal {
         Entity old = entitiesById.get(e.getId());
         
         if (old!=null && mode.wasNotLoaded()) {
-            if (old.equals(e)) {
+            if (old == deproxyIfNecessary(e)) {
                 log.warn("{} redundant call to start management of entity {}; ignoring",
this, e);
             } else {
-                throw new IllegalStateException("call to manage entity "+e+" ("+mode+") but
different entity "+old+" already known under that id at "+this);
+                throw new IdAlreadyExistsException("call to manage entity "+e+" ("+mode+")
but "
+                        + "different entity "+old+" already known under that id '"+e.getId()+"'
at "+this);
             }
             return false;
         }
 
         BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(e),
-            "{} starting management of entity {}", this, e);
+                "{} starting management of entity {}", this, e);
         Entity realE = toRealEntity(e);
         
         Entity oldProxy = entityProxiesById.get(e.getId());
         Entity proxyE;
         if (oldProxy!=null) {
             if (mode.wasNotLoaded()) {
-                throw new IllegalStateException("call to manage entity "+e+" from unloaded
state ("+mode+") but already had proxy "+oldProxy+" already known under that id at "+this);
+                throw new IdAlreadyExistsException("call to manage entity "+e+" from unloaded
"
+                        + "state ("+mode+") but already had proxy "+oldProxy+" already known
"
+                        + "under that id '"+e.getId()+"' at "+this);
             }
             // make the old proxy point at this new delegate
             // (some other tricks done in the call below)
@@ -698,7 +710,7 @@ public class LocalEntityManager implements EntityManagerInternal {
         entityProxiesById.put(e.getId(), proxyE);
         entityTypes.put(e.getId(), realE.getClass().getName());
         entitiesById.put(e.getId(), realE);
-
+        
         preManagedEntitiesById.remove(e.getId());
         if ((e instanceof Application) && (e.getParent()==null)) {
             applications.add((Application)proxyE);
@@ -764,6 +776,7 @@ public class LocalEntityManager implements EntityManagerInternal {
             entities.remove(proxyE);
             entityProxiesById.remove(e.getId());
             entityModesById.remove(e.getId());
+            
             Object old = entitiesById.remove(e.getId());
 
             entityTypes.remove(e.getId());
@@ -868,6 +881,11 @@ public class LocalEntityManager implements EntityManagerInternal {
         return result;
     }
     
+    private Entity deproxyIfNecessary(Entity e) {
+        return (e instanceof AbstractEntity) ? e : Entities.deproxy(e);
+    }
+    
+
     private boolean isRunning() {
         return managementContext.isRunning();
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java
b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java
index a980a3c..8289ab7 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/NonDeploymentEntityManager.java
@@ -32,6 +32,7 @@ import org.apache.brooklyn.api.policy.PolicySpec;
 import org.apache.brooklyn.api.sensor.Enricher;
 import org.apache.brooklyn.api.sensor.EnricherSpec;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 
 public class NonDeploymentEntityManager implements EntityManagerInternal {
@@ -61,6 +62,15 @@ public class NonDeploymentEntityManager implements EntityManagerInternal
{
     }
     
     @Override
+    public <T extends Entity> T createEntity(EntitySpec<T> spec, Optional<String>
entityId) {
+        if (isInitialManagementContextReal()) {
+            return ((EntityManagerInternal)initialManagementContext.getEntityManager()).createEntity(spec,
entityId);
+        } else {
+            throw new IllegalStateException("Non-deployment context "+this+" (with no initial
management context supplied) is not valid for this operation.");
+        }
+    }
+    
+    @Override
     public <T extends Entity> T createEntity(Map<?,?> config, Class<T>
type) {
         return createEntity(EntitySpec.create(type).configure(config));
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
index 0cf933c..e3ca216 100644
--- a/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/core/objs/proxy/InternalEntityFactory.java
@@ -63,6 +63,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -171,7 +172,7 @@ public class InternalEntityFactory extends InternalFactory {
      * {@link Entities#manage(Entity)} (if it's in a managed application)
      * or {@link Entities#startManagement(org.apache.brooklyn.api.entity.Application, org.apache.brooklyn.api.management.ManagementContext)}
      * (if it's an application) */
-    public <T extends Entity> T createEntity(EntitySpec<T> spec) {
+    public <T extends Entity> T createEntity(EntitySpec<T> spec, Optional<String>
entityId) {
         /* Order is important here. Changed Jul 2014 when supporting children in spec.
          * (Previously was much simpler, and parent was set right after running initializers;
and there were no children.)
          * <p>
@@ -186,12 +187,13 @@ public class InternalEntityFactory extends InternalFactory {
         Map<String,Entity> entitiesByEntityId = MutableMap.of();
         Map<String,EntitySpec<?>> specsByEntityId = MutableMap.of();
         
-        T entity = createEntityAndDescendantsUninitialized(spec, entitiesByEntityId, specsByEntityId);
+        T entity = createEntityAndDescendantsUninitialized(spec, entityId, entitiesByEntityId,
specsByEntityId);
         initEntityAndDescendants(entity.getId(), entitiesByEntityId, specsByEntityId);
         return entity;
     }
     
-    protected <T extends Entity> T createEntityAndDescendantsUninitialized(EntitySpec<T>
spec, Map<String,Entity> entitiesByEntityId, Map<String,EntitySpec<?>> specsByEntityId)
{
+    private <T extends Entity> T createEntityAndDescendantsUninitialized(EntitySpec<T>
spec, Optional<String> entityId, 
+            Map<String,Entity> entitiesByEntityId, Map<String,EntitySpec<?>>
specsByEntityId) {
         if (spec.getFlags().containsKey("parent") || spec.getFlags().containsKey("owner"))
{
             throw new IllegalArgumentException("Spec's flags must not contain parent or owner;
use spec.parent() instead for "+spec);
         }
@@ -202,7 +204,7 @@ public class InternalEntityFactory extends InternalFactory {
         try {
             Class<? extends T> clazz = getImplementedBy(spec);
             
-            T entity = constructEntity(clazz, spec);
+            T entity = constructEntity(clazz, spec, entityId);
             
             loadUnitializedEntity(entity, spec);
 
@@ -218,7 +220,7 @@ public class InternalEntityFactory extends InternalFactory {
                     log.warn("Child spec "+childSpec+" is already set with parent "+entity+";
how did this happen?!");
                 }
                 childSpec.parent(entity);
-                Entity child = createEntityAndDescendantsUninitialized(childSpec, entitiesByEntityId,
specsByEntityId);
+                Entity child = createEntityAndDescendantsUninitialized(childSpec, Optional.absent(),
entitiesByEntityId, specsByEntityId);
                 entity.addChild(child);
             }
             
@@ -392,10 +394,9 @@ public class InternalEntityFactory extends InternalFactory {
      * configuration from the {@link EntitySpec} is <b>not</b> normally applied,
      * although for old-style entities flags from the spec are passed to the constructor.
      * <p>
-     * @deprecated since 0.9.0 becoming private
-     */ @Deprecated
-    public <T extends Entity> T constructEntity(Class<? extends T> clazz, EntitySpec<T>
spec) {
-        T entity = constructEntityImpl(clazz, spec, null, null);
+     */
+    private <T extends Entity> T constructEntity(Class<? extends T> clazz, EntitySpec<T>
spec, Optional<String> entityId) {
+        T entity = constructEntityImpl(clazz, spec, null, entityId);
         if (((AbstractEntity)entity).getProxy() == null) ((AbstractEntity)entity).setProxy(createEntityProxy(spec,
entity));
         return entity;
     }
@@ -414,7 +415,7 @@ public class InternalEntityFactory extends InternalFactory {
         checkNotNull(entityId, "entityId");
         checkState(interfaces != null && !Iterables.isEmpty(interfaces), "must have
at least one interface for entity %s:%s", clazz, entityId);
         
-        T entity = constructEntityImpl(clazz, null, null, entityId);
+        T entity = constructEntityImpl(clazz, null, null, Optional.of(entityId));
         if (((AbstractEntity)entity).getProxy() == null) {
             Entity proxy = managementContext.getEntityManager().getEntity(entity.getId());
             if (proxy==null) {
@@ -429,11 +430,12 @@ public class InternalEntityFactory extends InternalFactory {
         return entity;
     }
 
-    private <T extends Entity> T constructEntityImpl(Class<? extends T> clazz,
EntitySpec<?> optionalSpec, Map<String, ?> optionalConstructorFlags, String optionalEntityId)
{
+    private <T extends Entity> T constructEntityImpl(Class<? extends T> clazz,
EntitySpec<?> optionalSpec, 
+            Map<String, ?> optionalConstructorFlags, Optional<String> entityId)
{
         T entity = construct(clazz, optionalSpec, optionalConstructorFlags);
         
-        if (optionalEntityId != null) {
-            FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", optionalEntityId), entity);
+        if (entityId.isPresent()) {
+            FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", entityId.get()), entity);
         }
         if (entity instanceof AbstractApplication) {
             FlagUtils.setFieldsFromFlags(ImmutableMap.of("mgmt", managementContext), entity);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java
b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java
index 976beda..7d21071 100644
--- a/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/EntityManagerTest.java
@@ -20,6 +20,7 @@ package org.apache.brooklyn.core.entity.proxying;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 
 import java.util.ConcurrentModificationException;
@@ -30,9 +31,12 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.mgmt.EntityManager;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal;
+import org.apache.brooklyn.core.mgmt.internal.IdAlreadyExistsException;
 import org.apache.brooklyn.core.mgmt.internal.LocalEntityManager;
 import org.apache.brooklyn.core.objs.proxy.EntityProxy;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
@@ -47,6 +51,7 @@ import org.slf4j.LoggerFactory;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -114,6 +119,24 @@ public class EntityManagerTest extends BrooklynAppUnitTestSupport {
         Asserts.assertEqualsIgnoringOrder(entityManager.findEntitiesInApplication(app, Predicates.instanceOf(TestApplication.class)),
ImmutableList.of(app));
     }
     
+    @Test
+    public void testCreateEntitiesWithDuplicateIdFails() {
+        TestApplication origApp = app;
+        Entity origDeproxiedApp = Entities.deproxy(app);
+        
+        try {
+            TestApplication app2 = ((EntityManagerInternal)entityManager).createEntity(EntitySpec.create(TestApplication.class),
Optional.of(app.getId()));
+            Asserts.shouldHaveFailedPreviously("app2="+app2);
+        } catch (IdAlreadyExistsException e) {
+            // success
+        }
+        
+        // Should not have affected the existing app!
+        Entity postApp = entityManager.getEntity(app.getId());
+        assertSame(postApp, origApp);
+        assertSame(Entities.deproxy(postApp), origDeproxiedApp);
+    }
+    
     // See https://issues.apache.org/jira/browse/BROOKLYN-352
     // Before the fix, 250ms was sufficient to cause the ConcurrentModificationException
     @Test

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/test/java/org/apache/brooklyn/core/entity/proxying/InternalEntityFactoryTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/entity/proxying/InternalEntityFactoryTest.java
b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/InternalEntityFactoryTest.java
index 7439b71..816242a 100644
--- a/core/src/test/java/org/apache/brooklyn/core/entity/proxying/InternalEntityFactoryTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/entity/proxying/InternalEntityFactoryTest.java
@@ -41,6 +41,8 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Optional;
+
 public class InternalEntityFactoryTest {
 
     private ManagementContextInternal mgmt;
@@ -61,7 +63,7 @@ public class InternalEntityFactoryTest {
     @Test
     public void testCreatesEntity() throws Exception {
         EntitySpec<TestApplication> spec = EntitySpec.create(TestApplication.class);
-        TestApplicationImpl app = (TestApplicationImpl) factory.createEntity(spec);
+        TestApplicationImpl app = (TestApplicationImpl) factory.createEntity(spec, Optional.absent());
         
         Entity proxy = app.getProxy();
         assertTrue(proxy instanceof Application, "proxy="+app);
@@ -74,7 +76,7 @@ public class InternalEntityFactoryTest {
     @Test
     public void testCreatesProxy() throws Exception {
         EntitySpec<Application> spec = EntitySpec.create(Application.class).impl(TestApplicationImpl.class);
-        Application app = factory.createEntity(spec);
+        Application app = factory.createEntity(spec, Optional.absent());
         Application proxy = factory.createEntityProxy(spec, app);
         TestApplicationImpl deproxied = (TestApplicationImpl) Entities.deproxy(proxy);
         
@@ -89,18 +91,25 @@ public class InternalEntityFactoryTest {
     }
     
     @Test
+    public void testSetsEntityId() throws Exception {
+        EntitySpec<TestApplication> spec = EntitySpec.create(TestApplication.class);
+        TestApplication app = factory.createEntity(spec, Optional.of("myentityid"));
+        assertEquals(app.getId(), "myentityid");
+    }
+    
+    @Test
     public void testSetsEntityIsLegacyConstruction() throws Exception {
         TestEntity legacy = new TestEntityImpl();
         assertTrue(legacy.isLegacyConstruction());
         
-        TestEntity entity = factory.createEntity(EntitySpec.create(TestEntity.class));
+        TestEntity entity = factory.createEntity(EntitySpec.create(TestEntity.class), Optional.absent());
         assertFalse(entity.isLegacyConstruction());
     }
     
     @Test
     public void testCreatesProxyImplementingAdditionalInterfaces() throws Exception {
         EntitySpec<Application> spec = EntitySpec.create(Application.class).impl(MyApplicationImpl.class).additionalInterfaces(MyInterface.class);
-        Application app = factory.createEntity(spec);
+        Application app = factory.createEntity(spec, Optional.absent());
         Application proxy = factory.createEntityProxy(spec, app);
         
         assertFalse(proxy instanceof MyApplicationImpl, "proxy="+proxy);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiVersionMoreEntityTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiVersionMoreEntityTest.java
b/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiVersionMoreEntityTest.java
index 0e19f52..76002a4 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiVersionMoreEntityTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/osgi/OsgiVersionMoreEntityTest.java
@@ -62,6 +62,7 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
@@ -117,7 +118,7 @@ public class OsgiVersionMoreEntityTest implements OsgiTestResources {
 
             @SuppressWarnings("unchecked")
             EntitySpec<Entity> spec = (((EntitySpec<Entity>)EntitySpec.create(bundleInterface))).impl(bundleCls);
-            AbstractEntity entityImpl = (AbstractEntity) factory.createEntity(spec);
+            AbstractEntity entityImpl = (AbstractEntity) factory.createEntity(spec, Optional.absent());
             Entity entityProxy = factory.createEntityProxy(spec, entityImpl);
             
             assertTrue(entityProxy instanceof EntityProxy, "proxy="+entityProxy);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
index d0e4d19..2e66fa4 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
@@ -27,6 +27,7 @@ import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -38,6 +39,8 @@ import org.apache.brooklyn.rest.domain.ApplicationSummary;
 import org.apache.brooklyn.rest.domain.EntityDetail;
 import org.apache.brooklyn.rest.domain.EntitySummary;
 
+import com.google.common.annotations.Beta;
+
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -101,7 +104,6 @@ public interface ApplicationApi {
     )
     @ApiResponses(value = {
             @ApiResponse(code = 404, message = "Undefined entity or location"),
-            @ApiResponse(code = 412, message = "Application already registered")
     })
     public Response createFromYaml(
             @ApiParam(
@@ -110,6 +112,24 @@ public interface ApplicationApi {
                     required = true)
             String yaml);
 
+    @Beta
+    @PUT
+    @Path("/{application}")
+    @Consumes({"application/x-yaml",
+            // see http://stackoverflow.com/questions/332129/yaml-mime-type
+            "text/yaml", "text/x-yaml", "application/yaml"})
+    @ApiOperation(
+            value = "[BETA] Create and start a new application from YAML, with the given
id",
+            response = org.apache.brooklyn.rest.domain.TaskSummary.class
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = 404, message = "Undefined entity or location"),
+            @ApiResponse(code = 409, message = "Application already registered")
+    })
+    public Response createFromYamlWithAppId(
+            @ApiParam(name = "applicationSpec", value = "App spec in CAMP YAML format", required
= true) String yaml,
+            @ApiParam(name = "application", value = "Application id", required = true) @PathParam("application")
String appId);
+
     @POST
     @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN})
     @ApiOperation(
@@ -117,8 +137,7 @@ public interface ApplicationApi {
             response = org.apache.brooklyn.rest.domain.TaskSummary.class
     )
     @ApiResponses(value = {
-            @ApiResponse(code = 404, message = "Undefined entity or location"),
-            @ApiResponse(code = 412, message = "Application already registered")
+            @ApiResponse(code = 404, message = "Undefined entity or location")
     })
     public Response createPoly(
             @ApiParam(
@@ -134,8 +153,7 @@ public interface ApplicationApi {
             response = org.apache.brooklyn.rest.domain.TaskSummary.class
     )
     @ApiResponses(value = {
-            @ApiResponse(code = 404, message = "Undefined entity or location"),
-            @ApiResponse(code = 412, message = "Application already registered")
+            @ApiResponse(code = 404, message = "Undefined entity or location")
     })
     public Response createFromForm(
             @ApiParam(

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
index 1a42103..973eebe 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
@@ -19,9 +19,9 @@
 package org.apache.brooklyn.rest.resources;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static javax.ws.rs.core.Response.Status.ACCEPTED;
 import static javax.ws.rs.core.Response.created;
 import static javax.ws.rs.core.Response.status;
+import static javax.ws.rs.core.Response.Status.ACCEPTED;
 import static org.apache.brooklyn.rest.util.WebResourceUtils.serviceAbsoluteUriBuilder;
 
 import java.net.URI;
@@ -32,6 +32,8 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.regex.Pattern;
+
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
@@ -58,6 +60,7 @@ import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.StringAndArgument;
+import org.apache.brooklyn.core.mgmt.internal.IdAlreadyExistsException;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
@@ -87,6 +90,7 @@ import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMap;
@@ -101,6 +105,14 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
     @Context
     private UriInfo uriInfo;
 
+    /**
+     * Only lower-case letters and digits; min 10 chars; max 1024 chars.
+     * 
+     * We are this extreme because some existing entity implementations rely on the entity-id
format 
+     * for use in hostnames, etc.
+     */
+    private final Pattern appIdPattern = Pattern.compile("[a-z0-9]{10,1022}");
+    
     private EntityDetail fromEntity(Entity entity) {
         Boolean serviceUp = entity.getAttribute(Attributes.SERVICE_UP);
 
@@ -240,6 +252,18 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
 
     @Override
     public Response createFromYaml(String yaml) {
+        return createFromYaml(yaml, Optional.absent());
+    }
+    
+    @Override
+    public Response createFromYamlWithAppId(String yaml, String appId) {
+        if (!appIdPattern.matcher(appId).matches()) {
+            throw new IllegalArgumentException("Invalid appId '"+appId+"'");
+        }
+        return createFromYaml(yaml, Optional.of(appId));
+    }
+    
+    protected Response createFromYaml(String yaml, Optional<String> appId) {
         // First of all, see if it's a URL
         Preconditions.checkNotNull(yaml, "Blueprint must not be null");
         URI uri = null;
@@ -284,7 +308,10 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
         }
 
         try {
-            return launch(yaml, spec);
+            return launch(yaml, spec, appId);
+        } catch (IdAlreadyExistsException e) {
+            log.warn("Failed REST deployment launching "+spec+": "+e);
+            throw WebResourceUtils.throwWebApplicationException(Response.Status.CONFLICT,
e, "Error launching blueprint, id already exists");
         } catch (Exception e) {
             Exceptions.propagateIfFatal(e);
             log.warn("Failed REST deployment launching "+spec+": "+e);
@@ -292,9 +319,9 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
         }
     }
 
-    private Response launch(String yaml, EntitySpec<? extends Application> spec) {
+    private Response launch(String yaml, EntitySpec<? extends Application> spec, Optional<String>
entityId) {
         try {
-            Application app = EntityManagementUtils.createUnstarted(mgmt(), spec);
+            Application app = EntityManagementUtils.createUnstarted(mgmt(), spec, entityId);
 
             boolean isEntitled = Entitlements.isEntitled(
                     mgmt().getEntitlementManager(),
@@ -374,7 +401,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
 
         if (spec != null) {
             try {
-                return launch(potentialYaml, spec);
+                return launch(potentialYaml, spec, Optional.absent());
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
                 log.warn("Failed REST deployment launching "+spec+": "+e);
@@ -409,7 +436,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource
implements
     private EntitySpec<? extends Application> createEntitySpecForApplication(String
potentialYaml) {
         return EntityManagementUtils.createEntitySpecForApplication(mgmt(), potentialYaml);
     }
-
+    
     private void checkApplicationTypesAreValid(ApplicationSpec applicationSpec) {
         String appType = applicationSpec.getType();
         if (appType != null) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index 2a53cc9..679553d 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -21,10 +21,13 @@ package org.apache.brooklyn.rest.resources;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Iterables.find;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
 import java.util.Collection;
 import java.util.List;
@@ -50,6 +53,7 @@ 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.core.mgmt.internal.IdAlreadyExistsException;
 import org.apache.brooklyn.entity.stock.BasicApplication;
 import org.apache.brooklyn.entity.stock.BasicEntity;
 import org.apache.brooklyn.rest.domain.ApiError;
@@ -74,6 +78,8 @@ 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.stream.Streams;
+import org.apache.brooklyn.util.text.StringPredicates;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.http.HttpHeaders;
@@ -84,12 +90,14 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
 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.google.common.collect.Range;
 
 @Test(singleThreaded = true,
         // by using a different suite name we disallow interleaving other tests between the
methods of this test class, which wrecks the test fixtures
@@ -662,6 +670,88 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest
{
         return Iterables.getOnlyElement(itemSummaries);
     }
 
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeploymentUsesAppId() throws Exception {
+        String yaml = "{ name: simple-app-yaml, services: [ { type: "+BasicApplication.class.getCanonicalName()+"
} ] }";
+        String appId = "myidtestdeploymentuidisset";
+
+        Response response = deployApp(yaml, appId);
+        assertResponseStatus(response, 201);
+
+        BasicApplication app = (BasicApplication) getManagementContext().getEntityManager().getEntity(appId);
+        assertNotNull(app);
+        assertEquals(app.getId(), appId);
+    }
+
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeploymentFailsOnInvalidAppId() throws Exception {
+        assertAppIdValid("abcdefghijkl");
+        assertAppIdValid("1234567890");
+        assertAppIdInvalid("tooshort"); // must be at least 10 chars
+        assertAppIdInvalid("a-bcdefghijkl"); // must be letters/digits only
+        assertAppIdInvalid("Abcdefghijkl"); // must be lower-case only
+        assertAppIdInvalid(com.google.common.base.Strings.repeat("a", 1025)); // too long
+    }
+
+    private void assertAppIdInvalid(String appId) {
+        String yaml = "{ name: simple-app-yaml, services: [ { type: "+BasicApplication.class.getCanonicalName()+"
} ] }";
+        Response response = deployApp(yaml, appId);
+        assertResponseStatus(response, 400, StringPredicates.containsLiteral("Invalid appId
'"+appId+"'"));
+        assertNull(getManagementContext().getEntityManager().getEntity(appId));
+    }
+    
+    private void assertAppIdValid(String appId) {
+        String yaml = "{ name: simple-app-yaml, services: [ { type: "+BasicApplication.class.getCanonicalName()+"
} ] }";
+        Response response = deployApp(yaml, appId);
+        assertResponseStatus(response, 201);
+        assertNotNull(getManagementContext().getEntityManager().getEntity(appId));
+    }
+    
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeploymentFailsOnDuplicateAppId() throws Exception {
+        // First app
+        String appId = "myuidtestdeploymentuidfailsonduplicate";
+        String yaml = "{ name: my-name-1, services: [ { type: "+BasicApplication.class.getCanonicalName()+"
} ] }";
+        Response response = deployApp(yaml, appId);
+        assertResponseStatus(response, 201);
+
+        BasicApplication app = (BasicApplication) getManagementContext().getEntityManager().getEntity(appId);
+        assertNotNull(app);
+
+        // Second app should get a conflict response (409)
+        String yaml2 = "{ name: my-name-2, services: [ { type: "+BasicApplication.class.getCanonicalName()+"
} ] }";
+        Response response2 = deployApp(yaml2, appId);
+        assertResponseStatus(response2, 409, StringPredicates.containsAllLiterals(
+                IdAlreadyExistsException.class.getSimpleName(), "already known under that
id '"+appId+"'"));
+
+        Optional<Application> app2 = Iterables.tryFind(getManagementContext().getApplications(),
EntityPredicates.displayNameEqualTo("my-name-2"));
+        assertFalse(app2.isPresent(), "app2="+app2);
+        
+        // Third app with different app id should work
+        String appId3 = "myuiddifferent";
+        String yaml3 = "{ name: my-name-3, services: [ { type: "+BasicApplication.class.getCanonicalName()+"
} ] }";
+        Response response3 = deployApp(yaml3, appId3);
+        assertResponseStatus(response3, 201);
+        
+        BasicApplication app3 = (BasicApplication) getManagementContext().getEntityManager().getEntity(appId3);
+        assertNotNull(app3);
+        
+        // Delete app1; then deploying app2 should succeed
+        Entities.unmanage(app);
+        
+        Response response2b = deployApp(yaml2, appId);
+        assertResponseStatus(response2b, 201);
+
+        BasicApplication app2b = (BasicApplication) getManagementContext().getEntityManager().getEntity(appId);
+        assertNotNull(app2b);
+        assertEquals(app2b.getDisplayName(), "my-name-2");
+    }
+
+    private Response deployApp(String yaml, String appId) {
+        return client().path("/applications/"+appId)
+                .put(Entity.entity(yaml, "application/x-yaml"));
+    }
+    
     private void deprecateCatalogItem(String symbolicName, String version, boolean deprecated)
{
         String id = String.format("%s:%s", symbolicName, version);
         Response response = client().path(String.format("/catalog/entities/%s/deprecated",
id))
@@ -697,4 +787,19 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest
{
 
         client().path("/catalog").post(yaml);
     }
+
+    private void assertResponseStatus(Response response, int expectedStatus, Predicate<?
super String> expectedBody) {
+        assertResponseStatus(response, Range.singleton(expectedStatus), expectedBody);
+    }
+    
+    private void assertResponseStatus(Response response, int expectedStatus) {
+        assertResponseStatus(response, Range.singleton(expectedStatus), Predicates.alwaysTrue());
+    }
+
+    private void assertResponseStatus(Response response, Range<Integer> expectedStatusRange,
Predicate<? super String> expectedBody) {
+        String body = Streams.readFullyString((InputStream)response.getEntity());
+        String errMsg = "response is "+response+"; status="+response.getStatus()+"; reason="+response.getStatusInfo().getReasonPhrase()+";
body="+body;
+        assertTrue(expectedStatusRange.contains(response.getStatus()), errMsg);
+        assertTrue(expectedBody.apply(body), errMsg);
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/3cd14917/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
index 0174332..30d6697 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/testing/BrooklynRestResourceTest.java
@@ -24,6 +24,7 @@ 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.Callable;
 import java.util.concurrent.TimeUnit;
@@ -39,6 +40,7 @@ import org.apache.brooklyn.rest.domain.ApplicationSpec;
 import org.apache.brooklyn.rest.domain.ApplicationSummary;
 import org.apache.brooklyn.rest.domain.Status;
 import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.repeat.Repeater;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.cxf.endpoint.Server;
@@ -162,6 +164,15 @@ public abstract class BrooklynRestResourceTest extends BrooklynRestApiTest
{
         return client().path(uri).get(ApplicationSummary.class).getStatus();
     }
 
+    protected Map<?, ?> getApplicationConfig(URI appUri) {
+        // appUri in a format like "http://localhost:9998/applications/mwk66lso65/config/current-state";

+        // Will call /applications/{application}/entities/{entity}/config
+        String[] pathParts = appUri.getPath().split("/");
+        String appId = pathParts[pathParts.length-1];
+        URI configUri = URI.create(Urls.mergePaths(appUri.toString(), "/entities/", appId,
"/config/current-state"));
+        return client().path(configUri).get(Map.class);
+    }
+
     protected void waitForPageFoundResponse(final String resource, final Class<?> clazz)
{
         boolean found = Repeater.create("Wait for page found")
                 .until(new Callable<Boolean>() {


Mime
View raw message