diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java index 5916d0a..28bed10 100644 --- a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CloudFoundryAcceptanceTest.java @@ -146,6 +146,10 @@ class CloudFoundryAcceptanceTest { return getServiceInstanceMono(serviceInstanceName).blockOptional(); } + Optional getServiceInstance(String serviceInstanceName, String space) { + return getServiceInstanceMono(serviceInstanceName, space).blockOptional(); + } + Mono getServiceInstanceMono() { return getServiceInstanceMono(SERVICE_INSTANCE_NAME); } @@ -154,6 +158,10 @@ class CloudFoundryAcceptanceTest { return cloudFoundryService.getServiceInstance(serviceInstanceName); } + private Mono getServiceInstanceMono(String serviceInstanceName, String space) { + return cloudFoundryService.getServiceInstance(serviceInstanceName, space); + } + Optional getApplicationSummaryByName(String appName) { return cloudFoundryService .getApplications() diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CreateInstanceWithServicesAndTargetAcceptanceTest.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CreateInstanceWithServicesAndTargetAcceptanceTest.java new file mode 100644 index 0000000..81e0d06 --- /dev/null +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/CreateInstanceWithServicesAndTargetAcceptanceTest.java @@ -0,0 +1,67 @@ +package org.springframework.cloud.appbroker.acceptance; + +import java.util.List; +import java.util.Optional; + +import org.cloudfoundry.operations.applications.ApplicationSummary; +import org.cloudfoundry.operations.services.ServiceInstanceSummary; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class CreateInstanceWithServicesAndTargetAcceptanceTest extends CloudFoundryAcceptanceTest { + + private static final String BROKER_APP_SERVICES = "services-target"; + private static final String SI_1_NAME = "service-instance-1"; + private static final String SERVICE_1_NAME = "db-service"; + + @Test + @AppBrokerTestProperties({ + "spring.cloud.appbroker.services[0].service-name=example", + "spring.cloud.appbroker.services[0].plan-name=standard", + "spring.cloud.appbroker.services[0].apps[0].name=" + BROKER_APP_SERVICES, + "spring.cloud.appbroker.services[0].apps[0].path=classpath:demo.jar", + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + SI_1_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + SI_1_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + SERVICE_1_NAME, + "spring.cloud.appbroker.services[0].services[0].plan=standard", + "spring.cloud.appbroker.services[0].target.name=SpacePerServiceInstance" + }) + void shouldPushAppWithServicesBind() { + // given that a service is available in the marketplace + setupServiceBrokerForService("db-service"); + + // when a service instance is created with target + createServiceInstance(); + Optional serviceInstance = getServiceInstance(); + assertThat(serviceInstance).isNotEmpty(); + + // then backing applications are deployed in a space named as the service instance id + String space = serviceInstance.orElseThrow(RuntimeException::new).getId(); + + Optional backingApplication = + getApplicationSummaryByNameAndSpace(BROKER_APP_SERVICES, space); + assertThat(backingApplication).isNotEmpty(); + + // and the backing service bind to it + Optional backingServiceInstance = getServiceInstance(SI_1_NAME, space); + assertThat(backingServiceInstance).isNotEmpty(); + assertThat(backingServiceInstance.get().getApplications()).contains(BROKER_APP_SERVICES); + + // when the service instance is deleted + deleteServiceInstance(); + + // then the space is deleted + List spaces = getSpaces(); + assertThat(spaces).doesNotContain(space); + } + + @Override + @AfterEach + void tearDown() { + super.tearDown(); + + deleteServiceBrokerForService("db-service"); + } +} diff --git a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java index 41b88f4..51bcc80 100644 --- a/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java +++ b/spring-cloud-app-broker-acceptance-tests/src/test/java/org.springframework.cloud.appbroker.acceptance/fixtures/cf/CloudFoundryService.java @@ -183,6 +183,15 @@ public class CloudFoundryService { getServiceInstanceFromList(serviceInstanceName)); } + public Mono getServiceInstance(String serviceInstanceName, String space) { + return loggingMono( + createOperationsForSpace(space) + .services() + .listInstances() + .filter(si -> si.getName().equals(serviceInstanceName)) + .next()); + } + public Mono> getApplications() { return loggingMono( cloudFoundryOperations.applications().list().collectList()); @@ -193,8 +202,7 @@ public class CloudFoundryService { } public Mono getApplicationSummaryByNameAndSpace(String appName, String space) { - final String defaultOrg = cloudFoundryProperties.getDefaultOrg(); - return loggingFlux(createOperationsForSpace(space, defaultOrg) + return loggingFlux(createOperationsForSpace(space) .applications() .list() .filter(applicationSummary -> applicationSummary.getName().equals(appName)) @@ -209,9 +217,8 @@ public class CloudFoundryService { } public Mono getApplicationEnvironmentByAppNameAndSpace(String appName, String space) { - final String defaultOrg = cloudFoundryProperties.getDefaultOrg(); return loggingMono( - createOperationsForSpace(space, defaultOrg) + createOperationsForSpace(space) .applications() .getEnvironments(GetApplicationEnvironmentsRequest.builder().name(appName).build())); } @@ -250,7 +257,8 @@ public class CloudFoundryService { .then(getDefaultOrg(organizationOperations)))); } - private CloudFoundryOperations createOperationsForSpace(String space, String defaultOrg) { + private CloudFoundryOperations createOperationsForSpace(String space) { + final String defaultOrg = cloudFoundryProperties.getDefaultOrg(); return DefaultCloudFoundryOperations.builder() .from((DefaultCloudFoundryOperations) cloudFoundryOperations) .organization(defaultOrg) diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java index d21de14..45c4eaf 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/BackingService.java @@ -26,15 +26,17 @@ public class BackingService { private String name; private String plan; private Map parameters; + private Map properties; private BackingService() { } - BackingService(String serviceInstanceName, String name, String plan, Map parameters) { + BackingService(String serviceInstanceName, String name, String plan, Map parameters, Map properties) { this.serviceInstanceName = serviceInstanceName; this.name = name; this.plan = plan; this.parameters = parameters; + this.properties = properties; } BackingService(BackingService backingServiceToCopy) { @@ -44,6 +46,9 @@ public class BackingService { this.parameters = backingServiceToCopy.parameters == null ? new HashMap<>() : new HashMap<>(backingServiceToCopy.parameters); + this.properties = backingServiceToCopy.properties == null + ? new HashMap<>() + : new HashMap<>(backingServiceToCopy.properties); } public String getServiceInstanceName() { @@ -78,6 +83,14 @@ public class BackingService { this.parameters = parameters; } + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -90,12 +103,13 @@ public class BackingService { return Objects.equals(serviceInstanceName, that.serviceInstanceName) && Objects.equals(name, that.name) && Objects.equals(plan, that.plan) && - Objects.equals(parameters, that.parameters); + Objects.equals(parameters, that.parameters) && + Objects.equals(properties, that.properties); } @Override public int hashCode() { - return Objects.hash(serviceInstanceName, name, plan, parameters); + return Objects.hash(serviceInstanceName, name, plan, parameters, properties); } @Override @@ -105,6 +119,7 @@ public class BackingService { ", name='" + name + '\'' + ", plan='" + plan + '\'' + ", parameters=" + parameters + + ", properties=" + properties + '}'; } @@ -118,12 +133,13 @@ public class BackingService { private String name; private String plan; private Map parameters = new HashMap<>(); + private Map properties = new HashMap<>(); BackingServiceBuilder() { } public BackingService build() { - return new BackingService(serviceInstanceName, name, plan, parameters); + return new BackingService(serviceInstanceName, name, plan, parameters, properties); } public BackingServiceBuilder serviceInstanceName(String serviceInstanceName) { @@ -145,6 +161,11 @@ public class BackingService { this.parameters = parameters; return this; } + + public BackingServiceBuilder properties(Map properties) { + this.properties = properties; + return this; + } } } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java index 2a83d4e..ff7877b 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/deployer/DeployerClient.java @@ -60,6 +60,7 @@ public class DeployerClient { .name(backingService.getName()) .plan(backingService.getPlan()) .parameters(backingService.getParameters()) + .properties(backingService.getProperties()) .build()) .doOnRequest(l -> log.info("Creating backing service {}", backingService.getName())) .doOnSuccess(d -> log.info("Finished creating backing service {}", backingService.getName())) @@ -70,6 +71,7 @@ public class DeployerClient { Mono deleteServiceInstance(BackingService backingService) { return appDeployer.deleteServiceInstance(DeleteServiceInstanceRequest.builder() .name(backingService.getServiceInstanceName()) + .properties(backingService.getProperties()) .build()) .map(DeleteServiceInstanceResponse::getName); } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/SpacePerServiceInstance.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/SpacePerServiceInstance.java index 6e1a85d..d3c5b70 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/SpacePerServiceInstance.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/SpacePerServiceInstance.java @@ -16,9 +16,8 @@ package org.springframework.cloud.appbroker.extensions.targets; -import reactor.core.publisher.Mono; +import java.util.Map; -import org.springframework.cloud.appbroker.deployer.BackingApplication; import org.springframework.cloud.appbroker.deployer.DeploymentProperties; public class SpacePerServiceInstance extends TargetFactory { @@ -32,13 +31,11 @@ public class SpacePerServiceInstance extends TargetFactory apply(BackingApplication backingApplication, String serviceInstanceId) { - backingApplication.addProperty(DeploymentProperties.HOST_PROPERTY_KEY, - backingApplication.getName() + "-" + serviceInstanceId); - backingApplication.addProperty(DeploymentProperties.TARGET_PROPERTY_KEY, - serviceInstanceId); + private Map apply(Map properties, String name, String serviceInstanceId) { + properties.put(DeploymentProperties.HOST_PROPERTY_KEY, name + "-" + serviceInstanceId); + properties.put(DeploymentProperties.TARGET_PROPERTY_KEY, serviceInstanceId); - return Mono.just(backingApplication); + return properties; } static class Config { diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/Target.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/Target.java index 1036ec7..a7f871e 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/Target.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/Target.java @@ -16,11 +16,9 @@ package org.springframework.cloud.appbroker.extensions.targets; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.appbroker.deployer.BackingApplication; +import java.util.Map; public interface Target { - Mono apply(BackingApplication backingApplication, String serviceInstanceId); + Map apply(Map properties, String name, String serviceInstanceId); } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/TargetService.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/TargetService.java index 2e31c32..818a132 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/TargetService.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/extensions/targets/TargetService.java @@ -18,11 +18,13 @@ package org.springframework.cloud.appbroker.extensions.targets; import java.util.Collections; import java.util.List; +import java.util.Map; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.appbroker.deployer.BackingApplication; +import org.springframework.cloud.appbroker.deployer.BackingService; import org.springframework.cloud.appbroker.deployer.TargetSpec; import org.springframework.cloud.appbroker.extensions.ExtensionLocator; @@ -34,15 +36,15 @@ public class TargetService { locator = new ExtensionLocator<>(factories); } - public Mono> add(List backingApplications, - TargetSpec targetSpec, - String serviceInstanceId) { - + public Mono> addToBackingApplications(List backingApplications, + TargetSpec targetSpec, + String serviceInstanceId) { return Flux.fromIterable(backingApplications) .flatMap(backingApplication -> { if (targetSpec != null) { Target target = locator.getByName(targetSpec.getName(), Collections.emptyMap()); - return target.apply(backingApplication, serviceInstanceId); + Map properties = target.apply(backingApplication.getProperties(), backingApplication.getName(), serviceInstanceId); + backingApplication.setProperties(properties); } return Mono.just(backingApplication); @@ -50,4 +52,21 @@ public class TargetService { .collectList(); } + + public Mono> addToBackingServices(List backingServices, + TargetSpec targetSpec, + String serviceInstanceId) { + return Flux.fromIterable(backingServices) + .flatMap(backingService -> { + if (targetSpec != null) { + Target target = locator.getByName(targetSpec.getName(), Collections.emptyMap()); + Map properties = target.apply(backingService.getProperties(), backingService.getName(), serviceInstanceId); + backingService.setProperties(properties); + } + return Mono.just(backingService); + }) + .collectList(); + + } + } diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java index b75b25e..44d1166 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflow.java @@ -63,10 +63,11 @@ public class AppDeploymentCreateServiceInstanceWorkflow public Flux create(CreateServiceInstanceRequest request) { return getBackingServicesForService(request.getServiceDefinition(), request.getPlanId()) - .flatMapMany(backingServicesProvisionService::createServiceInstance) + .flatMapMany(backingService -> targetService.addToBackingServices(backingService, getTargetForService(request.getServiceDefinition(), request.getPlanId()) , request.getServiceInstanceId())) + .flatMap(backingServicesProvisionService::createServiceInstance) .thenMany( getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId()) - .flatMap(backingApps -> targetService.add(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()) , request.getServiceInstanceId())) + .flatMap(backingApps -> targetService.addToBackingApplications(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()) , request.getServiceInstanceId())) .flatMap(backingApps -> parametersTransformationService.transformParameters(backingApps, request.getParameters())) .flatMap(backingApplications -> credentialProviderService.addCredentials(backingApplications, request.getServiceInstanceId())) .flatMapMany(deploymentService::deploy) diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java index e0a8ac4..af77d18 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflow.java @@ -59,11 +59,12 @@ public class AppDeploymentDeleteServiceInstanceWorkflow public Flux delete(DeleteServiceInstanceRequest request) { return getBackingServicesForService(request.getServiceDefinition(), request.getPlanId()) - .flatMapMany(backingServicesProvisionService::deleteServiceInstance) + .flatMapMany(backingService -> targetService.addToBackingServices(backingService, getTargetForService(request.getServiceDefinition(), request.getPlanId()) , request.getServiceInstanceId())) + .flatMap(backingServicesProvisionService::deleteServiceInstance) .thenMany( getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId()) .flatMap(backingApplications -> credentialProviderService.deleteCredentials(backingApplications, request.getServiceInstanceId())) - .flatMap(backingApps -> targetService.add(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()), request.getServiceInstanceId())) + .flatMap(backingApps -> targetService.addToBackingApplications(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()), request.getServiceInstanceId())) .flatMapMany(deploymentService::undeploy) .doOnRequest(l -> log.info("Undeploying applications {}", brokeredServices)) .doOnEach(s -> log.info("Finished undeploying {}", s)) diff --git a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java index f480657..7d807f7 100644 --- a/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java +++ b/spring-cloud-app-broker-core/src/main/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflow.java @@ -53,7 +53,7 @@ public class AppDeploymentUpdateServiceInstanceWorkflow public Flux update(UpdateServiceInstanceRequest request) { return getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId()) - .flatMap(backingApps -> targetService.add(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()), request.getServiceInstanceId())) + .flatMap(backingApps -> targetService.addToBackingApplications(backingApps, getTargetForService(request.getServiceDefinition(), request.getPlanId()), request.getServiceInstanceId())) .flatMap(backingApps -> parametersTransformationService.transformParameters(backingApps, request.getParameters())) .flatMapMany(deploymentService::deploy) diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/targets/TargetServiceTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/targets/TargetServiceTest.java index 92a673f..2ce3293 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/targets/TargetServiceTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/extensions/targets/TargetServiceTest.java @@ -44,7 +44,7 @@ class TargetServiceTest { BackingApplication backingApplication = BackingApplication.builder().name("app-name").build(); //when add gets called - List updatedBackingApplications = targetService.add(singletonList(backingApplication), targetSpec, "service-id").block(); + List updatedBackingApplications = targetService.addToBackingApplications(singletonList(backingApplication), targetSpec, "service-id").block(); //then a host and space are added BackingApplication updatedBackingApplication = updatedBackingApplications.get(0); @@ -60,7 +60,7 @@ class TargetServiceTest { BackingApplication backingApplication2 = BackingApplication.builder().name("app-name2").build(); //when add gets called - List updatedBackingApplications = targetService.add(Lists.list(backingApplication1, backingApplication2), targetSpec, "service-id").block(); + List updatedBackingApplications = targetService.addToBackingApplications(Lists.list(backingApplication1, backingApplication2), targetSpec, "service-id").block(); //then a host and space are added BackingApplication updatedBackingApplication1 = updatedBackingApplications.get(0); diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java index 328803e..863ed1c 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentCreateServiceInstanceWorkflowTest.java @@ -101,6 +101,7 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { .build()) .build(); + targetSpec = TargetSpec.builder().name("TargetSpace").build(); BrokeredServices brokeredServices = BrokeredServices .builder() .service(BrokeredService @@ -173,7 +174,8 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { .verifyComplete(); final String expectedServiceId = "service-instance-id"; - verify(targetService).add(backingApps, targetSpec, expectedServiceId); + verify(targetService).addToBackingServices(backingServices, targetSpec, expectedServiceId); + verify(targetService).addToBackingApplications(backingApps, targetSpec, expectedServiceId); verifyNoMoreInteractionsWithServices(); } @@ -196,8 +198,10 @@ class AppDeploymentCreateServiceInstanceWorkflowTest { .willReturn(Mono.just(backingApps)); given(this.credentialProviderService.addCredentials(eq(backingApps), eq(request.getServiceInstanceId()))) .willReturn(Mono.just(backingApps)); - given(this.targetService.add(eq(backingApps), eq(targetSpec), eq(request.getServiceInstanceId()))) + given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq(request.getServiceInstanceId()))) .willReturn(Mono.just(backingApps)); + given(this.targetService.addToBackingServices(eq(backingServices), eq(targetSpec), eq(request.getServiceInstanceId()))) + .willReturn(Mono.just(backingServices)); given(this.backingServicesProvisionService.createServiceInstance(eq(backingServices))) .willReturn(Flux.just("my-service-instance")); } diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java index a25ccf7..fd0ef3d 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentDeleteServiceInstanceWorkflowTest.java @@ -92,6 +92,7 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest { .build()) .build(); + targetSpec = TargetSpec.builder().name("TargetSpace").build(); BrokeredServices brokeredServices = BrokeredServices .builder() .service(BrokeredService @@ -121,8 +122,10 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest { .willReturn(Flux.just("undeployed1", "undeployed2")); given(this.credentialProviderService.deleteCredentials(eq(backingApps), eq(request.getServiceInstanceId()))) .willReturn(Mono.just(backingApps)); - given(this.targetService.add(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) + given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) .willReturn(Mono.just(backingApps)); + given(this.targetService.addToBackingServices(eq(backingServices), eq(targetSpec), eq("service-instance-id"))) + .willReturn(Mono.just(backingServices)); given(this.backingServicesProvisionService.deleteServiceInstance(eq(backingServices))) .willReturn(Flux.just("my-service-instance")); diff --git a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java index bcadf8b..8d302fe 100644 --- a/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java +++ b/spring-cloud-app-broker-core/src/test/java/org/springframework/cloud/appbroker/workflow/instance/AppDeploymentUpdateServiceInstanceWorkflowTest.java @@ -75,6 +75,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { .build()) .build(); + targetSpec = TargetSpec.builder().name("TargetSpace").build(); BrokeredServices brokeredServices = BrokeredServices .builder() .service(BrokeredService.builder() @@ -88,8 +89,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { updateServiceInstanceWorkflow = new AppDeploymentUpdateServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService, parametersTransformationService, - targetService) - ; + targetService); } @Test @@ -99,7 +99,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { given(this.backingAppDeploymentService.deploy(eq(backingApps))) .willReturn(Flux.just("app1", "app2")); - given(this.targetService.add(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) + given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) .willReturn(Mono.just(backingApps)); given(this.parametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters()))) .willReturn(Mono.just(backingApps)); @@ -124,7 +124,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest { .willReturn(Flux.just("app1", "app2")); given(this.parametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters()))) .willReturn(Mono.just(backingApps)); - given(this.targetService.add(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) + given(this.targetService.addToBackingApplications(eq(backingApps), eq(targetSpec), eq("service-instance-id"))) .willReturn(Mono.just(backingApps)); StepVerifier diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java index 6997c6c..6b8f7e1 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/main/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployer.java @@ -507,29 +507,63 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware @Override public Mono createServiceInstance(CreateServiceInstanceRequest request) { - return this.operations + org.cloudfoundry.operations.services.CreateServiceInstanceRequest createServiceInstanceRequest = + org.cloudfoundry.operations.services.CreateServiceInstanceRequest + .builder() + .serviceInstanceName(request.getServiceInstanceName()) + .serviceName(request.getName()) + .planName(request.getPlan()) + .parameters(request.getParameters()) + .build(); + + Mono createServiceInstanceResponseMono = + Mono.just(CreateServiceInstanceResponse.builder().name(request.getServiceInstanceName()).build()); + + if (request.getProperties().containsKey(DeploymentProperties.TARGET_PROPERTY_KEY)) { + return createSpace(request.getProperties().get(DeploymentProperties.TARGET_PROPERTY_KEY)) + .then( + createCloudFoundryOperationsForSpace(request.getProperties().get(DeploymentProperties.TARGET_PROPERTY_KEY)) + .services() + .createInstance(createServiceInstanceRequest) + .then(createServiceInstanceResponseMono)); + } + return operations .services() - .createInstance( - org.cloudfoundry.operations.services.CreateServiceInstanceRequest - .builder() - .serviceInstanceName(request.getServiceInstanceName()) - .serviceName(request.getName()) - .planName(request.getPlan()) - .parameters(request.getParameters()) - .build()) - .then(Mono.just(CreateServiceInstanceResponse.builder().name(request.getServiceInstanceName()).build())); + .createInstance(createServiceInstanceRequest) + .then(createServiceInstanceResponseMono); } @Override public Mono deleteServiceInstance(DeleteServiceInstanceRequest request) { final String serviceInstanceName = request.getName(); - return this.operations + + final CloudFoundryOperations operations; + if (request.getProperties().containsKey(DeploymentProperties.TARGET_PROPERTY_KEY)) { + operations = createCloudFoundryOperationsForSpace(request.getProperties().get(DeploymentProperties.TARGET_PROPERTY_KEY)); + } + else { + operations = this.operations; + } + + return operations .services() .getInstance(GetServiceInstanceRequest.builder().name(serviceInstanceName).build()) .map(ServiceInstance::getApplications) - .flatMap(unbindApplications(serviceInstanceName)) + .flatMap((Function, Mono>) applications -> + Flux.fromIterable(applications) + .flatMap( + application -> + operations + .services() + .unbind( + UnbindServiceInstanceRequest + .builder() + .applicationName(application) + .serviceInstanceName(serviceInstanceName) + .build()) + ).collectList()) .then( - this.operations + operations .services() .deleteInstance( org.cloudfoundry.operations.services.DeleteServiceInstanceRequest @@ -539,19 +573,4 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware .then(Mono.just(DeleteServiceInstanceResponse.builder().name(serviceInstanceName).build()))); } - private Function, Mono> unbindApplications(String serviceInstanceName) { - return applications -> - Flux.fromIterable(applications) - .flatMap( - application -> - this.operations - .services() - .unbind( - UnbindServiceInstanceRequest - .builder() - .applicationName(application) - .serviceInstanceName(serviceInstanceName) - .build()) - ).collectList(); - } } \ No newline at end of file diff --git a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java index 107c401..f2649ce 100644 --- a/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java +++ b/spring-cloud-app-broker-deployer-cloudfoundry/src/test/java/org/springframework/cloud/appbroker/deployer/cloudfoundry/CloudFoundryAppDeployerTest.java @@ -30,6 +30,7 @@ import org.cloudfoundry.operations.services.ServiceInstance; import org.cloudfoundry.operations.services.ServiceInstanceType; import org.cloudfoundry.operations.services.Services; import org.cloudfoundry.operations.services.UnbindServiceInstanceRequest; +import org.cloudfoundry.operations.spaces.Spaces; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -42,12 +43,14 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import org.springframework.cloud.appbroker.deployer.AppDeployer; +import org.springframework.cloud.appbroker.deployer.CreateServiceInstanceRequest; import org.springframework.cloud.appbroker.deployer.DeleteServiceInstanceRequest; import org.springframework.cloud.appbroker.deployer.DeployApplicationRequest; import org.springframework.cloud.appbroker.deployer.DeploymentProperties; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.ResourceLoader; +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; @@ -70,6 +73,9 @@ class CloudFoundryAppDeployerTest { @Mock private Services services; + @Mock + private Spaces spaces; + @Mock private CloudFoundryOperations cloudFoundryOperations; @@ -86,15 +92,12 @@ class CloudFoundryAppDeployerTest { deploymentProperties = new CloudFoundryDeploymentProperties(); CloudFoundryTargetProperties targetProperties = new CloudFoundryTargetProperties(); - when(applications.pushManifest(any())) - .thenReturn(Mono.empty()); - when(cloudFoundryOperations.applications()) - .thenReturn(applications); - when(resourceLoader.getResource(APP_PATH)) - .thenReturn(new FileSystemResource(APP_PATH)); + when(applications.pushManifest(any())).thenReturn(Mono.empty()); + when(cloudFoundryOperations.applications()).thenReturn(applications); + when(resourceLoader.getResource(APP_PATH)).thenReturn(new FileSystemResource(APP_PATH)); - when(cloudFoundryOperations.services()) - .thenReturn(services); + when(cloudFoundryOperations.services()).thenReturn(services); + when(cloudFoundryOperations.spaces()).thenReturn(spaces); appDeployer = new CloudFoundryAppDeployer(deploymentProperties, cloudFoundryOperations, cloudFoundryClient, targetProperties, resourceLoader); @@ -322,6 +325,7 @@ class CloudFoundryAppDeployerTest { DeleteServiceInstanceRequest request = DeleteServiceInstanceRequest.builder() .name("service-instance-name") + .properties(emptyMap()) .build(); StepVerifier.create( @@ -331,6 +335,31 @@ class CloudFoundryAppDeployerTest { } + @Test + void createServiceInstance() { + when(services.createInstance( + org.cloudfoundry.operations.services.CreateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .serviceName("db-service") + .planName("standard") + .parameters(emptyMap()) + .build())) + .thenReturn(Mono.empty()); + + CreateServiceInstanceRequest request = + CreateServiceInstanceRequest.builder() + .serviceInstanceName("service-instance-name") + .name("db-service") + .plan("standard") + .parameters(emptyMap()) + .build(); + + StepVerifier.create( + appDeployer.createServiceInstance(request)) + .assertNext(response -> assertThat(response.getName()).isEqualTo("service-instance-name")) + .verifyComplete(); + } + private ApplicationManifest.Builder baseManifest() { return ApplicationManifest.builder() .environmentVariable("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}") diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/CreateServiceInstanceRequest.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/CreateServiceInstanceRequest.java index 93f33e5..f9f27cf 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/CreateServiceInstanceRequest.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/CreateServiceInstanceRequest.java @@ -25,18 +25,18 @@ public class CreateServiceInstanceRequest { private final String name; private final String plan; private final Map parameters; - private final String target; + private final Map properties; CreateServiceInstanceRequest(String serviceInstanceName, String name, String plan, Map parameters, - String target) { + Map properties) { this.serviceInstanceName = serviceInstanceName; this.name = name; this.plan = plan; this.parameters = parameters; - this.target = target; + this.properties = properties; } @@ -61,8 +61,8 @@ public class CreateServiceInstanceRequest { return parameters; } - public String getTarget() { - return target; + public Map getProperties() { + return properties; } public static class CreateServiceInstanceRequestBuilder { @@ -71,7 +71,7 @@ public class CreateServiceInstanceRequest { private String name; private String plan; private final Map parameters = new HashMap<>(); - private String target; + private final Map properties = new HashMap<>(); CreateServiceInstanceRequestBuilder() { } @@ -104,13 +104,16 @@ public class CreateServiceInstanceRequest { return this; } - public CreateServiceInstanceRequestBuilder target(String target) { - this.target = target; + public CreateServiceInstanceRequestBuilder properties(Map properties) { + if (properties == null) { + return this; + } + this.properties.putAll(properties); return this; } public CreateServiceInstanceRequest build() { - return new CreateServiceInstanceRequest(serviceInstanceName, name, plan, parameters, target); + return new CreateServiceInstanceRequest(serviceInstanceName, name, plan, parameters, properties); } } diff --git a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java index e60410f..8b34f8b 100644 --- a/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java +++ b/spring-cloud-app-broker-deployer/src/main/java/org/springframework/cloud/appbroker/deployer/DeleteServiceInstanceRequest.java @@ -16,12 +16,16 @@ package org.springframework.cloud.appbroker.deployer; +import java.util.Map; + public class DeleteServiceInstanceRequest { private final String name; + private final Map properties; - DeleteServiceInstanceRequest(String name) { + DeleteServiceInstanceRequest(String name, Map properties) { this.name = name; + this.properties = properties; } public static DeleteServiceInstanceRequestBuilder builder() { @@ -32,9 +36,14 @@ public class DeleteServiceInstanceRequest { return name; } + public Map getProperties() { + return properties; + } + public static class DeleteServiceInstanceRequestBuilder { private String name; + private Map properties; DeleteServiceInstanceRequestBuilder() { } @@ -44,8 +53,13 @@ public class DeleteServiceInstanceRequest { return this; } + public DeleteServiceInstanceRequestBuilder properties(Map properties) { + this.properties = properties; + return this; + } + public DeleteServiceInstanceRequest build() { - return new DeleteServiceInstanceRequest(name); + return new DeleteServiceInstanceRequest(name, properties); } } diff --git a/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/CreateInstanceWithServicesAndTargetComponentTest.java b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/CreateInstanceWithServicesAndTargetComponentTest.java new file mode 100644 index 0000000..0bd7d76 --- /dev/null +++ b/spring-cloud-app-broker-sample/src/test/java/org.springframework.cloud.appbroker/sample/CreateInstanceWithServicesAndTargetComponentTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016-2018. the original author or authors. + * + * Licensed 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.springframework.cloud.appbroker.sample; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.appbroker.sample.fixtures.CloudControllerStubFixture; +import org.springframework.cloud.appbroker.sample.fixtures.OpenServiceBrokerApiFixture; +import org.springframework.cloud.servicebroker.model.instance.OperationState; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.TestPropertySource; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.springframework.cloud.appbroker.sample.CreateInstanceWithServicesAndTargetComponentTest.APP_NAME; +import static org.springframework.cloud.appbroker.sample.CreateInstanceWithServicesAndTargetComponentTest.SERVICE_1_NAME; +import static org.springframework.cloud.appbroker.sample.CreateInstanceWithServicesAndTargetComponentTest.SERVICE_INSTANCE_1_NAME; + +@TestPropertySource(properties = { + "spring.cloud.appbroker.services[0].service-name=example", + "spring.cloud.appbroker.services[0].plan-name=standard", + "spring.cloud.appbroker.services[0].apps[0].path=classpath:demo.jar", + "spring.cloud.appbroker.services[0].apps[0].name=" + APP_NAME, + "spring.cloud.appbroker.services[0].apps[0].services[0].service-instance-name=" + SERVICE_INSTANCE_1_NAME, + "spring.cloud.appbroker.services[0].services[0].service-instance-name=" + SERVICE_INSTANCE_1_NAME, + "spring.cloud.appbroker.services[0].services[0].name=" + SERVICE_1_NAME, + "spring.cloud.appbroker.services[0].services[0].plan=standard", + "spring.cloud.appbroker.services[0].target.name=SpacePerServiceInstance" +}) +class CreateInstanceWithServicesAndTargetComponentTest extends WiremockComponentTest { + + static final String APP_NAME = "app-new-services-target"; + + static final String SERVICE_INSTANCE_1_NAME = "my-db-service"; + static final String SERVICE_1_NAME = "db-service"; + + @Autowired + private OpenServiceBrokerApiFixture brokerFixture; + + @Autowired + private CloudControllerStubFixture cloudControllerFixture; + + @Test + void pushAppWithServicesInSpace() { + String serviceInstanceId = "instance-id"; + cloudControllerFixture.stubAppDoesNotExistInSpace(APP_NAME, serviceInstanceId); + final String host = APP_NAME + "-" + serviceInstanceId; + cloudControllerFixture.stubPushAppWithHost(APP_NAME, host); + + // given that service instances does not exist + cloudControllerFixture.stubServiceInstanceDoesNotExists(SERVICE_INSTANCE_1_NAME); + + // and the services are available in the marketplace + cloudControllerFixture.stubServiceExists(SERVICE_1_NAME); + + // will create and bind the service instance + cloudControllerFixture.stubCreateServiceInstance(SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubCreateServiceBinding(APP_NAME, SERVICE_INSTANCE_1_NAME); + cloudControllerFixture.stubServiceInstanceExists(SERVICE_INSTANCE_1_NAME); + + // when a service instance is created + given(brokerFixture.serviceInstanceRequest()) + .when() + .put(brokerFixture.createServiceInstanceUrl(), "instance-id") + .then() + .statusCode(HttpStatus.ACCEPTED.value()); + + // when the "last_operation" API is polled + given(brokerFixture.serviceInstanceRequest()) + .when() + .get(brokerFixture.getLastInstanceOperationUrl(), "instance-id") + .then() + .statusCode(HttpStatus.OK.value()) + .body("state", is(equalTo(OperationState.IN_PROGRESS.toString()))); + + String state = brokerFixture.waitForAsyncOperationComplete("instance-id"); + assertThat(state).isEqualTo(OperationState.SUCCEEDED.toString()); + } +} \ No newline at end of file