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'");
+ }
+}