Deploy backing apps to space dedicated service instance

Finishes #93
This commit is contained in:
Alberto Rios
2018-09-18 15:51:21 +02:00
parent 28532056c5
commit b1069f8a67
31 changed files with 979 additions and 169 deletions

View File

@@ -156,6 +156,20 @@ configure(allprojects) {
"${result.skippedTestCount} skipped"
}
}
// print failed tests after the execution
def failedTests = []
afterTest { test, result ->
if (result.resultType == TestResult.ResultType.FAILURE) {
failedTests << test
}
}
afterSuite {
failedTests.each { test -> println "FAILED test: ${test.className} > ${test.name}" }
}
}
pmd {

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.appbroker.acceptance;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
@@ -117,10 +118,18 @@ class CloudFoundryAcceptanceTest {
.blockOptional();
}
Optional<ApplicationSummary> getApplicationSummaryByNameAndSpace(String appName, String space) {
return cloudFoundryService.getApplicationSummaryByName(appName, space).blockOptional();
}
ApplicationEnvironments getApplicationEnvironmentByName(String appName) {
return cloudFoundryService.getApplicationEnvironmentByAppName(appName).block();
}
List<String> getSpaces() {
return cloudFoundryService.getSpaces().block();
}
private Path getSampleBrokerAppPath() {
return Paths.get(acceptanceTestProperties.getSampleBrokerAppPath(), "");
}

View File

@@ -0,0 +1,66 @@
/*
* 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.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.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CreateInstanceWithTargetAcceptanceTest extends CloudFoundryAcceptanceTest {
private static final String BROKER_SAMPLE_APP_CREATE_WITH_TARGET = "app-with-target";
@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_SAMPLE_APP_CREATE_WITH_TARGET,
"spring.cloud.appbroker.services[0].apps[0].path=classpath:demo.jar",
"spring.cloud.appbroker.services[0].apps[0].target.name=SpacePerServiceInstance"
})
void shouldPushAppWithTargetWhenCreateServiceCalled() {
// when a service instance is created
createServiceInstance();
Optional<ServiceInstanceSummary> serviceInstance = getServiceInstance();
assertThat(serviceInstance).isNotEmpty();
// then a backing application is deployed in a space named as the service instance id
String serviceInstanceId = serviceInstance.orElseThrow(RuntimeException::new).getId();
String spaceName = serviceInstanceId;
Optional<ApplicationSummary> backingApplication =
getApplicationSummaryByNameAndSpace(BROKER_SAMPLE_APP_CREATE_WITH_TARGET, spaceName);
assertThat(backingApplication).isNotEmpty();
// and has its route with the service instance id appended to it
ApplicationSummary applicationSummary = backingApplication.orElseThrow(RuntimeException::new);
assertThat(applicationSummary.getUrls()).isNotEmpty();
assertThat(applicationSummary.getUrls().get(0)).startsWith(BROKER_SAMPLE_APP_CREATE_WITH_TARGET + "-" + spaceName);
// when the service instance is deleted
deleteServiceInstance();
// then the space is deleted
List<String> spaces = getSpaces();
assertThat(spaces).doesNotContain(spaceName);
}
}

View File

@@ -24,6 +24,7 @@ import java.util.Map;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
import org.cloudfoundry.operations.applications.ApplicationManifest;
@@ -175,10 +176,10 @@ public class CloudFoundryService {
cloudFoundryOperations
.services()
.updateInstance(UpdateServiceInstanceRequest
.builder()
.serviceInstanceName(serviceInstanceName)
.parameters(parameters)
.build()));
.builder()
.serviceInstanceName(serviceInstanceName)
.parameters(parameters)
.build()));
}
public Mono<ServiceInstanceSummary> getServiceInstance(String serviceInstanceName) {
@@ -191,6 +192,23 @@ public class CloudFoundryService {
cloudFoundryOperations.applications().list().collectList());
}
public Mono<List<String>> getSpaces() {
return this.cloudFoundryOperations.spaces().list().map(SpaceSummary::getName).collectList();
}
public Mono<ApplicationSummary> getApplicationSummaryByName(String appName, String space) {
final String defaultOrg = cloudFoundryProperties.getDefaultOrg();
return loggingFlux(DefaultCloudFoundryOperations.builder()
.cloudFoundryClient(cloudFoundryClient)
.organization(defaultOrg)
.space(space)
.build()
.applications()
.list()
.filter(applicationSummary -> applicationSummary.getName().equals(appName))
).next();
}
public Mono<ApplicationEnvironments> getApplicationEnvironmentByAppName(String appName) {
return loggingMono(
cloudFoundryOperations
@@ -303,4 +321,11 @@ public class CloudFoundryService {
}
}
private <T> Flux<T> loggingFlux(Flux<T> publisher) {
if (LOGGER.isDebugEnabled()) {
return publisher.log();
} else {
return publisher;
}
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.appbroker.autoconfigure;
import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -25,28 +27,29 @@ import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.deployer.DeployerClient;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialGenerator;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderFactory;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.credentials.SimpleCredentialGenerator;
import org.springframework.cloud.appbroker.extensions.credentials.SpringSecurityBasicAuthCredentialProviderFactory;
import org.springframework.cloud.appbroker.extensions.parameters.EnvironmentMappingParametersTransformerFactory;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformerFactory;
import org.springframework.cloud.appbroker.extensions.parameters.PropertyMappingParametersTransformerFactory;
import org.springframework.cloud.appbroker.extensions.targets.SpacePerServiceInstance;
import org.springframework.cloud.appbroker.extensions.targets.TargetFactory;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.DeleteServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.UpdateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.WorkflowServiceInstanceService;
import org.springframework.cloud.appbroker.state.InMemoryServiceInstanceStateRepository;
import org.springframework.cloud.appbroker.state.ServiceInstanceStateRepository;
import org.springframework.cloud.appbroker.workflow.instance.AppDeploymentCreateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.workflow.instance.AppDeploymentDeleteServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.service.DeleteServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.workflow.instance.AppDeploymentUpdateServiceInstanceWorkflow;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@AutoConfigureAfter(AppDeployerAutoConfiguration.class)
@ConditionalOnBean(AppDeployer.class)
@@ -100,35 +103,45 @@ public class AppBrokerAutoConfiguration {
public SpringSecurityBasicAuthCredentialProviderFactory springSecurityBasicAuthCredentialProvider(CredentialGenerator credentialGenerator) {
return new SpringSecurityBasicAuthCredentialProviderFactory(credentialGenerator);
}
@Bean
public CredentialProviderService credentialProviderService(List<CredentialProviderFactory<?>> providers) {
return new CredentialProviderService(providers);
}
@Bean
public SpacePerServiceInstance targetFactory() {
return new SpacePerServiceInstance();
}
@Bean
public TargetService targetService(List<TargetFactory<?>> targets) {
return new TargetService(targets);
}
@Bean
public CreateServiceInstanceWorkflow appDeploymentCreateServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService backingAppDeploymentService,
ParametersTransformationService parametersTransformationService,
CredentialProviderService credentialProviderService) {
return new AppDeploymentCreateServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService,
parametersTransformationService, credentialProviderService);
CredentialProviderService credentialProviderService,
TargetService targetService) {
return new AppDeploymentCreateServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService, parametersTransformationService, credentialProviderService, targetService);
}
@Bean
public DeleteServiceInstanceWorkflow appDeploymentDeleteServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService backingAppDeploymentService,
CredentialProviderService credentialProviderService) {
return new AppDeploymentDeleteServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService,
credentialProviderService);
CredentialProviderService credentialProviderService,
TargetService targetService) {
return new AppDeploymentDeleteServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService, credentialProviderService, targetService);
}
@Bean
public UpdateServiceInstanceWorkflow appDeploymentUpdateServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService backingAppDeploymentService,
ParametersTransformationService parametersTransformationService) {
return new AppDeploymentUpdateServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService,
parametersTransformationService);
public UpdateServiceInstanceWorkflow updateServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService backingAppDeploymentService,
ParametersTransformationService parametersTransformationService,
TargetService targetService) {
return new AppDeploymentUpdateServiceInstanceWorkflow(brokeredServices, backingAppDeploymentService, parametersTransformationService, targetService);
}
@Bean

View File

@@ -34,9 +34,12 @@ public class AppDeployerAutoConfiguration {
@Bean
@ConditionalOnBean(CloudFoundryOperations.class)
public AppDeployer cloudFoundryAppDeployer(CloudFoundryOperations cloudFoundryOperations,
CloudFoundryProperties cloudFoundryProperties,
ResourceLoader resourceLoader) {
CloudFoundryDeploymentProperties cloudFoundryDeploymentProperties = new CloudFoundryDeploymentProperties();
return new CloudFoundryAppDeployer(cloudFoundryDeploymentProperties,
cloudFoundryOperations, resourceLoader);
cloudFoundryDeploymentProperties.setDefaultOrg(cloudFoundryProperties.getDefaultOrg());
cloudFoundryDeploymentProperties.setUsername(cloudFoundryProperties.getUsername());
return new CloudFoundryAppDeployer(cloudFoundryDeploymentProperties, cloudFoundryOperations, resourceLoader);
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.cloud.appbroker.deployer.BackingApplications;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.deployer.DeployerClient;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.service.WorkflowServiceInstanceService;
import org.springframework.cloud.appbroker.workflow.instance.AppDeploymentCreateServiceInstanceWorkflow;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
@@ -95,6 +96,7 @@ class AppBrokerAutoConfigurationTest {
assertThat(context).hasSingleBean(BackingAppDeploymentService.class);
assertThat(context).hasSingleBean(ParametersTransformationService.class);
assertThat(context).hasSingleBean(CredentialProviderService.class);
assertThat(context).hasSingleBean(TargetService.class);
assertThat(context).hasSingleBean(WorkflowServiceInstanceService.class);
assertThat(context).hasSingleBean(AppDeploymentCreateServiceInstanceWorkflow.class);
assertThat(context).hasSingleBean(AppDeploymentDeleteServiceInstanceWorkflow.class);

View File

@@ -26,7 +26,7 @@ import java.util.Objects;
public class BackingApplication {
private static final String VALUE_HIDDEN = "<value hidden>";
private String name;
private String path;
private Map<String, String> properties;
@@ -34,6 +34,7 @@ public class BackingApplication {
private List<String> services;
private List<ParametersTransformerSpec> parametersTransformers;
private List<CredentialProviderSpec> credentialProviders;
private TargetSpec target;
public BackingApplication(BackingApplication backingApplicationToCopy) {
this.name = backingApplicationToCopy.name;
@@ -53,15 +54,19 @@ public class BackingApplication {
this.credentialProviders = backingApplicationToCopy.credentialProviders == null
? new ArrayList<>()
: new ArrayList<>(backingApplicationToCopy.credentialProviders);
this.target = backingApplicationToCopy.target;
}
private BackingApplication() {
}
BackingApplication(String name, String path, Map<String, String> properties,
Map<String, String> environment, List<String> services,
private BackingApplication(String name, String path,
Map<String, String> properties,
Map<String, String> environment,
List<String> services,
List<ParametersTransformerSpec> parametersTransformers,
List<CredentialProviderSpec> credentialProviders) {
List<CredentialProviderSpec> credentialProviders,
TargetSpec target) {
this.name = name;
this.path = path;
this.properties = properties;
@@ -69,6 +74,7 @@ public class BackingApplication {
this.services = services;
this.parametersTransformers = parametersTransformers;
this.credentialProviders = credentialProviders;
this.target = target;
}
public String getName() {
@@ -135,6 +141,14 @@ public class BackingApplication {
this.credentialProviders = credentialProviders;
}
public TargetSpec getTarget() {
return target;
}
public void setTarget(TargetSpec target) {
this.target = target;
}
public static BackingApplicationBuilder builder() {
return new BackingApplicationBuilder();
}
@@ -154,13 +168,14 @@ public class BackingApplication {
Objects.equals(environment, that.environment) &&
Objects.equals(services, that.services) &&
Objects.equals(parametersTransformers, that.parametersTransformers) &&
Objects.equals(credentialProviders, that.credentialProviders);
Objects.equals(credentialProviders, that.credentialProviders) &&
Objects.equals(target, that.target);
}
@Override
public final int hashCode() {
return Objects.hash(name, path, properties, environment, services,
parametersTransformers, credentialProviders);
parametersTransformers, credentialProviders, target);
}
@Override
@@ -173,6 +188,7 @@ public class BackingApplication {
", services=" + services +
", parametersTransformers=" + parametersTransformers +
", credentialProviders=" + credentialProviders +
", target=" + target +
'}';
}
@@ -196,6 +212,7 @@ public class BackingApplication {
private final List<String> services = new ArrayList<>();
private final List<ParametersTransformerSpec> parameterTransformers = new ArrayList<>();
private final List<CredentialProviderSpec> credentialProviders = new ArrayList<>();
private TargetSpec target;
BackingApplicationBuilder() {
}
@@ -245,9 +262,14 @@ public class BackingApplication {
return this;
}
public BackingApplicationBuilder target(TargetSpec targetSpec) {
this.target = targetSpec;
return this;
}
public BackingApplication build() {
return new BackingApplication(name, path, properties, environment, services,
parameterTransformers, credentialProviders);
parameterTransformers, credentialProviders, target);
}
}
}

View File

@@ -46,6 +46,7 @@ public class DeployerClient {
Mono<String> undeploy(BackingApplication backingApplication) {
return appDeployer.undeploy(UndeployApplicationRequest.builder()
.properties(backingApplication.getProperties())
.name(backingApplication.getName())
.build())
.map(UndeployApplicationResponse::getName);

View File

@@ -0,0 +1,59 @@
/*
* 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;
public class TargetSpec {
private String name;
private TargetSpec() {
}
private TargetSpec(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static TargetSpecBuilder builder() {
return new TargetSpecBuilder();
}
public static class TargetSpecBuilder {
private String name;
private TargetSpecBuilder() {
}
public TargetSpecBuilder name(String name) {
this.name = name;
return this;
}
public TargetSpec build() {
return new TargetSpec(name);
}
}
}

View File

@@ -26,8 +26,7 @@ public class ExtensionLocator<T> {
private final Map<String, ExtensionFactory<T, ?>> factoriesByName = new HashMap<>();
public ExtensionLocator(List<? extends ExtensionFactory<T, ?>> factories) {
factories.forEach(parametersTransformer ->
this.factoriesByName.put(parametersTransformer.getName(), parametersTransformer));
factories.forEach(extension -> this.factoriesByName.put(extension.getName(), extension));
}
public T getByName(String name, Map<String, Object> args) {

View File

@@ -0,0 +1,45 @@
/*
* 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.extensions.targets;
import reactor.core.publisher.Mono;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
import org.springframework.cloud.appbroker.deployer.DeploymentProperties;
public class SpacePerServiceInstance extends TargetFactory<SpacePerServiceInstance.Config> {
public SpacePerServiceInstance() {
super(Config.class);
}
@Override
public Target create(Config config) {
return this::apply;
}
private Mono<BackingApplication> apply(BackingApplication backingApplication, String serviceInstanceId) {
backingApplication.getProperties().put(DeploymentProperties.HOST_KEY, backingApplication.getName() + "-" + serviceInstanceId);
backingApplication.getProperties().put(DeploymentProperties.TARGET_KEY, serviceInstanceId);
return Mono.just(backingApplication);
}
static class Config {
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.extensions.targets;
import reactor.core.publisher.Mono;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
public interface Target {
Mono<BackingApplication> apply(BackingApplication backingApplication, String serviceInstanceId);
}

View File

@@ -0,0 +1,37 @@
/*
* 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.extensions.targets;
import org.springframework.cloud.appbroker.extensions.AbstractExtensionFactory;
public abstract class TargetFactory<C> extends AbstractExtensionFactory<Target, C> {
TargetFactory() {
super();
}
TargetFactory(Class<C> configClass) {
super(configClass);
}
@Override
public abstract Target create(C config);
public String getName() {
return getShortName(TargetFactory.class);
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.extensions.targets;
import java.util.Collections;
import java.util.List;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
import org.springframework.cloud.appbroker.deployer.TargetSpec;
import org.springframework.cloud.appbroker.extensions.ExtensionLocator;
public class TargetService {
private final ExtensionLocator<Target> locator;
public TargetService(List<TargetFactory<?>> factories) {
locator = new ExtensionLocator<>(factories);
}
public Mono<List<BackingApplication>> add(List<BackingApplication> backingApplications,
String serviceInstanceId) {
return Flux.fromIterable(backingApplications)
.flatMap(backingApplication -> {
TargetSpec targetSpec = backingApplication.getTarget();
if (targetSpec != null) {
Target target = locator.getByName(targetSpec.getName(), Collections.emptyMap());
return target.apply(backingApplication, serviceInstanceId);
}
return Mono.just(backingApplication);
})
.collectList();
}
}

View File

@@ -16,43 +16,49 @@
package org.springframework.cloud.appbroker.workflow.instance;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceResponse.CreateServiceInstanceResponseBuilder;
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Flux;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceRequest;
import reactor.core.publisher.Mono;
import reactor.util.Logger;
import reactor.util.Loggers;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceResponse.CreateServiceInstanceResponseBuilder;
import org.springframework.core.annotation.Order;
@Order(0)
public class AppDeploymentCreateServiceInstanceWorkflow
extends AppDeploymentInstanceWorkflow
implements CreateServiceInstanceWorkflow {
private final Logger log = Loggers.getLogger(AppDeploymentCreateServiceInstanceWorkflow.class);
private final BackingAppDeploymentService deploymentService;
private final ParametersTransformationService parametersTransformationService;
private final CredentialProviderService credentialProviderService;
private final TargetService targetService;
public AppDeploymentCreateServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService deploymentService,
ParametersTransformationService parametersTransformationService,
CredentialProviderService credentialProviderService) {
CredentialProviderService credentialProviderService,
TargetService targetService) {
super(brokeredServices);
this.deploymentService = deploymentService;
this.parametersTransformationService = parametersTransformationService;
this.credentialProviderService = credentialProviderService;
this.targetService = targetService;
}
@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 ->

View File

@@ -16,34 +16,39 @@
package org.springframework.cloud.appbroker.workflow.instance;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.Logger;
import reactor.util.Loggers;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.service.DeleteServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.instance.DeleteServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.instance.DeleteServiceInstanceResponse.DeleteServiceInstanceResponseBuilder;
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Flux;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import reactor.core.publisher.Mono;
import reactor.util.Logger;
import reactor.util.Loggers;
@Order(0)
public class AppDeploymentDeleteServiceInstanceWorkflow
extends AppDeploymentInstanceWorkflow
implements DeleteServiceInstanceWorkflow {
private final Logger log = Loggers.getLogger(AppDeploymentDeleteServiceInstanceWorkflow.class);
private final BackingAppDeploymentService deploymentService;
private final CredentialProviderService credentialProviderService;
private final TargetService targetService;
public AppDeploymentDeleteServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService deploymentService,
CredentialProviderService credentialProviderService) {
CredentialProviderService credentialProviderService,
TargetService targetService) {
super(brokeredServices);
this.deploymentService = deploymentService;
this.credentialProviderService = credentialProviderService;
this.targetService = targetService;
}
@Override
@@ -51,6 +56,7 @@ public class AppDeploymentDeleteServiceInstanceWorkflow
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))

View File

@@ -16,9 +16,6 @@
package org.springframework.cloud.appbroker.workflow.instance;
import org.springframework.cloud.appbroker.service.UpdateServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInstanceResponse.UpdateServiceInstanceResponseBuilder;
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.Logger;
@@ -27,27 +24,36 @@ import reactor.util.Loggers;
import org.springframework.cloud.appbroker.deployer.BackingAppDeploymentService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.service.UpdateServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInstanceRequest;
import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInstanceResponse.UpdateServiceInstanceResponseBuilder;
import org.springframework.core.annotation.Order;
@Order(0)
public class AppDeploymentUpdateServiceInstanceWorkflow
extends AppDeploymentInstanceWorkflow
implements UpdateServiceInstanceWorkflow {
private final Logger log = Loggers.getLogger(AppDeploymentUpdateServiceInstanceWorkflow.class);
private final BackingAppDeploymentService deploymentService;
private final ParametersTransformationService parametersTransformationService;
private final TargetService targetService;
public AppDeploymentUpdateServiceInstanceWorkflow(BrokeredServices brokeredServices,
BackingAppDeploymentService deploymentService,
ParametersTransformationService parametersTransformationService) {
ParametersTransformationService parametersTransformationService,
TargetService targetService) {
super(brokeredServices);
this.deploymentService = deploymentService;
this.parametersTransformationService = parametersTransformationService;
this.targetService = targetService;
}
public Flux<Void> update(UpdateServiceInstanceRequest request) {
return getBackingApplicationsForService(request.getServiceDefinition(), request.getPlanId())
.flatMap(backingApps -> targetService.add(backingApps, request.getServiceInstanceId()))
.flatMap(backingApps ->
parametersTransformationService.transformParameters(backingApps, request.getParameters()))
.flatMapMany(deploymentService::deploy)

View File

@@ -0,0 +1,74 @@
/*
* 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.extensions.targets;
import java.util.List;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.appbroker.deployer.BackingApplication;
import org.springframework.cloud.appbroker.deployer.TargetSpec;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
class TargetServiceTest {
private TargetService targetService;
@BeforeEach
void setUp() {
targetService = new TargetService(singletonList(new SpacePerServiceInstance()));
}
@Test
void shouldAddProperties() {
// given an app with a target
TargetSpec spacePerServiceInstanceFactory = TargetSpec.builder().name("SpacePerServiceInstance").build();
BackingApplication backingApplication = BackingApplication.builder().name("app-name").target(spacePerServiceInstanceFactory).build();
//when add gets called
List<BackingApplication> updatedBackingApplications = targetService.add(singletonList(backingApplication), "service-id").block();
//then a host and space are added
BackingApplication updatedBackingApplication = updatedBackingApplications.get(0);
assertThat(updatedBackingApplication.getProperties().get("host")).isEqualTo("app-name-service-id");
assertThat(updatedBackingApplication.getProperties().get("target")).isEqualTo("service-id");
}
@Test
void shouldAddPropertiesToAllBackingApps() {
// given an app with a target
TargetSpec spacePerServiceInstanceFactory = TargetSpec.builder().name("SpacePerServiceInstance").build();
BackingApplication backingApplication1 = BackingApplication.builder().name("app-name1").target(spacePerServiceInstanceFactory).build();
BackingApplication backingApplication2 = BackingApplication.builder().name("app-name2").target(spacePerServiceInstanceFactory).build();
//when add gets called
List<BackingApplication> updatedBackingApplications = targetService.add(Lists.list(backingApplication1, backingApplication2), "service-id").block();
//then a host and space are added
BackingApplication updatedBackingApplication1 = updatedBackingApplications.get(0);
assertThat(updatedBackingApplication1.getProperties().get("host")).isEqualTo("app-name1-service-id");
assertThat(updatedBackingApplication1.getProperties().get("target")).isEqualTo("service-id");
BackingApplication updatedBackingApplication2 = updatedBackingApplications.get(1);
assertThat(updatedBackingApplication2.getProperties().get("host")).isEqualTo("app-name2-service-id");
assertThat(updatedBackingApplication2.getProperties().get("target")).isEqualTo("service-id");
}
}

View File

@@ -16,33 +16,35 @@
package org.springframework.cloud.appbroker.workflow.instance;
import java.util.HashMap;
import java.util.Map;
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 org.springframework.cloud.appbroker.deployer.BrokeredService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.catalog.Plan;
import org.springframework.cloud.servicebroker.model.catalog.ServiceDefinition;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
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 reactor.test.StepVerifier;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.appbroker.deployer.BrokeredService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.service.CreateServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.catalog.Plan;
import org.springframework.cloud.servicebroker.model.catalog.ServiceDefinition;
import org.springframework.cloud.servicebroker.model.instance.CreateServiceInstanceRequest;
import static java.util.Collections.singletonMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@ExtendWith(MockitoExtension.class)
@@ -57,25 +59,33 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
@Mock
private CredentialProviderService credentialProviderService;
@Mock
private TargetService targetService;
private BackingApplications backingApps;
private CreateServiceInstanceWorkflow createServiceInstanceWorkflow;
@BeforeEach
void setUp() {
backingApps = BackingApplications.builder()
.backingApplication(BackingApplication.builder()
backingApps = BackingApplications
.builder()
.backingApplication(BackingApplication
.builder()
.name("app1")
.path("http://myfiles/app1.jar")
.build())
.backingApplication(BackingApplication.builder()
.backingApplication(BackingApplication
.builder()
.name("app2")
.path("http://myfiles/app2.jar")
.build())
.build();
BrokeredServices brokeredServices = BrokeredServices.builder()
.service(BrokeredService.builder()
BrokeredServices brokeredServices = BrokeredServices
.builder()
.service(BrokeredService
.builder()
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
@@ -86,7 +96,8 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
brokeredServices,
backingAppDeploymentService,
parametersTransformationService,
credentialProviderService);
credentialProviderService,
targetService);
}
@Test
@@ -100,6 +111,8 @@ 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(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
StepVerifier
.create(createServiceInstanceWorkflow.create(request))
@@ -107,9 +120,9 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
.expectNext()
.verifyComplete();
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.parametersTransformationService);
verifyNoMoreInteractions(this.credentialProviderService);
verify(backingAppDeploymentService).deploy(backingApps);
verifyNoMoreInteractionsWithServices();
}
@Test
@@ -123,6 +136,8 @@ 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(request.getServiceInstanceId())))
.willReturn(Mono.just(backingApps));
StepVerifier
.create(createServiceInstanceWorkflow.create(request))
@@ -130,9 +145,35 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
.expectNext()
.verifyComplete();
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.parametersTransformationService);
verifyNoMoreInteractions(this.credentialProviderService);
verify(parametersTransformationService).transformParameters(backingApps, singletonMap("ENV_VAR_1", "value from parameters"));
verifyNoMoreInteractionsWithServices();
}
@Test
void createServiceInstanceWithTargetSucceeds() {
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));
StepVerifier
.create(createServiceInstanceWorkflow.create(request))
.expectNext()
.expectNext()
.verifyComplete();
final String expectedServiceId = "service-instance-id";
verify(targetService).add(backingApps, expectedServiceId);
verifyNoMoreInteractionsWithServices();
}
@Test
@@ -143,9 +184,14 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
.create(createServiceInstanceWorkflow.create(request))
.verifyComplete();
verifyNoMoreInteractionsWithServices();
}
private void verifyNoMoreInteractionsWithServices() {
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.parametersTransformationService);
verifyNoMoreInteractions(this.credentialProviderService);
verifyNoMoreInteractions(this.targetService);
}
private CreateServiceInstanceRequest buildRequest(String serviceName, String planName) {
@@ -154,17 +200,19 @@ class AppDeploymentCreateServiceInstanceWorkflowTest {
private CreateServiceInstanceRequest buildRequest(String serviceName, String planName,
Map<String, Object> parameters) {
return CreateServiceInstanceRequest.builder()
return CreateServiceInstanceRequest
.builder()
.serviceInstanceId("service-instance-id")
.serviceDefinitionId(serviceName + "-id")
.planId(planName + "-id")
.serviceInstanceId(serviceName + "-" + planName + "-instance-id")
.serviceDefinition(ServiceDefinition.builder()
.serviceDefinition(ServiceDefinition
.builder()
.id(serviceName + "-id")
.name(serviceName)
.plans(Plan.builder()
.id(planName + "-id")
.name(planName)
.build())
.id(planName + "-id")
.name(planName)
.build())
.build())
.parameters(parameters == null ? new HashMap<>() : parameters)
.build();

View File

@@ -21,19 +21,21 @@ 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.Flux;
import reactor.core.publisher.Mono;
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.BrokeredService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.credentials.CredentialProviderService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.appbroker.service.DeleteServiceInstanceWorkflow;
import org.springframework.cloud.servicebroker.model.catalog.Plan;
import org.springframework.cloud.servicebroker.model.catalog.ServiceDefinition;
import org.springframework.cloud.servicebroker.model.instance.DeleteServiceInstanceRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
@@ -45,6 +47,9 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
@Mock
private BackingAppDeploymentService backingAppDeploymentService;
@Mock
private TargetService targetService;
@Mock
private CredentialProviderService credentialProviderService;
@@ -53,27 +58,33 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
@BeforeEach
void setUp() {
backingApps = BackingApplications.builder()
backingApps = BackingApplications
.builder()
.backingApplication(BackingApplication.builder()
.name("app1")
.path("http://myfiles/app1.jar")
.build())
.name("app1")
.path("http://myfiles/app1.jar")
.build())
.backingApplication(BackingApplication.builder()
.name("app2")
.path("http://myfiles/app2.jar")
.build())
.name("app2")
.path("http://myfiles/app2.jar")
.build())
.build();
BrokeredServices brokeredServices = BrokeredServices.builder()
BrokeredServices brokeredServices = BrokeredServices
.builder()
.service(BrokeredService.builder()
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
.build())
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
.build())
.build();
deleteServiceInstanceWorkflow = new AppDeploymentDeleteServiceInstanceWorkflow(brokeredServices,
backingAppDeploymentService, credentialProviderService);
deleteServiceInstanceWorkflow =
new AppDeploymentDeleteServiceInstanceWorkflow(
brokeredServices,
backingAppDeploymentService,
credentialProviderService,
targetService);
}
@Test
@@ -84,6 +95,8 @@ 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("service-instance-id")))
.willReturn(Mono.just(backingApps));
StepVerifier
.create(deleteServiceInstanceWorkflow.delete(request))
@@ -93,6 +106,7 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.credentialProviderService);
verifyNoMoreInteractions(this.targetService);
}
@Test
@@ -103,21 +117,23 @@ class AppDeploymentDeleteServiceInstanceWorkflowTest {
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.credentialProviderService);
verifyNoMoreInteractions(this.targetService);
}
private DeleteServiceInstanceRequest buildRequest(String serviceName, String planName) {
return DeleteServiceInstanceRequest.builder()
return DeleteServiceInstanceRequest
.builder()
.serviceDefinitionId(serviceName + "-id")
.serviceInstanceId("service-instance-id")
.planId(planName + "-id")
.serviceInstanceId(serviceName + "-" + planName + "-instance-id")
.serviceDefinition(ServiceDefinition.builder()
.id(serviceName + "-id")
.name(serviceName)
.plans(Plan.builder()
.id(planName + "-id")
.name(planName)
.build())
.build())
.id(serviceName + "-id")
.name(serviceName)
.plans(Plan.builder()
.id(planName + "-id")
.name(planName)
.build())
.build())
.build();
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.cloud.appbroker.deployer.BackingApplications;
import org.springframework.cloud.appbroker.deployer.BrokeredService;
import org.springframework.cloud.appbroker.deployer.BrokeredServices;
import org.springframework.cloud.appbroker.extensions.parameters.ParametersTransformationService;
import org.springframework.cloud.appbroker.extensions.targets.TargetService;
import org.springframework.cloud.servicebroker.model.catalog.Plan;
import org.springframework.cloud.servicebroker.model.catalog.ServiceDefinition;
import org.springframework.cloud.servicebroker.model.instance.UpdateServiceInstanceRequest;
@@ -52,34 +53,40 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest {
@Mock
private ParametersTransformationService parametersTransformationService;
@Mock
private TargetService targetService;
private BackingApplications backingApps;
private AppDeploymentUpdateServiceInstanceWorkflow updateServiceInstanceWorkflow;
@BeforeEach
void setUp() {
backingApps = BackingApplications.builder()
backingApps = BackingApplications
.builder()
.backingApplication(BackingApplication.builder()
.name("app1")
.path("http://myfiles/app1.jar")
.build())
.name("app1")
.path("http://myfiles/app1.jar")
.build())
.backingApplication(BackingApplication.builder()
.name("app2")
.path("http://myfiles/app2.jar")
.build())
.name("app2")
.path("http://myfiles/app2.jar")
.build())
.build();
BrokeredServices brokeredServices = BrokeredServices.builder()
BrokeredServices brokeredServices = BrokeredServices
.builder()
.service(BrokeredService.builder()
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
.build())
.serviceName("service1")
.planName("plan1")
.apps(backingApps)
.build())
.build();
updateServiceInstanceWorkflow = new AppDeploymentUpdateServiceInstanceWorkflow(brokeredServices,
backingAppDeploymentService,
parametersTransformationService
);
parametersTransformationService,
targetService)
;
}
@Test
@@ -89,6 +96,8 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest {
given(this.backingAppDeploymentService.deploy(eq(backingApps)))
.willReturn(Flux.just("app1", "app2"));
given(this.targetService.add(eq(backingApps), eq("service-instance-id")))
.willReturn(Mono.just(backingApps));
given(this.parametersTransformationService.transformParameters(eq(backingApps), eq(request.getParameters())))
.willReturn(Mono.just(backingApps));
@@ -100,6 +109,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest {
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.parametersTransformationService);
verifyNoMoreInteractions(this.targetService);
}
@Test
@@ -111,6 +121,8 @@ 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("service-instance-id")))
.willReturn(Mono.just(backingApps));
StepVerifier
.create(updateServiceInstanceWorkflow.update(request))
@@ -120,6 +132,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest {
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.parametersTransformationService);
verifyNoMoreInteractions(this.targetService);
}
@Test
@@ -130,6 +143,7 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest {
verifyNoMoreInteractions(this.backingAppDeploymentService);
verifyNoMoreInteractions(this.parametersTransformationService);
verifyNoMoreInteractions(this.targetService);
}
private UpdateServiceInstanceRequest buildRequest(String serviceName, String planName) {
@@ -138,18 +152,19 @@ class AppDeploymentUpdateServiceInstanceWorkflowTest {
private UpdateServiceInstanceRequest buildRequest(String serviceName, String planName,
Map<String, Object> parameters) {
return UpdateServiceInstanceRequest.builder()
return UpdateServiceInstanceRequest
.builder()
.serviceInstanceId("service-instance-id")
.serviceDefinitionId(serviceName + "-id")
.planId(planName + "-id")
.serviceInstanceId(serviceName + "-" + planName + "-instance-id")
.serviceDefinition(ServiceDefinition.builder()
.id(serviceName + "-id")
.name(serviceName)
.plans(Plan.builder()
.id(planName + "-id")
.name(planName)
.build())
.build())
.id(serviceName + "-id")
.name(serviceName)
.plans(Plan.builder()
.id(planName + "-id")
.name(planName)
.build())
.build())
.parameters(parameters == null ? new HashMap<>() : parameters)
.build();
}

View File

@@ -34,30 +34,37 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.cloudfoundry.AbstractCloudFoundryException;
import org.cloudfoundry.UnknownCloudFoundryException;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.DefaultApplications;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.operations.applications.Docker;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
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.spaces.CreateSpaceRequest;
import org.cloudfoundry.operations.spaces.DefaultSpaces;
import org.cloudfoundry.operations.spaces.DeleteSpaceRequest;
import org.cloudfoundry.operations.spaces.GetSpaceRequest;
import org.cloudfoundry.operations.spaces.SpaceDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.appbroker.deployer.DeploymentProperties;
import org.springframework.cloud.appbroker.deployer.util.ByteSizeUtils;
import org.springframework.http.HttpStatus;
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;
import org.springframework.cloud.appbroker.deployer.AppDeployer;
import org.springframework.cloud.appbroker.deployer.DeployApplicationRequest;
import org.springframework.cloud.appbroker.deployer.DeployApplicationResponse;
import org.springframework.cloud.appbroker.deployer.DeploymentProperties;
import org.springframework.cloud.appbroker.deployer.UndeployApplicationRequest;
import org.springframework.cloud.appbroker.deployer.UndeployApplicationResponse;
import org.springframework.cloud.appbroker.deployer.util.ByteSizeUtils;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
@SuppressWarnings("PMD.GodClass")
@@ -118,15 +125,22 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware
logger.debug("Pushing manifest" + manifest.toString());
return requestPushApplication(
PushApplicationManifestRequest.builder()
.manifest(manifest)
.stagingTimeout(this.defaultDeploymentProperties.getStagingTimeout())
.startupTimeout(this.defaultDeploymentProperties.getStartupTimeout())
.build())
.doOnSuccess(v -> logger.info("Done uploading bits for {}", request.getName()))
.doOnError(e -> logger.error(String.format("Error creating app %s. Exception Message %s",
request.getName(), e.getMessage())));
PushApplicationManifestRequest applicationManifestRequest =
PushApplicationManifestRequest.builder()
.manifest(manifest)
.stagingTimeout(this.defaultDeploymentProperties.getStagingTimeout())
.startupTimeout(this.defaultDeploymentProperties.getStartupTimeout())
.build();
Mono<Void> requestPushApplication = requestPushApplication(applicationManifestRequest);
if (deploymentProperties.containsKey(DeploymentProperties.TARGET_KEY)) {
String space = deploymentProperties.get(DeploymentProperties.TARGET_KEY);
requestPushApplication = requestPushApplicationInSpace(applicationManifestRequest, space);
}
return requestPushApplication
.doOnSuccess(v -> logger.info("Done uploading bits for {}", request.getName()))
.doOnError(e -> logger.error(String.format("Error creating app %s. Exception Message %s", request.getName(), e.getMessage())));
}
private ApplicationManifest buildAppManifest(DeployApplicationRequest request,
@@ -173,28 +187,49 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware
.pushManifest(request);
}
private Mono<Void> requestPushApplicationInSpace(PushApplicationManifestRequest request, String space) {
return createSpaceOperations()
.create(CreateSpaceRequest.builder().name(space).build())
.then(createCloudFoundryOperationsForSpace(space).applications().pushManifest(request));
}
private DefaultCloudFoundryOperations createCloudFoundryOperationsForSpace(String space) {
return DefaultCloudFoundryOperations
.builder()
.from((DefaultCloudFoundryOperations) this.operations)
.space(space).build();
}
private Mono<String> getOrganizationIdPublisher() {
OrganizationInfoRequest organizationInfoRequest =
OrganizationInfoRequest.builder().name(this.defaultDeploymentProperties.getDefaultOrg()).build();
return this.operations.organizations().get(organizationInfoRequest).map(OrganizationDetail::getId);
}
@Override
public Mono<UndeployApplicationResponse> undeploy(UndeployApplicationRequest request) {
String appName = request.getName();
logger.trace("Undeploying application: request={}", request);
return requestGetApplication(appName)
.timeout(Duration.ofSeconds(this.defaultDeploymentProperties.getApiTimeout()))
.then(requestDeleteApplication(appName)
Map<String, String> deploymentProperties = request.getProperties();
Mono<Void> requestDeleteApplication;
if (deploymentProperties.containsKey(DeploymentProperties.TARGET_KEY)) {
String space = deploymentProperties.get(DeploymentProperties.TARGET_KEY);
requestDeleteApplication = requestDeleteApplicationInSpace(appName, space)
.then(createSpaceOperations().delete(DeleteSpaceRequest.builder().name(space).build()));
} else {
requestDeleteApplication = requestDeleteApplication(appName);
}
return
requestDeleteApplication
.timeout(Duration.ofSeconds(this.defaultDeploymentProperties.getApiTimeout()))
.doOnSuccess(v -> logger.info("Successfully undeployed app {}", appName))
.doOnError(logError(String.format("Failed to undeploy app %s", appName)))
.then(Mono.just(UndeployApplicationResponse.builder()
.name(appName)
.build())));
}
private Mono<ApplicationDetail> requestGetApplication(String name) {
return this.operations.applications()
.get(GetApplicationRequest.builder()
.name(name)
.build());
.build()));
}
private Mono<Void> requestDeleteApplication(String name) {
@@ -205,6 +240,28 @@ public class CloudFoundryAppDeployer implements AppDeployer, ResourceLoaderAware
.build());
}
private Mono<Void> requestDeleteApplicationInSpace(String name, String space) {
return createSpaceApplications(space)
.delete(DeleteApplicationRequest.builder()
.deleteRoutes(defaultDeploymentProperties.isDeleteRoutes())
.name(name)
.build());
}
private DefaultApplications createSpaceApplications(String space) {
return new DefaultApplications(
((DefaultCloudFoundryOperations) this.operations).getCloudFoundryClientPublisher(),
((DefaultCloudFoundryOperations) this.operations).getDopplerClientPublisher(),
(this.operations).spaces().get(GetSpaceRequest.builder().name(space).build()).map(SpaceDetail::getId));
}
private DefaultSpaces createSpaceOperations() {
return new DefaultSpaces(
((DefaultCloudFoundryOperations) this.operations).getCloudFoundryClientPublisher() ,
getOrganizationIdPublisher(),
Mono.just(this.defaultDeploymentProperties.getUsername()));
}
private Map<String, String> getEnvironmentVariables(Map<String, String> environment) {
Map<String, String> envVariables = new HashMap<>(getApplicationEnvironment(environment));

View File

@@ -151,6 +151,10 @@ public class CloudFoundryDeploymentProperties {
private String javaOpts;
private String defaultOrg;
private String username;
public String getBuildpack() {
return buildpack;
}
@@ -302,4 +306,20 @@ public class CloudFoundryDeploymentProperties {
public void setJavaOpts(String javaOpts) {
this.javaOpts = javaOpts;
}
public String getDefaultOrg() {
return defaultOrg;
}
public void setDefaultOrg(String defaultOrg) {
this.defaultOrg = defaultOrg;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@@ -83,4 +83,15 @@ public class DeploymentProperties {
* round up. Exactly how this property affects the deployments will vary between implementations.
*/
public static final String CPU_PROPERTY_KEY = "cpu";
/**
* The deployment property for the host that will be used in the app.
*/
public static final String HOST_KEY = "host";
/**
* The deployment property for the location where the app will be deployed.
* The location will vary between implementations.
*/
public static final String TARGET_KEY = "target";
}

View File

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

View File

@@ -0,0 +1,78 @@
/*
* 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.CreateInstanceWithTargetComponentTest.APP_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].target.name=SpacePerServiceInstance"
})
class CreateInstanceWithTargetComponentTest extends WiremockComponentTest {
static final String APP_NAME = "app-with-target";
@Autowired
private OpenServiceBrokerApiFixture brokerFixture;
@Autowired
private CloudControllerStubFixture cloudControllerFixture;
@Test
void shouldPushAppWithTargetWhenCreateServiceCalled() {
String serviceInstanceId = "instance-id";
cloudControllerFixture.stubAppDoesNotExistInSpace(APP_NAME, serviceInstanceId);
final String host = APP_NAME + "-" + serviceInstanceId;
cloudControllerFixture.stubPushAppWithHost(APP_NAME, host);
// 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

@@ -18,6 +18,7 @@ package org.springframework.cloud.appbroker.sample.fixtures;
import com.github.tomakehurst.wiremock.client.MappingBuilder;
import com.github.tomakehurst.wiremock.matching.ContentPattern;
import org.springframework.boot.test.context.TestComponent;
import static com.github.tomakehurst.wiremock.client.WireMock.created;
@@ -34,8 +35,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
@TestComponent
public class CloudControllerStubFixture extends WiremockStubFixture {
private static final String TEST_SPACE_GUID = "TEST-SPACE-GUID";
private static final String TEST_ORG_GUID = "TEST-ORG-GUID";
private static final String TEST_QUOTA_DEFINITION_GUID = "TEST-QUOTA-DEFINITION-GUID";
public void stubCommonCloudControllerRequests() {
stubGetPlatformInfo();
@@ -91,9 +94,36 @@ public class CloudControllerStubFixture extends WiremockStubFixture {
.withMetadata(optionalStubMapping())
.willReturn(ok()
.withBody(cc("empty-query-results"))));
stubFor(get(urlEqualTo("/v2/organizations/" + TEST_ORG_GUID + "/spaces?page=1"))
.willReturn(ok()
.withBody(cc("list-spaces",
replace("@org-guid", TEST_ORG_GUID),
replace("@space-guid", TEST_SPACE_GUID)))));
stubFor(get(urlEqualTo("/v2/organizations/" + TEST_ORG_GUID + "/space_quota_definitions?page=1"))
.willReturn(ok()
.withBody(cc("list-organizations-quota",
replace("@org-guid", TEST_ORG_GUID)))));
stubFor(get(urlEqualTo("/v2/quota_definitions/" + TEST_QUOTA_DEFINITION_GUID))
.willReturn(ok()
.withBody(cc("get-organizations-quota",
replace("@org-guid", TEST_ORG_GUID)))));
}
public void stubAppDoesNotExist(final String appName) {
stubAppDoesNotExistInSpace(appName, TEST_SPACE_GUID);
}
public void stubAppDoesNotExistInSpace(final String appName, final String space) {
stubFor(get(urlEqualTo("/v2/spaces/" + space + "/apps?q=name:" + appName + "&page=1"))
.willReturn(ok()
.withBody(cc("empty-query-results"))));
stubFor(post(urlPathEqualTo("/v2/spaces"))
.willReturn(ok()));
stubFor(get(urlEqualTo("/v2/spaces/" + TEST_SPACE_GUID + "/apps?q=name:" + appName + "&page=1"))
.willReturn(ok()
.withBody(cc("empty-query-results"))));
@@ -132,7 +162,16 @@ public class CloudControllerStubFixture extends WiremockStubFixture {
public void stubPushApp(final String appName, ContentPattern<?>... appMetadataPatterns) {
stubCreateAppMetadata(appName, appMetadataPatterns);
stubMapRouteToApp(appName);
stubAppAfterCreation(appName, appName);
}
public void stubPushAppWithHost(final String appName, final String host, ContentPattern<?>... appMetadataPatterns) {
stubCreateAppMetadata(appName, appMetadataPatterns);
stubAppAfterCreation(appName, host);
}
private void stubAppAfterCreation(String appName, String host) {
stubMapRouteToApp(appName, host);
stubUploadAppBits(appName);
stubInitializeAppState(appName);
stubCheckAppState(appName);
@@ -140,10 +179,7 @@ public class CloudControllerStubFixture extends WiremockStubFixture {
public void stubUpdateApp(final String appName, ContentPattern<?>... appMetadataPatterns) {
stubUpdateAppMetadata(appName, appMetadataPatterns);
stubMapRouteToApp(appName);
stubUploadAppBits(appName);
stubInitializeAppState(appName);
stubCheckAppState(appName);
stubAppAfterCreation(appName, appName);
}
private void stubCreateAppMetadata(String appName, ContentPattern<?>... appMetadataPatterns) {
@@ -172,17 +208,17 @@ public class CloudControllerStubFixture extends WiremockStubFixture {
replace("@guid", appGuid(appName))))));
}
private void stubMapRouteToApp(String appName) {
private void stubMapRouteToApp(String appName, String host) {
stubFor(get(urlPathEqualTo("/v2/apps/" + appGuid(appName) + "/routes"))
.willReturn(ok()
.withBody(cc("empty-query-results"))));
stubFor(get(urlEqualTo("/v2/routes?q=domain_guid:" + TEST_ORG_GUID + "&q=host:" + appName + "&page=1"))
stubFor(get(urlEqualTo("/v2/routes?q=domain_guid:" + TEST_ORG_GUID + "&q=host:" + host + "&page=1"))
.willReturn(ok()
.withBody(cc("empty-query-results"))));
stubFor(post(urlEqualTo("/v2/routes"))
.withRequestBody(matchingJsonPath("$.[?(@.host == '" + appName + "')]"))
.withRequestBody(matchingJsonPath("$.[?(@.host == '" + host + "')]"))
.willReturn(created()
.withBody(cc("list-routes",
replace("@name", appName),

View File

@@ -0,0 +1,20 @@
{
"metadata": {
"guid": "@org-guid",
"url": "/v2/organizations/@org-guid",
"created_at": "2018-07-19T20:33:45Z",
"updated_at": "2018-07-19T20:33:45Z"
},
"entity": {
"name": "default",
"non_basic_services_allowed": true,
"total_services": 100,
"total_routes": 1000,
"total_private_domains": -1,
"memory_limit": 10240,
"trial_db_allowed": false,
"instance_memory_limit": -1,
"app_instance_limit": -1,
"app_task_limit": -1
}
}

View File

@@ -0,0 +1,29 @@
{
"total_results": 1,
"total_pages": 1,
"prev_url": null,
"next_url": null,
"resources": [
{
"metadata": {
"guid": "@org-guid",
"url": "/v2/organizations/@org-guid",
"created_at": "2018-07-19T20:33:45Z",
"updated_at": "2018-07-19T20:33:45Z"
},
"entity": {
"organization_guid": "@org-guid",
"name": "default",
"non_basic_services_allowed": true,
"total_services": 100,
"total_routes": 1000,
"total_private_domains": -1,
"memory_limit": 10240,
"trial_db_allowed": false,
"instance_memory_limit": -1,
"app_instance_limit": -1,
"app_task_limit": -1
}
}
]
}

View File

@@ -14,10 +14,10 @@
"entity": {
"name": "test",
"billing_enabled": false,
"quota_definition_guid": "7885d305-f247-4d9c-b070-9c65536f09f3",
"quota_definition_guid": "TEST-QUOTA-DEFINITION-GUID",
"status": "active",
"default_isolation_segment_guid": null,
"quota_definition_url": "/v2/quota_definitions/7885d305-f247-4d9c-b070-9c65536f09f3",
"quota_definition_url": "/v2/quota_definitions/TEST-QUOTA-DEFINITION-GUID",
"spaces_url": "/v2/organizations/@org-guid/spaces",
"domains_url": "/v2/organizations/@org-guid/domains",
"private_domains_url": "/v2/organizations/@org-guid/private_domains",