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 <[email protected]> AuthorDate: Wed Apr 21 16:49:00 2021 +0100 Extend REST API to provide entity relations on GET request Signed-off-by: Mykola Mandra <[email protected]> --- .../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'"); + } +}
