diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java index e77bb356c0..94b295994f 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java @@ -26,6 +26,7 @@ private RequestVariables() { public static final String MTA_ID = "mtaId"; public static final String NAMESPACE = "namespace"; public static final String MTA_NAME = "name"; + public static final String FORMAT = "format"; } @@ -62,6 +63,8 @@ private Endpoints() { } public static final String MTA = "/{" + RequestVariables.MTA_ID + "}"; + public static final String MTA_GRAPH = MTA + "/graph"; + public static final String DESCRIPTOR_GRAPH = "/graph"; public static final String OPERATION = "/{" + PathVariables.OPERATION_ID + "}"; public static final String OPERATION_LOGS = OPERATION + "/logs"; public static final String OPERATION_LOG_CONTENT = OPERATION_LOGS + "/{" + PathVariables.LOG_ID + "}/content"; diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/MtasApiService.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/MtasApiService.java index dc9f99ca4e..00eed0565d 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/MtasApiService.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/MtasApiService.java @@ -3,6 +3,7 @@ import java.util.List; import org.cloudfoundry.multiapps.controller.api.model.Mta; +import org.cloudfoundry.multiapps.controller.api.model.MtaGraph; import org.springframework.http.ResponseEntity; public interface MtasApiService { @@ -11,6 +12,12 @@ public interface MtasApiService { ResponseEntity getMta(String spaceGuid, String mtaId); + ResponseEntity getMtaGraph(String spaceGuid, String mtaId, String namespace); + + ResponseEntity buildGraphFromDescriptor(String spaceGuid, String descriptorYaml); + + ResponseEntity buildGraphFromDescriptorAsDot(String spaceGuid, String descriptorYaml); + ResponseEntity> getMtas(String spaceGuid, String mtaId, String namespace); } diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraph.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraph.java new file mode 100644 index 0000000000..4688a259dc --- /dev/null +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraph.java @@ -0,0 +1,26 @@ +package org.cloudfoundry.multiapps.controller.api.model; + +import java.util.List; + +import org.immutables.value.Value; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import io.swagger.annotations.ApiModelProperty; + +@Value.Immutable +@JsonSerialize(as = ImmutableMtaGraph.class) +@JsonDeserialize(as = ImmutableMtaGraph.class) +public interface MtaGraph { + + @ApiModelProperty + @JsonProperty("nodes") + List getNodes(); + + @ApiModelProperty + @JsonProperty("edges") + List getEdges(); + +} diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraphEdge.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraphEdge.java new file mode 100644 index 0000000000..ce9d8efe77 --- /dev/null +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraphEdge.java @@ -0,0 +1,34 @@ +package org.cloudfoundry.multiapps.controller.api.model; + +import org.immutables.value.Value; + +import org.cloudfoundry.multiapps.common.Nullable; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import io.swagger.annotations.ApiModelProperty; + +@Value.Immutable +@JsonSerialize(as = ImmutableMtaGraphEdge.class) +@JsonDeserialize(as = ImmutableMtaGraphEdge.class) +public interface MtaGraphEdge { + + @ApiModelProperty + @JsonProperty("from") + String getFrom(); + + @ApiModelProperty + @JsonProperty("to") + String getTo(); + + @ApiModelProperty + @JsonProperty("type") + String getType(); + + @Nullable + @ApiModelProperty + @JsonProperty("name") + String getName(); + +} diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraphNode.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraphNode.java new file mode 100644 index 0000000000..5cb882c705 --- /dev/null +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/model/MtaGraphNode.java @@ -0,0 +1,24 @@ +package org.cloudfoundry.multiapps.controller.api.model; + +import org.immutables.value.Value; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import io.swagger.annotations.ApiModelProperty; + +@Value.Immutable +@JsonSerialize(as = ImmutableMtaGraphNode.class) +@JsonDeserialize(as = ImmutableMtaGraphNode.class) +public interface MtaGraphNode { + + @ApiModelProperty + @JsonProperty("id") + String getId(); + + @ApiModelProperty + @JsonProperty("type") + String getType(); + +} diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java index 1a38c30570..8ecf017a3a 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java @@ -11,11 +11,15 @@ import org.cloudfoundry.multiapps.controller.api.Constants.RequestVariables; import org.cloudfoundry.multiapps.controller.api.Constants.Resources; import org.cloudfoundry.multiapps.controller.api.model.Mta; +import org.cloudfoundry.multiapps.controller.api.model.MtaGraph; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import io.swagger.annotations.Api; @@ -48,6 +52,23 @@ public ResponseEntity getMta(@ApiParam(value = "GUID of space with mtas") @ return delegate.getMta(spaceGuid, mtaId); } + @GetMapping(path = Endpoints.MTA_GRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Retrieve the dependency graph of a deployed MTA", notes = "Retrieves the module/resource dependency graph of a Multi-Target Application in a space", response = MtaGraph.class, authorizations = { + @Authorization(value = "oauth2", scopes = { + + }) }, tags = {}) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = MtaGraph.class), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity + getMtaGraph(@ApiParam(value = "GUID of space with mtas") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "mtaID of requested mta") @PathVariable(RequestVariables.MTA_ID) String mtaId, + @ApiParam(value = "Namespace of the MTA") @RequestParam(value = RequestVariables.NAMESPACE, required = false) String namespace) { + return delegate.getMtaGraph(spaceGuid, mtaId, namespace); + } + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Retrieve all deployed MTAs in a space", notes = "Retrieves all Multi-Target Applications in a space", response = Mta.class, responseContainer = "List", authorizations = { @Authorization(value = "oauth2", scopes = { @@ -62,4 +83,24 @@ public ResponseEntity getMta(@ApiParam(value = "GUID of space with mtas") @ return delegate.getMtas(spaceGuid); } + @PostMapping(path = Endpoints.DESCRIPTOR_GRAPH, consumes = "application/yaml", produces = MediaType.APPLICATION_JSON_VALUE) + @ApiOperation(value = "Build dependency graph from a descriptor", notes = "Parses a supplied mtad.yaml and returns its module/resource dependency graph", response = MtaGraph.class, authorizations = { + @Authorization(value = "oauth2", scopes = { + + }) }, tags = {}) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = MtaGraph.class), + @ApiResponse(code = 400, message = HttpResponses.BAD_REQUEST), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity + buildGraphFromDescriptor(@ApiParam(value = "GUID of space with mtas") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "mtad.yaml content") @RequestBody String descriptorYaml, + @ApiParam(value = "Output format: json (default) or dot") @RequestParam(value = RequestVariables.FORMAT, required = false) String format) { + if ("dot".equalsIgnoreCase(format)) { + return delegate.buildGraphFromDescriptorAsDot(spaceGuid, descriptorYaml); + } + return delegate.buildGraphFromDescriptor(spaceGuid, descriptorYaml); + } + } diff --git a/multiapps-controller-api/src/main/resources/mtarest.yaml b/multiapps-controller-api/src/main/resources/mtarest.yaml index 851d4d6640..f63b471c8c 100644 --- a/multiapps-controller-api/src/main/resources/mtarest.yaml +++ b/multiapps-controller-api/src/main/resources/mtarest.yaml @@ -214,6 +214,87 @@ paths: description: "Internal Server Error" security: - oauth2: [] + /api/v1/spaces/{spaceGuid}/mtas/graph: + post: + summary: "Build dependency graph from a descriptor" + description: "Parses a supplied mtad.yaml and returns its module/resource dependency\ + \ graph" + operationId: "buildGraphFromDescriptor" + consumes: + - "application/yaml" + produces: + - "application/json" + parameters: + - name: "spaceGuid" + in: "path" + description: "GUID of space with mtas" + required: true + type: "string" + - in: "body" + name: "body" + description: "mtad.yaml content" + required: false + schema: + type: "string" + - name: "format" + in: "query" + description: "Output format: json (default) or dot" + required: false + type: "string" + responses: + "200": + description: "OK" + schema: + $ref: "#/definitions/MtaGraph" + "400": + description: "Bad Request" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "500": + description: "Internal Server Error" + security: + - oauth2: [] + /api/v1/spaces/{spaceGuid}/mtas/{mtaId}/graph: + get: + summary: "Retrieve the dependency graph of a deployed MTA" + description: "Retrieves the module/resource dependency graph of a Multi-Target\ + \ Application in a space" + operationId: "getMtaGraph" + produces: + - "application/json" + parameters: + - name: "spaceGuid" + in: "path" + description: "GUID of space with mtas" + required: true + type: "string" + - name: "mtaId" + in: "path" + description: "mtaID of requested mta" + required: true + type: "string" + - name: "namespace" + in: "query" + description: "Namespace of the MTA" + required: false + type: "string" + responses: + "200": + description: "OK" + schema: + $ref: "#/definitions/MtaGraph" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" + security: + - oauth2: [] /api/v1/spaces/{spaceGuid}/mtas: get: summary: "Retrieve all deployed MTAs in a space" @@ -732,6 +813,43 @@ definitions: uniqueItems: true items: type: "string" + MtaGraph: + type: "object" + properties: + nodes: + type: "array" + readOnly: true + items: + $ref: "#/definitions/MtaGraphNode" + edges: + type: "array" + readOnly: true + items: + $ref: "#/definitions/MtaGraphEdge" + MtaGraphEdge: + type: "object" + properties: + from: + type: "string" + readOnly: true + to: + type: "string" + readOnly: true + type: + type: "string" + readOnly: true + name: + type: "string" + readOnly: true + MtaGraphNode: + type: "object" + properties: + id: + type: "string" + readOnly: true + type: + type: "string" + readOnly: true Operation: type: "object" properties: @@ -789,6 +907,43 @@ definitions: readOnly: true additionalProperties: type: "object" + MtaGraph: + type: "object" + properties: + nodes: + type: "array" + readOnly: true + items: + $ref: "#/definitions/MtaGraphNode" + edges: + type: "array" + readOnly: true + items: + $ref: "#/definitions/MtaGraphEdge" + MtaGraphEdge: + type: "object" + properties: + from: + type: "string" + readOnly: true + to: + type: "string" + readOnly: true + type: + type: "string" + readOnly: true + name: + type: "string" + readOnly: true + MtaGraphNode: + type: "object" + properties: + id: + type: "string" + readOnly: true + type: + type: "string" + readOnly: true ProcessType: type: "object" properties: diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/MtaMetadataAnnotations.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/MtaMetadataAnnotations.java index cdcf26271d..7d12cda5f3 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/MtaMetadataAnnotations.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/MtaMetadataAnnotations.java @@ -7,6 +7,7 @@ public class MtaMetadataAnnotations { public static final String MTA_NAMESPACE = "mta_namespace"; public static final String MTA_MODULE = "mta_module"; public static final String MTA_MODULE_PUBLIC_PROVIDED_DEPENDENCIES = "mta_module_provided_dependencies"; + public static final String MTA_MODULE_REQUIRED_DEPENDENCIES = "mta_module_required_dependencies"; public static final String MTA_MODULE_BOUND_SERVICES = "mta_bound_services"; public static final String MTA_RESOURCE = "mta_resource"; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java index f9503d54ee..894f24fdfd 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/metadata/processor/MtaMetadataParser.java @@ -70,12 +70,14 @@ public DeployedMtaApplication parseDeployedMtaApplication(CloudApplication appli String moduleName = parseNameAttribute(metadataAnnotations, MtaMetadataAnnotations.MTA_MODULE); List providedDependencies = parseModuleProvidedDependencies(application.getName(), metadataAnnotations, MtaMetadataAnnotations.MTA_MODULE_PUBLIC_PROVIDED_DEPENDENCIES); + List requiredDependencies = parseList(metadataAnnotations, MtaMetadataAnnotations.MTA_MODULE_REQUIRED_DEPENDENCIES); List boundMtaServices = parseList(metadataAnnotations, MtaMetadataAnnotations.MTA_MODULE_BOUND_SERVICES); return ImmutableDeployedMtaApplication.builder() .from(application) .moduleName(moduleName) .boundMtaServices(boundMtaServices) .providedDependencyNames(providedDependencies) + .requiredDependencyNames(requiredDependencies) .build(); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java index 44f9dee90b..01c98dfb9b 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ApplicationMetadataBuilder.java @@ -13,18 +13,22 @@ import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; import org.cloudfoundry.multiapps.mta.model.Module; import org.cloudfoundry.multiapps.mta.model.ProvidedDependency; +import org.cloudfoundry.multiapps.mta.model.RequiredDependency; public class ApplicationMetadataBuilder { public static Metadata build(DeploymentDescriptor deploymentDescriptor, String namespace, Module module, List services) { String mtaModuleAnnotation = buildMtaModuleAnnotation(module); String mtaModuleProvidedDependenciesAnnotation = buildMtaModuleProvidedDependenciesAnnotation(module); + String mtaModuleRequiredDependenciesAnnotation = buildMtaModuleRequiredDependenciesAnnotation(module); String mtaServicesAnnotation = buildBoundMtaServicesAnnotation(services); Metadata.Builder builder = MtaMetadataBuilder.init(deploymentDescriptor, namespace) .annotation(MtaMetadataAnnotations.MTA_MODULE, mtaModuleAnnotation) .annotation(MtaMetadataAnnotations.MTA_MODULE_PUBLIC_PROVIDED_DEPENDENCIES, mtaModuleProvidedDependenciesAnnotation) + .annotation(MtaMetadataAnnotations.MTA_MODULE_REQUIRED_DEPENDENCIES, + mtaModuleRequiredDependenciesAnnotation) .annotation(MtaMetadataAnnotations.MTA_MODULE_BOUND_SERVICES, mtaServicesAnnotation); return builder.build(); @@ -45,6 +49,14 @@ private static String buildMtaModuleProvidedDependenciesAnnotation(Module module return JsonUtil.toJson(providedDependencies); } + private static String buildMtaModuleRequiredDependenciesAnnotation(Module module) { + List requiredDependencies = module.getRequiredDependencies() + .stream() + .map(RequiredDependency::getName) + .collect(Collectors.toList()); + return JsonUtil.toJson(requiredDependencies); + } + private static String buildBoundMtaServicesAnnotation(List services) { return JsonUtil.toJson(services); } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/DeployedMtaApplication.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/DeployedMtaApplication.java index 786aafff15..905cc518f3 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/DeployedMtaApplication.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/DeployedMtaApplication.java @@ -18,6 +18,8 @@ public abstract class DeployedMtaApplication extends CloudApplication { public abstract List getProvidedDependencyNames(); + public abstract List getRequiredDependencyNames(); + @Value.Default public ProductizationState getProductizationState() { return ProductizationState.LIVE; diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-04.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-04.json index c6a39da3a6..3a753d2e21 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-04.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-04.json @@ -50,6 +50,7 @@ "moduleName": "mta-module-2", "boundMtaServices": [], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-05.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-05.json index 1beec7755b..db68a026b3 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-05.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-05.json @@ -49,6 +49,7 @@ "moduleName": "mta-module-2", "boundMtaServices": [], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-06.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-06.json index 311aa51890..dd1726905b 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-06.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-06.json @@ -49,6 +49,7 @@ "moduleName": "mta-module", "boundMtaServices": [], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-09.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-09.json index e9e3264977..09fe8a918d 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-09.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-09.json @@ -54,6 +54,7 @@ "mta-service-2" ], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-10.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-10.json index df79e88cf2..c261f576f0 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-10.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-10.json @@ -56,6 +56,7 @@ "mta-service-4" ], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-11.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-11.json index 2f317e3fc1..f261edbfeb 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-11.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-11.json @@ -28,6 +28,7 @@ "moduleName": "mta-module-3", "boundMtaServices": [], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-12.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-12.json index aea9db60f5..5b7ee6815d 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-12.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/detect/metadata/deployed-mtas-12.json @@ -53,6 +53,7 @@ "mta-service-2" ], "providedDependencyNames": [], + "requiredDependencyNames": [], "productizationState": "LIVE" } ], diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/MtasApiServiceImpl.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/MtasApiServiceImpl.java index 3f0ce39840..ee55b58fe4 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/MtasApiServiceImpl.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/MtasApiServiceImpl.java @@ -1,7 +1,10 @@ package org.cloudfoundry.multiapps.controller.web.api.impl; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -13,23 +16,32 @@ import org.cloudfoundry.multiapps.controller.api.model.ImmutableMetadata; import org.cloudfoundry.multiapps.controller.api.model.ImmutableModule; import org.cloudfoundry.multiapps.controller.api.model.ImmutableMta; +import org.cloudfoundry.multiapps.controller.api.model.ImmutableMtaGraph; +import org.cloudfoundry.multiapps.controller.api.model.ImmutableMtaGraphEdge; +import org.cloudfoundry.multiapps.controller.api.model.ImmutableMtaGraphNode; import org.cloudfoundry.multiapps.controller.api.model.Metadata; import org.cloudfoundry.multiapps.controller.api.model.Module; import org.cloudfoundry.multiapps.controller.api.model.Mta; +import org.cloudfoundry.multiapps.controller.api.model.MtaGraph; +import org.cloudfoundry.multiapps.controller.api.model.MtaGraphEdge; +import org.cloudfoundry.multiapps.controller.api.model.MtaGraphNode; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; import org.cloudfoundry.multiapps.controller.core.auditlogging.MtasApiServiceAuditLog; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; import org.cloudfoundry.multiapps.controller.core.cf.detect.DeployedMtaDetector; import org.cloudfoundry.multiapps.controller.core.cf.metadata.MtaMetadata; +import org.cloudfoundry.multiapps.controller.core.helpers.DescriptorParserFacadeFactory; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.util.UserInfo; import org.cloudfoundry.multiapps.controller.web.Messages; import org.cloudfoundry.multiapps.controller.web.util.SecurityContextUtil; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; import org.cloudfoundry.multiapps.mta.model.Version; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @Named @@ -44,6 +56,9 @@ public class MtasApiServiceImpl implements MtasApiService { @Inject private MtasApiServiceAuditLog mtasApiServiceAuditLog; + @Inject + private DescriptorParserFacadeFactory descriptorParserFacadeFactory; + @Override public ResponseEntity> getMtas(String spaceGuid) { mtasApiServiceAuditLog.logGetMtas(SecurityContextUtil.getUsername(), spaceGuid); @@ -58,6 +73,26 @@ public ResponseEntity> getMtas(String spaceGuid) { public ResponseEntity getMta(String spaceGuid, String mtaId) { mtasApiServiceAuditLog.logGetMta(SecurityContextUtil.getUsername(), spaceGuid, mtaId); CloudControllerClient client = getCloudFoundryClient(spaceGuid); + DeployedMta deployedMta = detectSingleMta(mtaId, client); + + return ResponseEntity.ok() + .body(getMta(deployedMta, client)); + } + + @Override + public ResponseEntity getMtaGraph(String spaceGuid, String mtaId, String namespace) { + mtasApiServiceAuditLog.logGetMta(SecurityContextUtil.getUsername(), spaceGuid, mtaId); + CloudControllerClient client = getCloudFoundryClient(spaceGuid); + DeployedMta deployedMta = namespace != null + ? deployedMtaDetector.detectDeployedMtaByNameAndNamespace(mtaId, namespace, client) + .orElseThrow(() -> new NotFoundException(Messages.SPECIFIC_MTA_NOT_FOUND, mtaId, namespace)) + : detectSingleMta(mtaId, client); + + return ResponseEntity.ok() + .body(buildGraph(deployedMta)); + } + + private DeployedMta detectSingleMta(String mtaId, CloudControllerClient client) { List mtas = deployedMtaDetector.detectDeployedMtasByName(mtaId, client); if (mtas.isEmpty()) { @@ -68,8 +103,81 @@ public ResponseEntity getMta(String spaceGuid, String mtaId) { throw new ConflictException(Messages.MTA_SEARCH_NOT_UNIQUE_BY_NAME, mtaId); } - return ResponseEntity.ok() - .body(getMta(mtas.get(0), client)); + return mtas.get(0); + } + + private MtaGraph buildGraph(DeployedMta mta) { + Map instanceNameToResourceName = new HashMap<>(); + for (DeployedMtaService service : mta.getServices()) { + String resourceName = resolveResourceName(service); + instanceNameToResourceName.putIfAbsent(service.getName(), resourceName); + } + + // provided dependency name → module name that provides it + Map providedDepToModule = new HashMap<>(); + for (DeployedMtaApplication application : mta.getApplications()) { + for (String provided : application.getProvidedDependencyNames()) { + providedDepToModule.put(provided, application.getModuleName()); + } + } + + java.util.Set resourceNames = new java.util.HashSet<>(); + for (DeployedMtaService service : mta.getServices()) { + resourceNames.add(resolveResourceName(service)); + } + + List nodes = new ArrayList<>(); + for (DeployedMtaApplication application : mta.getApplications()) { + nodes.add(buildNode(application.getModuleName(), "module")); + } + for (DeployedMtaService service : mta.getServices()) { + nodes.add(buildNode(resolveResourceName(service), "resource")); + } + + List edges = new ArrayList<>(); + for (DeployedMtaApplication application : mta.getApplications()) { + for (String boundService : application.getBoundMtaServices()) { + String target = instanceNameToResourceName.getOrDefault(boundService, boundService); + edges.add(buildEdge(application.getModuleName(), target, "binds-service")); + } + for (String required : application.getRequiredDependencyNames()) { + String providerModule = providedDepToModule.get(required); + if (providerModule != null) { + edges.add(buildEdge(providerModule, application.getModuleName(), "provides", required)); + } else if (resourceNames.contains(required)) { + edges.add(buildEdge(application.getModuleName(), required, "requires-resource", required)); + } + } + } + + return ImmutableMtaGraph.builder() + .nodes(nodes) + .edges(edges) + .build(); + } + + private String resolveResourceName(DeployedMtaService service) { + return service.getResourceName() != null ? service.getResourceName() : service.getName(); + } + + private MtaGraphNode buildNode(String id, String type) { + return ImmutableMtaGraphNode.builder() + .id(id) + .type(type) + .build(); + } + + private MtaGraphEdge buildEdge(String from, String to, String type) { + return buildEdge(from, to, type, null); + } + + private MtaGraphEdge buildEdge(String from, String to, String type, String name) { + return ImmutableMtaGraphEdge.builder() + .from(from) + .to(to) + .type(type) + .name(name) + .build(); } @Override @@ -185,4 +293,79 @@ private String getVersion(Version version) { return version != null ? version.toString() : null; } + @Override + public ResponseEntity buildGraphFromDescriptor(String spaceGuid, String descriptorYaml) { + DeploymentDescriptor descriptor = descriptorParserFacadeFactory.getInstance() + .parseDeploymentDescriptor(descriptorYaml); + return ResponseEntity.ok() + .body(buildGraphFromDescriptor(descriptor)); + } + + @Override + public ResponseEntity buildGraphFromDescriptorAsDot(String spaceGuid, String descriptorYaml) { + DeploymentDescriptor descriptor = descriptorParserFacadeFactory.getInstance() + .parseDeploymentDescriptor(descriptorYaml); + MtaGraph graph = buildGraphFromDescriptor(descriptor); + return ResponseEntity.ok() + .contentType(MediaType.TEXT_PLAIN) + .body(toDot(graph)); + } + + private String toDot(MtaGraph graph) { + StringBuilder sb = new StringBuilder("digraph mta {\n rankdir=LR;\n"); + for (MtaGraphNode node : graph.getNodes()) { + String shape = "resource".equals(node.getType()) ? "cylinder" : "box"; + sb.append(" \"").append(node.getId()).append("\" [shape=").append(shape).append("];\n"); + } + for (MtaGraphEdge edge : graph.getEdges()) { + String label = edge.getName() != null ? edge.getType() + " " + edge.getName() : edge.getType(); + sb.append(" \"").append(edge.getFrom()).append("\" -> \"").append(edge.getTo()) + .append("\" [label=\"").append(label).append("\"];\n"); + } + sb.append("}\n"); + return sb.toString(); + } + + private MtaGraph buildGraphFromDescriptor(DeploymentDescriptor descriptor) { + // provided-dep name → module name that provides it + Map providedDepToModule = new HashMap<>(); + for (org.cloudfoundry.multiapps.mta.model.Module module : descriptor.getModules()) { + for (org.cloudfoundry.multiapps.mta.model.ProvidedDependency provided : module.getProvidedDependencies()) { + providedDepToModule.put(provided.getName(), module.getName()); + } + } + + // resource name → node (for quick lookup) + java.util.Set resourceNames = descriptor.getResources() + .stream() + .map(org.cloudfoundry.multiapps.mta.model.Resource::getName) + .collect(Collectors.toSet()); + + List nodes = new ArrayList<>(); + for (org.cloudfoundry.multiapps.mta.model.Module module : descriptor.getModules()) { + nodes.add(buildNode(module.getName(), "module")); + } + for (org.cloudfoundry.multiapps.mta.model.Resource resource : descriptor.getResources()) { + nodes.add(buildNode(resource.getName(), "resource")); + } + + List edges = new ArrayList<>(); + for (org.cloudfoundry.multiapps.mta.model.Module module : descriptor.getModules()) { + for (org.cloudfoundry.multiapps.mta.model.RequiredDependency req : module.getRequiredDependencies()) { + String depName = req.getName(); + String providerModule = providedDepToModule.get(depName); + if (providerModule != null) { + edges.add(buildEdge(providerModule, module.getName(), "provides", depName)); + } else if (resourceNames.contains(depName)) { + edges.add(buildEdge(module.getName(), depName, "requires-resource", depName)); + } + } + } + + return ImmutableMtaGraph.builder() + .nodes(nodes) + .edges(edges) + .build(); + } + }