You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2021/05/03 08:29:24 UTC

[brooklyn-server] 01/04: Extend REST API to provide entity relations on GET request

This is an automated email from the ASF dual-hosted git repository.

heneveld pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git

commit a5362f23d6d4cbb3b47bd1e611d32e92641e32be
Author: Mykola Mandra <my...@cloudsoftcorp.com>
AuthorDate: Wed Apr 21 16:49:00 2021 +0100

    Extend REST API to provide entity relations on GET request
    
    Signed-off-by: Mykola Mandra <my...@cloudsoftcorp.com>
---
 .../org/apache/brooklyn/rest/api/EntityApi.java    |   9 ++
 .../brooklyn/rest/domain/RelationSummary.java      |  72 ++++++++++++++
 .../apache/brooklyn/rest/domain/RelationType.java  |  79 +++++++++++++++
 .../brooklyn/rest/resources/EntityResource.java    |  33 +++++--
 .../resources/EntityRelationsResourceTest.java     | 110 +++++++++++++++++++++
 5 files changed, 297 insertions(+), 6 deletions(-)

diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
index 9455575..5bbd5b3 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/EntityApi.java
@@ -19,6 +19,7 @@
 package org.apache.brooklyn.rest.api;
 
 import io.swagger.annotations.Api;
+import org.apache.brooklyn.rest.domain.RelationSummary;
 import org.apache.brooklyn.rest.domain.EntitySummary;
 import org.apache.brooklyn.rest.domain.LocationSummary;
 import org.apache.brooklyn.rest.domain.TaskSummary;
@@ -74,6 +75,14 @@ public interface EntityApi {
             @PathParam("application") final String application,
             @PathParam("entity") final String entity);
 
+    @GET
+    @ApiOperation(value = "Fetch the list of relations of an entity",
+            response = RelationSummary.class)
+    @Path("/{entity}/relations")
+    List<RelationSummary> getRelations(
+            @PathParam("application") final String applicationId,
+            @PathParam("entity") final String entityId);
+
     @POST
     @ApiOperation(value = "Add a child or children to this entity given a YAML spec",
             response = org.apache.brooklyn.rest.domain.TaskSummary.class)
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/RelationSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/RelationSummary.java
new file mode 100644
index 0000000..255f49f
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/RelationSummary.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import com.fasterxml.jackson.annotation.*;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.Set;
+
+public class RelationSummary implements Serializable {
+
+    private static final long serialVersionUID = -3273187304766851859L;
+
+    private final RelationType type;
+    private final Set<String> targets;
+
+    public RelationSummary(
+            @JsonProperty("type") RelationType type,
+            @JsonProperty("targets") Set<String> targets) {
+        this.type = type;
+        this.targets = (targets == null) ? ImmutableSet.of() : ImmutableSet.copyOf(targets);
+    }
+
+    public RelationType getType() {
+        return type;
+    }
+
+    public Set<String> getTargets() {
+        return targets;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof RelationSummary)) return false;
+        RelationSummary that = (RelationSummary) o;
+        return Objects.equals(type, that.type)
+                && Objects.equals(targets, that.targets);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, targets);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("type", type)
+                .add("targets", targets)
+                .toString();
+    }
+}
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/RelationType.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/RelationType.java
new file mode 100644
index 0000000..c740ce3
--- /dev/null
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/RelationType.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.Set;
+
+public class RelationType implements Serializable {
+
+    private static final long serialVersionUID = -6467217117683357832L;
+
+    private final String name;
+    private final String target;
+    private final String source;
+
+    public RelationType(
+            @JsonProperty("name") String name,
+            @JsonProperty("target") String target,
+            @JsonProperty("source") String source) {
+        this.name = name;
+        this.target = target;
+        this.source = source;
+    }
+
+    public String getName() {
+        return name;
+    }
+    public String getTarget() {
+        return target;
+    }
+    public String getSource() {
+        return source;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof RelationType)) return false;
+        RelationType that = (RelationType) o;
+        return Objects.equals(name, that.name)
+                && Objects.equals(target, that.target)
+                && Objects.equals(source, that.source);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, target, source);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("name", name)
+                .add("target", target)
+                .add("source", source)
+                .toString();
+    }
+}
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
index f66d821..8809007 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/EntityResource.java
@@ -27,6 +27,7 @@ import java.net.URI;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
@@ -35,9 +36,12 @@ import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
+import com.google.common.collect.*;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.objs.BrooklynObject;
+import org.apache.brooklyn.api.relations.RelationshipType;
 import org.apache.brooklyn.core.mgmt.BrooklynTags;
 import org.apache.brooklyn.core.mgmt.BrooklynTags.NamedStringTag;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
@@ -47,9 +51,7 @@ import org.apache.brooklyn.core.mgmt.entitlement.EntitlementPredicates;
 import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
 import org.apache.brooklyn.core.typereg.RegisteredTypes;
 import org.apache.brooklyn.rest.api.EntityApi;
-import org.apache.brooklyn.rest.domain.EntitySummary;
-import org.apache.brooklyn.rest.domain.LocationSummary;
-import org.apache.brooklyn.rest.domain.TaskSummary;
+import org.apache.brooklyn.rest.domain.*;
 import org.apache.brooklyn.rest.filter.HaHotStateRequired;
 import org.apache.brooklyn.rest.transform.EntityTransformer;
 import org.apache.brooklyn.rest.transform.LocationTransformer;
@@ -64,9 +66,6 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.common.io.Files;
 
 @HaHotStateRequired
@@ -107,6 +106,28 @@ public class EntityResource extends AbstractBrooklynRestResource implements Enti
     }
 
     @Override
+    public List<RelationSummary> getRelations(final String applicationId, final String entityId) {
+        List<RelationSummary> entityRelations = Lists.newLinkedList();
+
+        Entity entity = brooklyn().getEntity(applicationId, entityId);
+        if (entity != null) {
+            for (RelationshipType<?,? extends BrooklynObject> relationship: entity.relations().getRelationshipTypes()) {
+                @SuppressWarnings({ "unchecked", "rawtypes" })
+                Set relations = entity.relations().getRelations((RelationshipType) relationship);
+                Set<String> relationIds = Sets.newLinkedHashSet();
+                for (Object r: relations) {
+                    relationIds.add(((BrooklynObject) r).getId());
+                }
+                RelationType relationType = new RelationType(relationship.getRelationshipTypeName(), relationship.getTargetName(), relationship.getSourceName());
+                RelationSummary relationSummary = new RelationSummary(relationType, relationIds);
+                entityRelations.add(relationSummary);
+            }
+        }
+
+        return entityRelations;
+    }
+
+    @Override
     public Response addChildren(String applicationToken, String entityToken, Boolean start, String timeoutS, String yaml) {
         final Entity parent = brooklyn().getEntity(applicationToken, entityToken);
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, parent)) {
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityRelationsResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityRelationsResourceTest.java
new file mode 100644
index 0000000..3d271a4
--- /dev/null
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/EntityRelationsResourceTest.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.rest.resources;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.entity.EntityRelations;
+import org.apache.brooklyn.rest.domain.ApplicationSpec;
+import org.apache.brooklyn.rest.domain.EntitySpec;
+import org.apache.brooklyn.rest.domain.RelationSummary;
+import org.apache.brooklyn.rest.testing.BrooklynRestResourceTest;
+import org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup;
+import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+
+import static org.testng.Assert.*;
+
+@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
+        suiteName = "EntityRelationsResourceTest")
+public class EntityRelationsResourceTest extends BrooklynRestResourceTest {
+
+    @BeforeClass(alwaysRun = true)
+    private void setUp() throws Exception {
+        // Deploy an application that we'll use to read the relations of.
+        startServer();
+        final ApplicationSpec applicationSpec = ApplicationSpec.builder().name("simple-app")
+                .entities(ImmutableSet.of(
+                        new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName()),
+                        new EntitySpec("simple-group", NameMatcherGroup.class.getName(), ImmutableMap.of("namematchergroup.regex", "simple-ent"))
+                ))
+                .locations(ImmutableSet.of("localhost"))
+                .build();
+        Response response = clientDeploy(applicationSpec);
+        int status = response.getStatus();
+        assertTrue(status >= 200 && status <= 299, "expected HTTP Response of 2xx but got " + status);
+        URI applicationUri = response.getLocation();
+        waitForApplicationToBeRunning(applicationUri);
+    }
+
+    @Test
+    public void testCustomRelations() {
+
+        // Expect no initial relations.
+        List<RelationSummary> simpleEntRelations = client().path(
+                URI.create("/applications/simple-app/entities/simple-ent/relations"))
+                .get(new GenericType<List<RelationSummary>>() {});
+        List<RelationSummary> simpleGroupRelations = client().path(
+                URI.create("/applications/simple-app/entities/simple-group/relations"))
+                .get(new GenericType<List<RelationSummary>>() {});
+        assertTrue(simpleEntRelations.isEmpty());
+        assertTrue(simpleGroupRelations.isEmpty());
+
+        // Add custom relation between 'simple-ent' and 'simple-group'.
+        Collection<Entity> entities = manager.getEntityManager().getEntities();
+        Entity simpleEnt = entities.stream().filter(e -> "simple-ent".equals(e.getDisplayName())).findFirst().orElse(null);
+        Entity simpleGroup = entities.stream().filter(e -> "simple-group".equals(e.getDisplayName())).findFirst().orElse(null);
+        assertNotNull(simpleEnt, "'simple-ent' was not found");
+        assertNotNull(simpleGroup, "'simple-group' was not found");
+        simpleGroup.relations().add(EntityRelations.HAS_TARGET, simpleEnt);
+
+        // Verify simple-ent relations.
+        simpleEntRelations = client().path(
+                URI.create("/applications/simple-app/entities/simple-ent/relations"))
+                .get(new GenericType<List<RelationSummary>>() {});
+        assertEquals(simpleEntRelations.size(), 1, "'simple-ent' must have 1 relation only");
+        RelationSummary simpleEntRelationSummary = simpleEntRelations.get(0);
+        assertEquals(simpleEntRelationSummary.getType().getName(), "targetted_by");
+        assertEquals(simpleEntRelationSummary.getType().getTarget(), "targetter");
+        assertEquals(simpleEntRelationSummary.getType().getSource(), "target");
+        assertEquals(simpleEntRelationSummary.getTargets().size(), 1, "'simple-ent' must have 1 target only");
+        assertTrue(simpleEntRelationSummary.getTargets().contains(simpleGroup.getId()), "'simple-ent' must target id of 'simple-group'");
+
+        // Verify simple-group relations.
+        simpleGroupRelations = client().path(
+                URI.create("/applications/simple-app/entities/simple-group/relations"))
+                .get(new GenericType<List<RelationSummary>>() {});
+        assertEquals(simpleGroupRelations.size(), 1, "'simple-group' must have 1 relation only");
+        RelationSummary simpleGroupRelationSummary = simpleGroupRelations.get(0);
+        assertEquals(simpleGroupRelationSummary.getType().getName(), "has_target");
+        assertEquals(simpleGroupRelationSummary.getType().getTarget(), "target");
+        assertEquals(simpleGroupRelationSummary.getType().getSource(), "targetter");
+        assertEquals(simpleGroupRelationSummary.getTargets().size(), 1, "'simple-group' must have 1 target only");
+        assertTrue(simpleGroupRelationSummary.getTargets().contains(simpleEnt.getId()), "'simple-group' must target id of 'simple-ent'");
+    }
+}