Adding service creation

This commit is contained in:
Alberto Rios
2018-10-31 16:43:11 +01:00
committed by Alberto Ríos
parent f6b68c800e
commit 64f1072a4b
26 changed files with 1226 additions and 86 deletions

View File

@@ -0,0 +1,60 @@
package org.springframework.cloud.appbroker.acceptance;
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 CreateInstanceWithServicesAcceptanceTest extends CloudFoundryAcceptanceTest {
private static final String BROKER_APP_SERVICES = "broker-app-new-services";
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"
})
void shouldPushAppWithServicesBind() {
// given that a service is available in the marketplace
setupServiceBrokerForService("db-service");
// when a service instance is created
createServiceInstance();
// then a backing application is deployed
Optional<ApplicationSummary> backingApplication = getApplicationSummaryByName(BROKER_APP_SERVICES);
assertThat(backingApplication).isNotEmpty();
// and the service bind to it
Optional<ServiceInstanceSummary> serviceInstance = getServiceInstance(SI_1_NAME);
assertThat(serviceInstance).isNotEmpty();
assertThat(serviceInstance.get().getApplications()).contains(BROKER_APP_SERVICES);
// when the service instance is deleted
deleteServiceInstance();
// service has no applications bound to it
Optional<ServiceInstanceSummary> serviceInstanceAfterDeletion = getServiceInstance(SI_1_NAME);
assertThat(serviceInstanceAfterDeletion).isEmpty();
}
@Override
@AfterEach
void tearDown() {
super.tearDown();
deleteServiceBrokerForService("db-service");
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.appbroker.deployer.AppDeployer;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.deployer.DeployerClient;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialGenerator;
@@ -131,21 +132,39 @@ public class AppBrokerAutoConfiguration {
return new TargetService(targets);
}
@Bean
public BackingServicesProvisionService backingServicesProvisionService(DeployerClient deployerClient) {
return new BackingServicesProvisionService(deployerClient);
}
@Bean
public CreateServiceInstanceWorkflow appDeploymentCreateServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService backingAppDeploymentService,
ParametersTransformationService parametersTransformationService,
CredentialProviderService credentialProviderService,
TargetService targetService) {
return new AppDeploymentCreateServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService, parametersTransformationService, credentialProviderService, targetService);
TargetService targetService,
BackingServicesProvisionService backingServicesProvisionService) {
return new AppDeploymentCreateServiceInstanceWorkflow(
brokeredServices,
backingAppDeploymentService,
parametersTransformationService,
credentialProviderService,
targetService,
backingServicesProvisionService);
}
@Bean
public DeleteServiceInstanceWorkflow appDeploymentDeleteServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService backingAppDeploymentService,
CredentialProviderService credentialProviderService,
TargetService targetService) {
return new AppDeploymentDeleteServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService, credentialProviderService, targetService);
TargetService targetService,
BackingServicesProvisionService backingServicesProvisionService) {
return new AppDeploymentDeleteServiceInstanceWorkflow(
brokeredServices,
backingAppDeploymentService,
credentialProviderService,
targetService,
backingServicesProvisionService);
}
@Bean

View File

@@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BackingApplications;
import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.deployer.DeployerClient;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
@@ -101,6 +102,7 @@ class AppBrokerAutoConfigurationTest {
assertThat(context).hasSingleBean(ParametersTransformationService.class);
assertThat(context).hasSingleBean(CredentialProviderService.class);
assertThat(context).hasSingleBean(TargetService.class);
assertThat(context).hasSingleBean(BackingServicesProvisionService.class);
assertThat(context).hasSingleBean(WorkflowServiceInstanceService.class);
assertThat(context).hasSingleBean(WorkflowServiceInstanceBindingService.class);
assertThat(context).hasSingleBean(AppDeploymentCreateServiceInstanceWorkflow.class);

View File

@@ -0,0 +1,150 @@
/*
* 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.deployer;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class BackingService {
private String serviceInstanceName;
private String name;
private String plan;
private Map<String, String> parameters;
private BackingService() {
}
BackingService(String serviceInstanceName, String name, String plan, Map<String, String> parameters) {
this.serviceInstanceName = serviceInstanceName;
this.name = name;
this.plan = plan;
this.parameters = parameters;
}
BackingService(BackingService backingServiceToCopy) {
this.serviceInstanceName = backingServiceToCopy.serviceInstanceName;
this.name = backingServiceToCopy.name;
this.plan = backingServiceToCopy.plan;
this.parameters = backingServiceToCopy.parameters == null
? new HashMap<>()
: new HashMap<>(backingServiceToCopy.parameters);
}
public String getServiceInstanceName() {
return serviceInstanceName;
}
public void setServiceInstanceName(String serviceInstanceName) {
this.serviceInstanceName = serviceInstanceName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPlan() {
return plan;
}
public void setPlan(String plan) {
this.plan = plan;
}
public Map<String, String> getParameters() {
return parameters;
}
public void setParameters(Map<String, String> parameters) {
this.parameters = parameters;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BackingService that = (BackingService) o;
return Objects.equals(serviceInstanceName, that.serviceInstanceName) &&
Objects.equals(name, that.name) &&
Objects.equals(plan, that.plan) &&
Objects.equals(parameters, that.parameters);
}
@Override
public int hashCode() {
return Objects.hash(serviceInstanceName, name, plan, parameters);
}
@Override
public String toString() {
return "BackingService{" +
"serviceInstanceName='" + serviceInstanceName + '\'' +
", name='" + name + '\'' +
", plan='" + plan + '\'' +
", parameters=" + parameters +
'}';
}
public static BackingServiceBuilder builder() {
return new BackingServiceBuilder();
}
public static final class BackingServiceBuilder {
private String serviceInstanceName;
private String name;
private String plan;
private Map<String, String> parameters = new HashMap<>();
BackingServiceBuilder() {
}
public BackingService build() {
return new BackingService(serviceInstanceName, name, plan, parameters);
}
public BackingServiceBuilder serviceInstanceName(String serviceInstanceName) {
this.serviceInstanceName = serviceInstanceName;
return this;
}
public BackingServiceBuilder name(String name) {
this.name = name;
return this;
}
public BackingServiceBuilder plan(String plan) {
this.plan = plan;
return this;
}
public BackingServiceBuilder parameters(Map<String, String> parameters) {
this.parameters = parameters;
return this;
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.deployer;
import java.util.ArrayList;
import java.util.List;
public class BackingServices extends ArrayList<BackingService> {
private static final long serialVersionUID = 1L;
private BackingServices() {
}
BackingServices(List<BackingService> backingServices) {
super.addAll(backingServices);
}
public BackingServices(BackingServices backingServicesToCopy) {
backingServicesToCopy.forEach(backingServiceToCopy ->
this.add(new BackingService(backingServiceToCopy)));
}
public static BackingServicesBuilder builder() {
return new BackingServicesBuilder();
}
public static class BackingServicesBuilder {
private final List<BackingService> backingServices = new ArrayList<>();
public BackingServicesBuilder backingService(BackingService backingService) {
this.backingServices.add(backingService);
return this;
}
public BackingServices build() {
return new BackingServices(backingServices);
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.deployer;
import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import reactor.util.Logger;
import reactor.util.Loggers;
public class BackingServicesProvisionService {
private final Logger log = Loggers.getLogger(BackingServicesProvisionService.class);
private final DeployerClient deployerClient;
public BackingServicesProvisionService(DeployerClient deployerClient) {
this.deployerClient = deployerClient;
}
public Flux<String> createServiceInstance(List<BackingService> backingServices) {
return Flux.fromIterable(backingServices)
.parallel()
.runOn(Schedulers.parallel())
.flatMap(deployerClient::createServiceInstance)
.doOnRequest(l -> log.info("Creating backing services {}", backingServices))
.doOnEach(d -> log.info("Finished creating backing service {}", d))
.doOnComplete(() -> log.info("Finished creating backing service {}", backingServices))
.doOnError(e -> log.info("Error creating backing services {} with error {}", backingServices, e))
.sequential();
}
public Flux<String> deleteServiceInstance(List<BackingService> backingServices) {
return Flux.fromIterable(backingServices)
.parallel()
.runOn(Schedulers.parallel())
.flatMap(deployerClient::deleteServiceInstance)
.doOnRequest(l -> log.info("Deleting backing services {}", backingServices))
.doOnEach(d -> log.info("Finished deleting backing service {}", d))
.doOnComplete(() -> log.info("Finished deleting backing service {}", backingServices))
.doOnError(e -> log.info("Error deleting backing services {} with error {}", backingServices, e))
.sequential();
}
}

View File

@@ -19,17 +19,20 @@ package org.springframework.cloud.appbroker.deployer;
import java.util.Objects;
public class BrokeredService {
private String serviceName;
private String planName;
private BackingApplications apps;
private BackingServices services;
private BrokeredService() {
}
BrokeredService(String serviceName, String planName, BackingApplications apps) {
BrokeredService(String serviceName, String planName, BackingApplications apps, BackingServices services) {
this.serviceName = serviceName;
this.planName = planName;
this.apps = apps;
this.services = services;
}
public String getServiceName() {
@@ -56,6 +59,14 @@ public class BrokeredService {
this.apps = apps;
}
public BackingServices getServices() {
return services;
}
public void setServices(BackingServices services) {
this.services = services;
}
public static BrokeredServiceBuilder builder() {
return new BrokeredServiceBuilder();
}
@@ -71,12 +82,13 @@ public class BrokeredService {
BrokeredService that = (BrokeredService) o;
return Objects.equals(serviceName, that.serviceName) &&
Objects.equals(planName, that.planName) &&
Objects.equals(apps, that.apps);
Objects.equals(apps, that.apps) &&
Objects.equals(services, that.services);
}
@Override
public final int hashCode() {
return Objects.hash(serviceName, planName, apps);
return Objects.hash(serviceName, planName, apps, services);
}
@Override
@@ -85,13 +97,16 @@ public class BrokeredService {
"serviceName='" + serviceName + '\'' +
", planName='" + planName + '\'' +
", apps=" + apps +
", services=" + services +
'}';
}
public static class BrokeredServiceBuilder {
private String id;
private String planId;
private BackingApplications backingApplications;
private BackingServices backingServices;
public BrokeredServiceBuilder serviceName(String id) {
this.id = id;
@@ -108,8 +123,13 @@ public class BrokeredService {
return this;
}
public BrokeredServiceBuilder services(BackingServices backingServices) {
this.backingServices = backingServices;
return this;
}
public BrokeredService build() {
return new BrokeredService(id, planId, backingApplications);
return new BrokeredService(id, planId, backingApplications, backingServices);
}
}
}

View File

@@ -54,4 +54,23 @@ public class DeployerClient {
.map(UndeployApplicationResponse::getName);
}
Mono<String> createServiceInstance(BackingService backingService) {
return appDeployer.createServiceInstance(CreateServiceInstanceRequest.builder()
.serviceInstanceName(backingService.getServiceInstanceName())
.name(backingService.getName())
.plan(backingService.getPlan())
.parameters(backingService.getParameters())
.build())
.doOnRequest(l -> log.info("Creating backing service {}", backingService.getName()))
.doOnSuccess(d -> log.info("Finished creating backing service {}", backingService.getName()))
.doOnError(e -> log.info("Error creating backing service {} with error {}", backingService.getName(), e))
.map(CreateServiceInstanceResponse::getName);
}
Mono<String> deleteServiceInstance(BackingService backingService) {
return appDeployer.deleteServiceInstance(DeleteServiceInstanceRequest.builder()
.name(backingService.getServiceInstanceName())
.build())
.map(DeleteServiceInstanceResponse::getName);
}
}

View File

@@ -22,6 +22,7 @@ import reactor.util.Logger;
import reactor.util.Loggers;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
@@ -42,33 +43,38 @@ public class AppDeploymentCreateServiceInstanceWorkflow
private final ParametersTransformationService parametersTransformationService;
private final CredentialProviderService credentialProviderService;
private final TargetService targetService;
private final BackingServicesProvisionService backingServicesProvisionService;
public AppDeploymentCreateServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService deploymentService,
ParametersTransformationService parametersTransformationService,
CredentialProviderService credentialProviderService,
TargetService targetService) {
TargetService targetService,
BackingServicesProvisionService backingServicesProvisionService) {
super(brokeredServices);
this.deploymentService = deploymentService;
this.parametersTransformationService = parametersTransformationService;
this.credentialProviderService = credentialProviderService;
this.targetService = targetService;
this.backingServicesProvisionService = backingServicesProvisionService;
}
@Override
public Flux<Void> create(CreateServiceInstanceRequest request) {
return getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId())
.flatMap(backingApps -> targetService.add(backingApps, request.getServiceInstanceId()))
.flatMap(backingApps ->
parametersTransformationService.transformParameters(backingApps, request.getParameters()))
.flatMap(backingApplications ->
credentialProviderService.addCredentials(backingApplications, request.getServiceInstanceId()))
.flatMapMany(deploymentService::deploy)
.doOnRequest(l -> log.info("Deploying applications {}", brokeredServices))
.doOnEach(s -> log.info("Finished deploying {}", s))
.doOnComplete(() -> log.info("Finished deploying applications {}", brokeredServices))
.doOnError(e -> log.info("Error deploying applications {} with error {}", brokeredServices, e))
.flatMap(apps -> Flux.empty());
return
getBackingServicesForService(request.getServiceDefinition(), request.getPlanId())
.flatMapMany(backingServicesProvisionService::createServiceInstance)
.thenMany(
getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId())
.flatMap(backingApps -> targetService.add(backingApps, request.getServiceInstanceId()))
.flatMap(backingApps -> parametersTransformationService.transformParameters(backingApps, request.getParameters()))
.flatMap(backingApplications -> credentialProviderService.addCredentials(backingApplications, request.getServiceInstanceId()))
.flatMapMany(deploymentService::deploy)
.doOnRequest(l -> log.info("Deploying applications {}", brokeredServices))
.doOnEach(s -> log.info("Finished deploying {}", s))
.doOnComplete(() -> log.info("Finished deploying applications {}", brokeredServices))
.doOnError(e -> log.info("Error deploying applications {} with error {}", brokeredServices, e))
.flatMap(apps -> Flux.empty()));
}
@Override

View File

@@ -22,6 +22,7 @@ import reactor.util.Logger;
import reactor.util.Loggers;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
@@ -40,29 +41,35 @@ public class AppDeploymentDeleteServiceInstanceWorkflow
private final BackingAppDeploymentService deploymentService;
private final CredentialProviderService credentialProviderService;
private final TargetService targetService;
private final BackingServicesProvisionService backingServicesProvisionService;
public AppDeploymentDeleteServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService deploymentService,
CredentialProviderService credentialProviderService,
TargetService targetService) {
TargetService targetService,
BackingServicesProvisionService backingServicesProvisionService) {
super(brokeredServices);
this.deploymentService = deploymentService;
this.credentialProviderService = credentialProviderService;
this.targetService = targetService;
this.backingServicesProvisionService = backingServicesProvisionService;
}
@Override
public Flux<Void> delete(DeleteServiceInstanceRequest request) {
return getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId())
.flatMap(backingApplications ->
credentialProviderService.deleteCredentials(backingApplications, request.getServiceInstanceId()))
.flatMap(backingApps -> targetService.add(backingApps, request.getServiceInstanceId()))
.flatMapMany(deploymentService::undeploy)
.doOnRequest(l -> log.info("Undeploying applications {}", brokeredServices))
.doOnEach(s -> log.info("Finished undeploying {}", s))
.doOnComplete(() -> log.info("Finished undeploying applications {}", brokeredServices))
.doOnError(e -> log.info("Error undeploying applications {} with error {}", brokeredServices, e))
.flatMap(apps -> Flux.empty());
return
getBackingServicesForService(request.getServiceDefinition(), request.getPlanId())
.flatMapMany(backingServicesProvisionService::deleteServiceInstance)
.thenMany(
getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId())
.flatMap(backingApplications -> credentialProviderService.deleteCredentials(backingApplications, request.getServiceInstanceId()))
.flatMap(backingApps -> targetService.add(backingApps, request.getServiceInstanceId()))
.flatMapMany(deploymentService::undeploy)
.doOnRequest(l -> log.info("Undeploying applications {}", brokeredServices))
.doOnEach(s -> log.info("Finished undeploying {}", s))
.doOnComplete(() -> log.info("Finished undeploying applications {}", brokeredServices))
.doOnError(e -> log.info("Error undeploying applications {} with error {}", brokeredServices, e))
.flatMap(apps -> Flux.empty()));
}
@Override

View File

@@ -16,14 +16,17 @@
package org.springframework.cloud.appbroker.workflow.instance;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
import org.springframework.cloud.appbroker.deployer.BackingApplications;
import org.springframework.cloud.appbroker.deployer.BackingService;
import org.springframework.cloud.appbroker.deployer.BackingServices;
import org.springframework.cloud.appbroker.deployer.BrokeredService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.servicebroker.model.catalog.ServiceDefinition;
import reactor.core.publisher.Mono;
import java.util.List;
class AppDeploymentInstanceWorkflow {
@@ -44,25 +47,38 @@ class AppDeploymentInstanceWorkflow {
Mono.justOrEmpty(findBackingApplications(serviceDefinition, planId)));
}
Mono<List<BackingService>> getBackingServicesForService(ServiceDefinition serviceDefinition, String planId) {
return Mono.defer(() ->
Mono.justOrEmpty(findBackingServices(serviceDefinition, planId)));
}
private BackingApplications findBackingApplications(ServiceDefinition serviceDefinition,
String planId) {
BrokeredService brokeredService = findBrokeredService(serviceDefinition, planId);
return brokeredService == null ? null : new BackingApplications(brokeredService.getApps());
}
private BackingServices findBackingServices(ServiceDefinition serviceDefinition,
String planId) {
BrokeredService brokeredService = findBrokeredService(serviceDefinition, planId);
return brokeredService == null || brokeredService.getServices() == null
? null
: new BackingServices(brokeredService.getServices());
}
private BrokeredService findBrokeredService(ServiceDefinition serviceDefinition,
String planId) {
String serviceName = serviceDefinition.getName();
String planName = serviceDefinition.getPlans().stream()
.filter(plan -> plan.getId().equals(planId))
.findFirst().get().getName();
.filter(plan -> plan.getId().equals(planId))
.findFirst().get().getName();
return brokeredServices.stream()
.filter(brokeredService ->
brokeredService.getServiceName().equals(serviceName)
&& brokeredService.getPlanName().equals(planName))
.findFirst()
.orElse(null);
.filter(brokeredService ->
brokeredService.getServiceName().equals(serviceName)
&& brokeredService.getPlanName().equals(planName))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,85 @@
package org.springframework.cloud.appbroker.deployer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.Mockito.doReturn;
@ExtendWith(MockitoExtension.class)
class BackingServicesProvisionServiceTest {
@Mock
private DeployerClient deployerClient;
private BackingServicesProvisionService backingServicesProvisionService;
private BackingServices backingServices;
@BeforeEach
void setUp() {
backingServicesProvisionService = new BackingServicesProvisionService(deployerClient);
backingServices = BackingServices.builder()
.backingService(BackingService.builder()
.serviceInstanceName("si1")
.name("service1")
.plan("standard")
.parameters(Collections.singletonMap("key1", "value1"))
.build())
.backingService(BackingService.builder()
.serviceInstanceName("si2")
.name("service2")
.plan("free")
.parameters(Collections.singletonMap("key2", "value2"))
.build())
.build();
}
@Test
@SuppressWarnings("UnassignedFluxMonoInstance")
void shouldCreateServiceInstance() {
doReturn(Mono.just("si1"))
.when(deployerClient).createServiceInstance(backingServices.get(0));
doReturn(Mono.just("si2"))
.when(deployerClient).createServiceInstance(backingServices.get(1));
List<String> expectedValues = new ArrayList<>();
expectedValues.add("si1");
expectedValues.add("si2");
StepVerifier.create(backingServicesProvisionService.createServiceInstance(backingServices))
// deployments are run in parallel, so the order of completion is not predictable
// ensure that both expected signals are sent in any order
.expectNextMatches(expectedValues::remove)
.expectNextMatches(expectedValues::remove)
.verifyComplete();
}
@Test
@SuppressWarnings("UnassignedFluxMonoInstance")
void shouldDeleteServiceInstance() {
doReturn(Mono.just("deleted1"))
.when(deployerClient).deleteServiceInstance(backingServices.get(0));
doReturn(Mono.just("deleted2"))
.when(deployerClient).deleteServiceInstance(backingServices.get(1));
List<String> expectedValues = new ArrayList<>();
expectedValues.add("deleted1");
expectedValues.add("deleted2");
StepVerifier.create(backingServicesProvisionService.deleteServiceInstance(backingServices))
// deployments are run in parallel, so the order of completion is not predictable
// ensure that both expected signals are sent in any order
.expectNextMatches(expectedValues::remove)
.expectNextMatches(expectedValues::remove)
.verifyComplete();
}
}

View File

@@ -31,6 +31,9 @@ import reactor.test.StepVerifier;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
import org.springframework.cloud.appbroker.deployer.BackingApplications;
import org.springframework.cloud.appbroker.deployer.BackingService;
import org.springframework.cloud.appbroker.deployer.BackingServices;
import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.BrokeredService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
@@ -62,7 +65,11 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
@Mock
private TargetService targetService;
@Mock
private BackingServicesProvisionService backingServicesProvisionService;
private BackingApplications backingApps;
private BackingServices backingServices;
private CreateServiceInstanceWorkflow createServiceInstanceWorkflow;
@@ -82,6 +89,16 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
.build())
.build();
backingServices = BackingServices
.builder()
.backingService(BackingService
.builder()
.name("my-service")
.plan("a-plan")
.serviceInstanceName("my-service-instance")
.build())
.build();
BrokeredServices brokeredServices = BrokeredServices
.builder()
.service(BrokeredService
@@ -89,6 +106,7 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
.services(backingServices)
.build())
.build();
@@ -97,7 +115,8 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
backingAppDeploymentService,
parametersTransformationService,
credentialProviderService,
targetService);
targetService,
backingServicesProvisionService);
}
@Test
@@ -105,14 +124,7 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
void createServiceInstanceSucceeds() {
CreateServiceInstanceRequest request = buildRequest("service1", "plan1");
given(this.backingAppDeploymentService.deploy(eq(backingApps)))
.willReturn(Flux.just("app1", "app2"));
given(this.parametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters())))
.willReturn(Mono.just(backingApps));
given(this.credentialProviderService.addCredentials(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
given(this.targetService.add(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
setupMocks(request);
StepVerifier
.create(createServiceInstanceWorkflow.create(request))
@@ -121,6 +133,7 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
.verifyComplete();
verify(backingAppDeploymentService).deploy(backingApps);
verify(backingServicesProvisionService).createServiceInstance(backingServices);
verifyNoMoreInteractionsWithServices();
}
@@ -130,14 +143,7 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
CreateServiceInstanceRequest request = buildRequest("service1", "plan1",
singletonMap("ENV_VAR_1", "value from parameters"));
given(this.backingAppDeploymentService.deploy(eq(backingApps)))
.willReturn(Flux.just("app1", "app2"));
given(this.parametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters())))
.willReturn(Mono.just(backingApps));
given(this.credentialProviderService.addCredentials(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
given(this.targetService.add(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
setupMocks(request);
StepVerifier
.create(createServiceInstanceWorkflow.create(request))
@@ -155,14 +161,7 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
CreateServiceInstanceRequest request = buildRequest("service1", "plan1",
singletonMap("ENV_VAR_1", "value from parameters"));
given(this.backingAppDeploymentService.deploy(eq(backingApps)))
.willReturn(Flux.just("app1", "app2"));
given(this.parametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters())))
.willReturn(Mono.just(backingApps));
given(this.credentialProviderService.addCredentials(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
given(this.targetService.add(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
setupMocks(request);
StepVerifier
.create(createServiceInstanceWorkflow.create(request))
@@ -187,11 +186,25 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
verifyNoMoreInteractionsWithServices();
}
private void setupMocks(CreateServiceInstanceRequest request) {
given(this.backingAppDeploymentService.deploy(eq(backingApps)))
.willReturn(Flux.just("app1", "app2"));
given(this.parametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters())))
.willReturn(Mono.just(backingApps));
given(this.credentialProviderService.addCredentials(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
given(this.targetService.add(eq(backingApps), eq(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
given(this.backingServicesProvisionService.createServiceInstance(eq(backingServices)))
.willReturn(Flux.just("my-service-instance"));
}
private void verifyNoMoreInteractionsWithServices() {
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.parametersTransformationService);
verifyNoMoreInteractions(this.credentialProviderService);
verifyNoMoreInteractions(this.targetService);
verifyNoMoreInteractions(this.backingServicesProvisionService);
}
private CreateServiceInstanceRequest buildRequest(String serviceName, String planName) {

View File

@@ -28,6 +28,9 @@ import reactor.test.StepVerifier;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
import org.springframework.cloud.appbroker.deployer.BackingApplications;
import org.springframework.cloud.appbroker.deployer.BackingService;
import org.springframework.cloud.appbroker.deployer.BackingServices;
import org.springframework.cloud.appbroker.deployer.BackingServicesProvisionService;
import org.springframework.cloud.appbroker.deployer.BrokeredService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
@@ -53,30 +56,49 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
@Mock
private CredentialProviderService credentialProviderService;
private DeleteServiceInstanceWorkflow deleteServiceInstanceWorkflow;
@Mock
private BackingServicesProvisionService backingServicesProvisionService;
private BackingApplications backingApps;
private BackingServices backingServices;
private DeleteServiceInstanceWorkflow deleteServiceInstanceWorkflow;
@BeforeEach
void setUp() {
backingApps = BackingApplications
.builder()
.backingApplication(BackingApplication.builder()
.name("app1")
.path("http://myfiles/app1.jar")
.build())
.backingApplication(BackingApplication.builder()
.name("app2")
.path("http://myfiles/app2.jar")
.build())
.backingApplication(BackingApplication
.builder()
.name("app1")
.path("http://myfiles/app1.jar")
.build())
.backingApplication(BackingApplication
.builder()
.name("app2")
.path("http://myfiles/app2.jar")
.build())
.build();
backingServices = BackingServices
.builder()
.backingService(BackingService
.builder()
.name("my-service")
.plan("a-plan")
.serviceInstanceName("my-service-instance")
.build())
.build();
BrokeredServices brokeredServices = BrokeredServices
.builder()
.service(BrokeredService.builder()
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
.build())
.service(BrokeredService
.builder()
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
.services(backingServices)
.build())
.build();
deleteServiceInstanceWorkflow =
@@ -84,7 +106,8 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
brokeredServices,
backingAppDeploymentService,
credentialProviderService,
targetService);
targetService,
backingServicesProvisionService);
}
@Test
@@ -97,6 +120,8 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
.willReturn(Mono.just(backingApps));
given(this.targetService.add(eq(backingApps), eq("service-instance-id")))
.willReturn(Mono.just(backingApps));
given(this.backingServicesProvisionService.deleteServiceInstance(eq(backingServices)))
.willReturn(Flux.just("my-service-instance"));
StepVerifier
.create(deleteServiceInstanceWorkflow.delete(request))
@@ -104,9 +129,7 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
.expectNext()
.verifyComplete();
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.credentialProviderService);
verifyNoMoreInteractions(this.targetService);
verifyNoMoreInteractionsWithServices();
}
@Test
@@ -115,6 +138,11 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
.create(deleteServiceInstanceWorkflow.delete(buildRequest("unsupported-service", "plan1")))
.verifyComplete();
verifyNoMoreInteractionsWithServices();
}
private void verifyNoMoreInteractionsWithServices() {
verifyNoMoreInteractions(this.backingServicesProvisionService);
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.credentialProviderService);
verifyNoMoreInteractions(this.targetService);

View File

@@ -21,10 +21,12 @@ import java.nio.file.Path;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -45,14 +47,22 @@ import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.applications.Route;
import org.cloudfoundry.operations.organizations.OrganizationDetail;
import org.cloudfoundry.operations.organizations.OrganizationInfoRequest;
import org.cloudfoundry.operations.services.GetServiceInstanceRequest;
import org.cloudfoundry.operations.services.ServiceInstance;
import org.cloudfoundry.operations.services.UnbindServiceInstanceRequest;
import org.cloudfoundry.operations.spaces.GetSpaceRequest;
import org.cloudfoundry.operations.spaces.SpaceDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.appbroker.deployer.AppDeployer;
import org.springframework.cloud.appbroker.deployer.CreateServiceInstanceRequest;
import org.springframework.cloud.appbroker.deployer.CreateServiceInstanceResponse;
import org.springframework.cloud.appbroker.deployer.DeleteServiceInstanceRequest;
import org.springframework.cloud.appbroker.deployer.DeleteServiceInstanceResponse;
import org.springframework.cloud.appbroker.deployer.DeployApplicationRequest;
import org.springframework.cloud.appbroker.deployer.DeployApplicationResponse;
import org.springframework.cloud.appbroker.deployer.DeploymentProperties;
@@ -494,4 +504,54 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware
}
};
}
@Override
public Mono<CreateServiceInstanceResponse> createServiceInstance(CreateServiceInstanceRequest request) {
return this.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()));
}
@Override
public Mono<DeleteServiceInstanceResponse> deleteServiceInstance(DeleteServiceInstanceRequest request) {
final String serviceInstanceName = request.getName();
return this.operations
.services()
.getInstance(GetServiceInstanceRequest.builder().name(serviceInstanceName).build())
.map(ServiceInstance::getApplications)
.flatMap(unbindApplications(serviceInstanceName))
.then(
this.operations
.services()
.deleteInstance(
org.cloudfoundry.operations.services.DeleteServiceInstanceRequest
.builder()
.name(serviceInstanceName)
.build())
.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

@@ -25,18 +25,26 @@ import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.Applications;
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.services.GetServiceInstanceRequest;
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.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.appbroker.deployer.DeploymentProperties;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.cloud.appbroker.deployer.AppDeployer;
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;
@@ -48,6 +56,7 @@ import static org.mockito.Mockito.when;
@SuppressWarnings("UnassignedFluxMonoInstance")
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class CloudFoundryAppDeployerTest {
private static final String APP_NAME = "test-app";
@@ -58,6 +67,9 @@ class CloudFoundryAppDeployerTest {
@Mock
private Applications applications;
@Mock
private Services services;
@Mock
private CloudFoundryOperations cloudFoundryOperations;
@@ -81,6 +93,9 @@ class CloudFoundryAppDeployerTest {
when(resourceLoader.getResource(APP_PATH))
.thenReturn(new FileSystemResource(APP_PATH));
when(cloudFoundryOperations.services())
.thenReturn(services);
appDeployer = new CloudFoundryAppDeployer(deploymentProperties,
cloudFoundryOperations, cloudFoundryClient, targetProperties, resourceLoader);
}
@@ -276,6 +291,46 @@ class CloudFoundryAppDeployerTest {
verify(applications).pushManifest(argThat(matchesManifest(expectedManifest)));
}
@Test
void deleteServiceInstanceShouldUnbindServices() {
when(services.deleteInstance(
org.cloudfoundry.operations.services.DeleteServiceInstanceRequest.builder()
.name("service-instance-name")
.build()))
.thenReturn(Mono.empty());
when(services.getInstance(GetServiceInstanceRequest.builder().name("service-instance-name").build()))
.thenReturn(Mono.just(ServiceInstance.builder()
.id("siid")
.type(ServiceInstanceType.MANAGED)
.name("service-instance-name")
.applications("app1", "app2")
.build()));
when(services.unbind(UnbindServiceInstanceRequest.builder()
.serviceInstanceName("service-instance-name")
.applicationName("app1")
.build()))
.thenReturn(Mono.empty());
when(services.unbind(UnbindServiceInstanceRequest.builder()
.serviceInstanceName("service-instance-name")
.applicationName("app2")
.build()))
.thenReturn(Mono.empty());
DeleteServiceInstanceRequest request =
DeleteServiceInstanceRequest.builder()
.name("service-instance-name")
.build();
StepVerifier.create(
appDeployer.deleteServiceInstance(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

@@ -28,4 +28,12 @@ public interface AppDeployer {
return Mono.empty();
}
default Mono<CreateServiceInstanceResponse> createServiceInstance(CreateServiceInstanceRequest request) {
return Mono.empty();
}
default Mono<DeleteServiceInstanceResponse> deleteServiceInstance(DeleteServiceInstanceRequest request) {
return Mono.empty();
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2002-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.deployer;
import java.util.HashMap;
import java.util.Map;
public class CreateServiceInstanceRequest {
private final String serviceInstanceName;
private final String name;
private final String plan;
private final Map<String, String> parameters;
private final String target;
CreateServiceInstanceRequest(String serviceInstanceName,
String name,
String plan,
Map<String, String> parameters,
String target) {
this.serviceInstanceName = serviceInstanceName;
this.name = name;
this.plan = plan;
this.parameters = parameters;
this.target = target;
}
public static CreateServiceInstanceRequestBuilder builder() {
return new CreateServiceInstanceRequestBuilder();
}
public String getServiceInstanceName() {
return serviceInstanceName;
}
public String getName() {
return name;
}
public String getPlan() {
return plan;
}
public Map<String, String> getParameters() {
return parameters;
}
public String getTarget() {
return target;
}
public static class CreateServiceInstanceRequestBuilder {
private String serviceInstanceName;
private String name;
private String plan;
private final Map<String, String> parameters = new HashMap<>();
private String target;
CreateServiceInstanceRequestBuilder() {
}
public CreateServiceInstanceRequestBuilder serviceInstanceName(String serviceInstanceName) {
this.serviceInstanceName = serviceInstanceName;
return this;
}
public CreateServiceInstanceRequestBuilder name(String name) {
this.name = name;
return this;
}
public CreateServiceInstanceRequestBuilder plan(String plan) {
this.plan = plan;
return this;
}
public CreateServiceInstanceRequestBuilder parameters(String key, String value) {
this.parameters.put(key, value);
return this;
}
public CreateServiceInstanceRequestBuilder parameters(Map<String, String> parameters) {
if (parameters == null) {
return this;
}
this.parameters.putAll(parameters);
return this;
}
public CreateServiceInstanceRequestBuilder target(String target) {
this.target = target;
return this;
}
public CreateServiceInstanceRequest build() {
return new CreateServiceInstanceRequest(serviceInstanceName, name, plan, parameters, target);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-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.deployer;
public class CreateServiceInstanceResponse {
private final String name;
CreateServiceInstanceResponse(String name) {
this.name = name;
}
public static CreateServiceInstanceResponseBuilder builder() {
return new CreateServiceInstanceResponseBuilder();
}
public String getName() {
return name;
}
public static class CreateServiceInstanceResponseBuilder {
private String name;
CreateServiceInstanceResponseBuilder() {
}
public CreateServiceInstanceResponseBuilder name(String name) {
this.name = name;
return this;
}
public CreateServiceInstanceResponse build() {
return new CreateServiceInstanceResponse(name);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-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.deployer;
public class DeleteServiceInstanceRequest {
private final String name;
DeleteServiceInstanceRequest(String name) {
this.name = name;
}
public static DeleteServiceInstanceRequestBuilder builder() {
return new DeleteServiceInstanceRequestBuilder();
}
public String getName() {
return name;
}
public static class DeleteServiceInstanceRequestBuilder {
private String name;
DeleteServiceInstanceRequestBuilder() {
}
public DeleteServiceInstanceRequestBuilder name(String name) {
this.name = name;
return this;
}
public DeleteServiceInstanceRequest build() {
return new DeleteServiceInstanceRequest(name);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-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.deployer;
public class DeleteServiceInstanceResponse {
private final String name;
DeleteServiceInstanceResponse(String name) {
this.name = name;
}
public static DeleteServiceInstanceResponseBuilder builder() {
return new DeleteServiceInstanceResponseBuilder();
}
public String getName() {
return name;
}
public static class DeleteServiceInstanceResponseBuilder {
private String name;
DeleteServiceInstanceResponseBuilder() {
}
public DeleteServiceInstanceResponseBuilder name(String name) {
this.name = name;
return this;
}
public DeleteServiceInstanceResponse build() {
return new DeleteServiceInstanceResponse(name);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.CreateInstanceWithServicesComponentTest.APP_NAME;
import static org.springframework.cloud.appbroker.sample.CreateInstanceWithServicesComponentTest.SERVICE_1_NAME;
import static org.springframework.cloud.appbroker.sample.CreateInstanceWithServicesComponentTest.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"
})
class CreateInstanceWithServicesComponentTest extends WiremockComponentTest {
static final String APP_NAME = "app-with-new-services";
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 pushAppWithServicesWhenServicesExist() {
cloudControllerFixture.stubAppDoesNotExist(APP_NAME);
cloudControllerFixture.stubPushApp(APP_NAME);
// 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());
}
}

View File

@@ -291,6 +291,32 @@ public class CloudControllerStubFixture extends WiremockStubFixture {
replace("@guid", serviceInstanceName + "-GUID")))));
}
public void stubServiceInstanceDoesNotExists(String serviceInstanceName) {
stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/service_instances" +
"?q=name:" + serviceInstanceName +
"&page=1" +
"&return_user_provided_service_instances=true"))
.willReturn(ok()
.withBody(cc("list-space-service_instances-empty"))));
}
public void stubServiceExists(String serviceName) {
stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/services" +
"?q=label:" + serviceName +
"&page=1"))
.willReturn(ok()
.withBody(cc("list-space-services"))));
stubFor(get(urlEqualTo("/v2/service_plans?q=service_guid:SERVICE-ID&page=1"))
.willReturn(ok()
.withBody(cc("list-service-plans"))));
}
public void stubCreateServiceInstance(String serviceInstanceName) {
stubFor(post(urlEqualTo("/v2/service_instances?accepts_incomplete=true"))
.withRequestBody(matchingJsonPath("$.[?(@.name == '" + serviceInstanceName + "')]"))
.willReturn(ok()));
}
public void stubCreateServiceBinding(String appName, String serviceInstanceName) {
String serviceInstanceGuid = serviceInstanceName + "-GUID";
String serviceBindingGuid = appGuid(appName) + "-" + serviceInstanceGuid;

View File

@@ -0,0 +1,43 @@
{
"total_results": 1,
"total_pages": 1,
"prev_url": null,
"next_url": null,
"resources": [
{
"metadata": {
"guid": "SERVICE-PLAN-ID",
"url": "/v2/service_plans/SERVICE-PLAN-ID",
"created_at": "2018-01-26T20:48:20Z",
"updated_at": "2018-06-11T18:08:05Z"
},
"entity": {
"name": "standard",
"free": true,
"description": "basic",
"service_guid": "SERVICE-ID",
"extra": "{}",
"public": true,
"bindable": true,
"active": true,
"service_url": "/v2/services/SERVICE-ID",
"service_instances_url": "/v2/service_plans/SERVICE-PLAN-ID/service_instances",
"schemas": {
"service_instance": {
"create": {
"parameters": {}
},
"update": {
"parameters": {}
}
},
"service_binding": {
"create": {
"parameters": {}
}
}
}
}
}
]
}

View File

@@ -0,0 +1,8 @@
{
"total_results": 0,
"total_pages": 1,
"prev_url": null,
"next_url": null,
"resources": [
]
}

View File

@@ -0,0 +1,34 @@
{
"total_results": 1,
"total_pages": 1,
"prev_url": null,
"next_url": null,
"resources": [
{
"metadata": {
"guid": "SERVICE-ID",
"url": "/v2/services/SERVICE-ID",
"created_at": "2018-01-26T20:48:20Z",
"updated_at": "2018-06-11T18:08:05Z"
},
"entity": {
"label": "db-service",
"provider": null,
"url": null,
"description": "My DB Service",
"long_description": null,
"version": null,
"info_url": null,
"active": 1,
"bindable": 1,
"extra": "{}",
"tags": [
],
"requires": [],
"documentation_url": null,
"plan_updateable": 0,
"service_plans_url": "/v2/services/SERVICE-ID/service_plans"
}
}
]
}