Deploying backing service instances to a space

Finishes #94
This commit is contained in:
Alberto Rios
2018-11-07 07:56:02 +01:00
committed by Scott Frederick
parent 0f703d2ec6
commit 6d4e291083
20 changed files with 377 additions and 87 deletions

View File

@@ -146,6 +146,10 @@ class CloudFoundryAcceptanceTest {
return getServiceInstanceMono(serviceInstanceName).blockOptional();
}
Optional<ServiceInstanceSummary> getServiceInstance(String serviceInstanceName, String space) {
return getServiceInstanceMono(serviceInstanceName, space).blockOptional();
}
Mono<ServiceInstanceSummary> getServiceInstanceMono() {
return getServiceInstanceMono(SERVICE_INSTANCE_NAME);
}
@@ -154,6 +158,10 @@ class CloudFoundryAcceptanceTest {
return cloudFoundryService.getServiceInstance(serviceInstanceName);
}
private Mono<ServiceInstanceSummary> getServiceInstanceMono(String serviceInstanceName, String space) {
return cloudFoundryService.getServiceInstance(serviceInstanceName, space);
}
Optional<ApplicationSummary> getApplicationSummaryByName(String appName) {
return cloudFoundryService
.getApplications()

View File

@@ -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<ServiceInstanceSummary> 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<ApplicationSummary> backingApplication =
getApplicationSummaryByNameAndSpace(BROKER_APP_SERVICES, space);
assertThat(backingApplication).isNotEmpty();
// and the backing service bind to it
Optional<ServiceInstanceSummary> 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<String> spaces = getSpaces();
assertThat(spaces).doesNotContain(space);
}
@Override
@AfterEach
void tearDown() {
super.tearDown();
deleteServiceBrokerForService("db-service");
}
}

View File

@@ -183,6 +183,15 @@ public class CloudFoundryService {
getServiceInstanceFromList(serviceInstanceName));
}
public Mono<ServiceInstanceSummary> getServiceInstance(String serviceInstanceName, String space) {
return loggingMono(
createOperationsForSpace(space)
.services()
.listInstances()
.filter(si -> si.getName().equals(serviceInstanceName))
.next());
}
public Mono<List<ApplicationSummary>> getApplications() {
return loggingMono(
cloudFoundryOperations.applications().list().collectList());
@@ -193,8 +202,7 @@ public class CloudFoundryService {
}
public Mono<ApplicationSummary> 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<ApplicationEnvironments> 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)

View File

@@ -26,15 +26,17 @@ public class BackingService {
private String name;
private String plan;
private Map<String, String> parameters;
private Map<String, String> properties;
private BackingService() {
}
BackingService(String serviceInstanceName, String name, String plan, Map<String, String> parameters) {
BackingService(String serviceInstanceName, String name, String plan, Map<String, String> parameters, Map<String, String> 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<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> 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<String, String> parameters = new HashMap<>();
private Map<String, String> 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<String, String> properties) {
this.properties = properties;
return this;
}
}
}

View File

@@ -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<String> deleteServiceInstance(BackingService backingService) {
return appDeployer.deleteServiceInstance(DeleteServiceInstanceRequest.builder()
.name(backingService.getServiceInstanceName())
.properties(backingService.getProperties())
.build())
.map(DeleteServiceInstanceResponse::getName);
}

View File

@@ -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<SpacePerServiceInstance.Config> {
@@ -32,13 +31,11 @@ public class SpacePerServiceInstance extends TargetFactory<SpacePerServiceInstan
return this::apply;
}
private Mono<BackingApplication> apply(BackingApplication backingApplication, String serviceInstanceId) {
backingApplication.addProperty(DeploymentProperties.HOST_PROPERTY_KEY,
backingApplication.getName() + "-" + serviceInstanceId);
backingApplication.addProperty(DeploymentProperties.TARGET_PROPERTY_KEY,
serviceInstanceId);
private Map<String, String> apply(Map<String, String> 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 {

View File

@@ -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<BackingApplication> apply(BackingApplication backingApplication, String serviceInstanceId);
Map<String, String> apply(Map<String, String> properties, String name, String serviceInstanceId);
}

View File

@@ -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<List<BackingApplication>> add(List<BackingApplication> backingApplications,
TargetSpec targetSpec,
String serviceInstanceId) {
public Mono<List<BackingApplication>> addToBackingApplications(List<BackingApplication> 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<String, String> 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<List<BackingService>> addToBackingServices(List<BackingService> backingServices,
TargetSpec targetSpec,
String serviceInstanceId) {
return Flux.fromIterable(backingServices)
.flatMap(backingService -> {
if (targetSpec != null) {
Target target = locator.getByName(targetSpec.getName(), Collections.emptyMap());
Map<String, String> properties = target.apply(backingService.getProperties(), backingService.getName(), serviceInstanceId);
backingService.setProperties(properties);
}
return Mono.just(backingService);
})
.collectList();
}
}

View File

@@ -63,10 +63,11 @@ public class AppDeploymentCreateServiceInstanceWorkflow
public Flux<Void> 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)

View File

@@ -59,11 +59,12 @@ public class AppDeploymentDeleteServiceInstanceWorkflow
public Flux<Void> 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))

View File

@@ -53,7 +53,7 @@ public class AppDeploymentUpdateServiceInstanceWorkflow
public Flux<Void> 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)

View File

@@ -44,7 +44,7 @@ class TargetServiceTest {
BackingApplication backingApplication = BackingApplication.builder().name("app-name").build();
//when add gets called
List<BackingApplication> updatedBackingApplications = targetService.add(singletonList(backingApplication), targetSpec, "service-id").block();
List<BackingApplication> 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<BackingApplication> updatedBackingApplications = targetService.add(Lists.list(backingApplication1, backingApplication2), targetSpec, "service-id").block();
List<BackingApplication> updatedBackingApplications = targetService.addToBackingApplications(Lists.list(backingApplication1, backingApplication2), targetSpec, "service-id").block();
//then a host and space are added
BackingApplication updatedBackingApplication1 = updatedBackingApplications.get(0);

View File

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

View File

@@ -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"));

View File

@@ -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

View File

@@ -507,29 +507,63 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware
@Override
public Mono<CreateServiceInstanceResponse> 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<CreateServiceInstanceResponse> 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<DeleteServiceInstanceResponse> 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<List<String>, 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<List<String>, Mono<?>> unbindApplications(String serviceInstanceName) {
return applications ->
Flux.fromIterable(applications)
.flatMap(
application ->
this.operations
.services()
.unbind(
UnbindServiceInstanceRequest
.builder()
.applicationName(application)
.serviceInstanceName(serviceInstanceName)
.build())
).collectList();
}
}

View File

@@ -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}")

View File

@@ -25,18 +25,18 @@ public class CreateServiceInstanceRequest {
private final String name;
private final String plan;
private final Map<String, String> parameters;
private final String target;
private final Map<String, String> properties;
CreateServiceInstanceRequest(String serviceInstanceName,
String name,
String plan,
Map<String, String> parameters,
String target) {
Map<String, String> 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<String, String> getProperties() {
return properties;
}
public static class CreateServiceInstanceRequestBuilder {
@@ -71,7 +71,7 @@ public class CreateServiceInstanceRequest {
private String name;
private String plan;
private final Map<String, String> parameters = new HashMap<>();
private String target;
private final Map<String, String> 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<String, String> 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);
}
}

View File

@@ -16,12 +16,16 @@
package org.springframework.cloud.appbroker.deployer;
import java.util.Map;
public class DeleteServiceInstanceRequest {
private final String name;
private final Map<String, String> properties;
DeleteServiceInstanceRequest(String name) {
DeleteServiceInstanceRequest(String name, Map<String, String> properties) {
this.name = name;
this.properties = properties;
}
public static DeleteServiceInstanceRequestBuilder builder() {
@@ -32,9 +36,14 @@ public class DeleteServiceInstanceRequest {
return name;
}
public Map<String, String> getProperties() {
return properties;
}
public static class DeleteServiceInstanceRequestBuilder {
private String name;
private Map<String, String> properties;
DeleteServiceInstanceRequestBuilder() {
}
@@ -44,8 +53,13 @@ public class DeleteServiceInstanceRequest {
return this;
}
public DeleteServiceInstanceRequestBuilder properties(Map<String, String> properties) {
this.properties = properties;
return this;
}
public DeleteServiceInstanceRequest build() {
return new DeleteServiceInstanceRequest(name);
return new DeleteServiceInstanceRequest(name, properties);
}
}

View File

@@ -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());
}
}