diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRoute.java b/src/main/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRoute.java index e42f0008b..ccfe65b2a 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRoute.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRoute.java @@ -1,14 +1,20 @@ package com.sap.cloudfoundry.client.facade.adapters; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + import org.cloudfoundry.client.v3.routes.Route; import org.immutables.value.Value; +import com.sap.cloudfoundry.client.facade.Nullable; import com.sap.cloudfoundry.client.facade.domain.CloudRoute; import com.sap.cloudfoundry.client.facade.domain.ImmutableCloudDomain; -import com.sap.cloudfoundry.client.facade.domain.ImmutableCloudRoute; import com.sap.cloudfoundry.client.facade.domain.ImmutableCloudMetadata; - -import java.util.UUID; +import com.sap.cloudfoundry.client.facade.domain.ImmutableCloudRoute; +import com.sap.cloudfoundry.client.facade.domain.ImmutableRouteDestination; +import com.sap.cloudfoundry.client.facade.domain.RouteDestination; @Value.Immutable public abstract class RawCloudRoute extends RawCloudEntity { @@ -16,9 +22,13 @@ public abstract class RawCloudRoute extends RawCloudEntity { @Value.Parameter public abstract Route getRoute(); + @Nullable + public abstract UUID getApplicationGuid(); + @Override public CloudRoute derive() { Route route = getRoute(); + List destinations = mapDestinations(); String domainGuid = route.getRelationships() .getDomain() .getData() @@ -35,6 +45,8 @@ public CloudRoute derive() { .build()) .path(route.getPath()) .url(route.getUrl()) + .destinations(destinations) + .requestedProtocol(computeRequestedProtocol(destinations)) .build(); } @@ -43,7 +55,8 @@ private static String computeDomain(Route route) { if (!route.getHost() .isEmpty()) { domain = domain.substring(route.getHost() - .length() + 1); + .length() + + 1); } if (!route.getPath() .isEmpty()) { @@ -55,4 +68,32 @@ private static String computeDomain(Route route) { return domain; } + private List mapDestinations() { + return getRoute().getDestinations() + .stream() + .map(destination -> ImmutableRouteDestination.builder() + .metadata(ImmutableCloudMetadata.builder() + .guid(UUID.fromString(destination.getDestinationId())) + .build()) + .applicationGuid(UUID.fromString(destination.getApplication() + .getApplicationId())) + .port(destination.getPort()) + .weight(destination.getWeight()) + .protocol(destination.getProtocol()) + .build()) + .collect(Collectors.toList()); + } + + private String computeRequestedProtocol(List destinations) { + UUID applicationGuid = getApplicationGuid(); + if (applicationGuid == null) { + return null; + } + return destinations.stream() + .filter(destination -> Objects.equals(destination.getApplicationGuid(), applicationGuid)) + .findFirst() + .map(RouteDestination::getProtocol) + .orElse(null); + } + } diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/domain/CloudRoute.java b/src/main/java/com/sap/cloudfoundry/client/facade/domain/CloudRoute.java index 1f1cf498f..94d12bd18 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/domain/CloudRoute.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/domain/CloudRoute.java @@ -1,13 +1,14 @@ package com.sap.cloudfoundry.client.facade.domain; +import java.util.List; +import java.util.Objects; + import org.immutables.value.Value; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.sap.cloudfoundry.client.facade.Nullable; -import java.util.Objects; - @Value.Immutable @JsonSerialize(as = ImmutableCloudRoute.class) @JsonDeserialize(as = ImmutableCloudRoute.class) @@ -29,6 +30,12 @@ public int getAppsUsingRoute() { @Nullable public abstract Integer getPort(); + @Nullable + public abstract String getRequestedProtocol(); + + @Nullable + public abstract List getDestinations(); + public abstract String getUrl(); @Override @@ -58,10 +65,12 @@ public boolean equals(Object object) { var thisDomain = getDomain().getName(); var otherDomain = otherRoute.getDomain() .getName(); + // @formatter:off return thisDomain.equals(otherDomain) && areEmptyOrEqual(getHost(), otherRoute.getHost()) && areEmptyOrEqual(getPath(), otherRoute.getPath()) && Objects.equals(getPort(), otherRoute.getPort()); + // @formatter:on } private static boolean areEmptyOrEqual(String lhs, String rhs) { diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/domain/RouteDestination.java b/src/main/java/com/sap/cloudfoundry/client/facade/domain/RouteDestination.java new file mode 100644 index 000000000..924d3be28 --- /dev/null +++ b/src/main/java/com/sap/cloudfoundry/client/facade/domain/RouteDestination.java @@ -0,0 +1,30 @@ +package com.sap.cloudfoundry.client.facade.domain; + +import java.util.UUID; + +import org.immutables.value.Value; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.sap.cloudfoundry.client.facade.Nullable; + +@Value.Immutable +@JsonSerialize(as = ImmutableRouteDestination.class) +@JsonDeserialize(as = ImmutableRouteDestination.class) +public abstract class RouteDestination extends CloudEntity implements Derivable { + + public abstract UUID getApplicationGuid(); + + @Nullable + public abstract Integer getPort(); + + @Nullable + public abstract Integer getWeight(); + + public abstract String getProtocol(); + + @Override + public RouteDestination derive() { + return this; + } +} diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java b/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java index 2d3f53a74..5ffe4d448 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java @@ -9,6 +9,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -204,6 +205,7 @@ import com.sap.cloudfoundry.client.facade.domain.ImmutableInstancesInfo; import com.sap.cloudfoundry.client.facade.domain.ImmutableUpload; import com.sap.cloudfoundry.client.facade.domain.InstancesInfo; +import com.sap.cloudfoundry.client.facade.domain.RouteDestination; import com.sap.cloudfoundry.client.facade.domain.ServicePlanVisibility; import com.sap.cloudfoundry.client.facade.domain.Staging; import com.sap.cloudfoundry.client.facade.domain.Status; @@ -412,7 +414,7 @@ private void addRoutes(Set routes, UUID applicationGuid) { UUID domainGuid = domains.get(route.getDomain() .getName()); UUID routeGuid = getOrAddRoute(domainGuid, route.getHost(), route.getPath()); - bindRoute(routeGuid, applicationGuid); + bindRoute(routeGuid, applicationGuid, route.getRequestedProtocol()); } } @@ -703,7 +705,10 @@ public CloudProcess getApplicationProcess(UUID applicationGuid) { @Override public List getApplicationRoutes(UUID applicationGuid) { - return fetchList(() -> getRouteResourcesByAppGuid(applicationGuid), ImmutableRawCloudRoute::of); + return fetchList(() -> getRouteResourcesByAppGuid(applicationGuid), routeResource -> ImmutableRawCloudRoute.builder() + .route(routeResource) + .applicationGuid(applicationGuid) + .build()); } @Override @@ -1261,35 +1266,58 @@ public void updateApplicationStaging(String applicationName, Staging staging) { @Override public void updateApplicationRoutes(String applicationName, Set updatedRoutes) { UUID applicationGuid = getApplicationGuid(applicationName); - List appRoutes = getRouteResourcesByAppGuid(applicationGuid).collectList() - .defaultIfEmpty(Collections.emptyList()) - .block(); - - List outdatedRoutes = getOutdatedRoutes(appRoutes, updatedRoutes); - Set newRoutes = getNewRoutes(updatedRoutes, appRoutes); - + List appRoutes = fetchList(() -> getRouteResourcesByAppGuid(applicationGuid), ImmutableRawCloudRoute::of); + Set outdatedRoutes = getOutdatedRoutes(applicationGuid, appRoutes, updatedRoutes); + Set newRoutes = getNewRoutes(applicationGuid, appRoutes, updatedRoutes); removeRoutes(outdatedRoutes, applicationGuid); addRoutes(newRoutes, applicationGuid); } - private List getOutdatedRoutes(List currentRoutes, Set updatedRoutes) { - Set urls = updatedRoutes.stream() - .map(CloudRoute::getUrl) - .collect(Collectors.toSet()); + private Set getOutdatedRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { return currentRoutes.stream() - .filter(routeResource -> !urls.contains(routeResource.getUrl())) - .collect(Collectors.toList()); + .filter(currentRoute -> isRouteOutdated(applicationGuid, currentRoute, updatedRoutes)) + .collect(Collectors.toSet()); } - private Set getNewRoutes(Set updatedRoutes, List currentRoutes) { - Set urls = currentRoutes.stream() - .map(RouteResource::getUrl) - .collect(Collectors.toSet()); + private boolean isRouteOutdated(UUID applicationGuid, CloudRoute currentRoute, Set updatedRoutes) { + Optional updatedRoute = findRoute(currentRoute.getUrl(), updatedRoutes); + if (updatedRoute.isEmpty()) { + return true; + } + return isProtocolChanged(applicationGuid, currentRoute, updatedRoute.get()); + } + + private Optional findRoute(String url, Collection routes) { + return routes.stream() + .filter(route -> Objects.equals(url, route.getUrl())) + .findFirst(); + } + + private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, CloudRoute updatedRoute) { + if (updatedRoute.getRequestedProtocol() == null) { + return false; + } + return currentRoute.getDestinations() + .stream() + .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) + .noneMatch(routeDestination -> Objects.equals(routeDestination.getProtocol(), + updatedRoute.getRequestedProtocol())); + } + + private Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { return updatedRoutes.stream() - .filter(route -> !urls.contains(route.getUrl())) + .filter(updatedRoute -> isRouteUpdated(applicationGuid, updatedRoute, currentRoutes)) .collect(Collectors.toSet()); } + private boolean isRouteUpdated(UUID applicationGuid, CloudRoute updatedRoute, List currentRoutes) { + Optional currentRoute = findRoute(updatedRoute.getUrl(), currentRoutes); + if (currentRoute.isEmpty()) { + return true; + } + return isProtocolChanged(applicationGuid, currentRoute.get(), updatedRoute); + } + @Override public String updateServiceBroker(CloudServiceBroker serviceBroker) { Assert.notNull(serviceBroker, "Service broker must not be null."); @@ -1804,13 +1832,12 @@ private void assertSpaceProvided(String operation) { Assert.notNull(target, "Unable to " + operation + " without specifying organization and space to use."); } - private void removeRoutes(List routes, UUID applicationGuid) { - for (RouteResource route : routes) { - for (Destination destination : route.getDestinations()) { - if (destination.getApplication() - .getApplicationId() - .equals(applicationGuid.toString())) { - unbindRoute(route.getId(), destination.getDestinationId()); + private void removeRoutes(Set routes, UUID applicationGuid) { + for (CloudRoute route : routes) { + for (RouteDestination destination : route.getDestinations()) { + if (destination.getApplicationGuid() + .equals(applicationGuid)) { + unbindRoute(route.getGuid(), destination.getGuid()); } } } @@ -1826,29 +1853,30 @@ private void validateDomainForRoute(CloudRoute route, Map existing } } - private void unbindRoute(String routeGuid, String destinationGuid) { + private void unbindRoute(UUID routeGuid, UUID destinationGuid) { delegate.routesV3() .removeDestinations(RemoveRouteDestinationsRequest.builder() - .routeId(routeGuid) - .destinationId(destinationGuid) + .routeId(routeGuid.toString()) + .destinationId(destinationGuid.toString()) .build()) .block(); } - private void bindRoute(UUID routeGuid, UUID applicationGuid) { + private void bindRoute(UUID routeGuid, UUID applicationGuid, String protocol) { delegate.routesV3() .insertDestinations(InsertRouteDestinationsRequest.builder() .routeId(routeGuid.toString()) - .destination(createDestination(applicationGuid)) + .destination(createDestination(applicationGuid, protocol)) .build()) .block(); } - private Destination createDestination(UUID applicationGuid) { + private Destination createDestination(UUID applicationGuid, String protocol) { return Destination.builder() .application(org.cloudfoundry.client.v3.routes.Application.builder() .applicationId(applicationGuid.toString()) .build()) + .protocol(protocol) .build(); } diff --git a/src/test/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRouteTest.java b/src/test/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRouteTest.java index 1648f86f9..4e15668a6 100644 --- a/src/test/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRouteTest.java +++ b/src/test/java/com/sap/cloudfoundry/client/facade/adapters/RawCloudRouteTest.java @@ -1,6 +1,5 @@ package com.sap.cloudfoundry.client.facade.adapters; -import java.util.Collections; import java.util.List; import java.util.UUID; @@ -24,7 +23,13 @@ class RawCloudRouteTest { private static final String DOMAIN_NAME = "example.com"; private static final CloudDomain DOMAIN = buildTestDomain(); private static final Integer APPS_USING_ROUTE = 2; - private static final List DESTINATIONS = buildTestDestinations(APPS_USING_ROUTE); + private static final UUID DESTINATION_1_GUID = UUID.randomUUID(); + private static final UUID APPLICATION_1_GUID = UUID.randomUUID(); + private static final String DESTINATION_1_PROTOCOL = "http2"; + private static final UUID DESTINATION_2_GUID = UUID.randomUUID(); + private static final UUID APPLICATION_2_GUID = UUID.randomUUID(); + private static final String DESTINATION_2_PROTOCOL = "http1"; + private static final List DESTINATIONS = buildTestDestinations(); @Test void testDerive() { @@ -72,12 +77,19 @@ private static CloudDomain buildTestDomain() { .build(); } - private static List buildTestDestinations(int count) { - return Collections.nCopies(count, Destination.builder() - .application(Application.builder() - .applicationId("") - .build()) - .build()); + private static List buildTestDestinations() { + return List.of(buildDestination(DESTINATION_1_GUID.toString(), APPLICATION_1_GUID.toString(), DESTINATION_1_PROTOCOL), + buildDestination(DESTINATION_2_GUID.toString(), APPLICATION_2_GUID.toString(), DESTINATION_2_PROTOCOL)); + } + + private static Destination buildDestination(String destinationGuid, String applicationGuid, String protocol) { + return Destination.builder() + .destinationId(destinationGuid) + .application(Application.builder() + .applicationId(applicationGuid) + .build()) + .protocol(protocol) + .build(); } private static ToOneRelationship buildToOneRelationship(UUID guid) {