Java tutorial
/* * 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 * * https://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.deployer.spi.cloudfoundry; import java.io.IOException; import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.compress.utils.Sets; import org.cloudfoundry.operations.CloudFoundryOperations; import org.cloudfoundry.operations.applications.ApplicationDetail; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; import org.cloudfoundry.operations.applications.ApplicationManifest; import org.cloudfoundry.operations.applications.Applications; import org.cloudfoundry.operations.applications.DeleteApplicationRequest; import org.cloudfoundry.operations.applications.Docker; import org.cloudfoundry.operations.applications.GetApplicationRequest; import org.cloudfoundry.operations.applications.InstanceDetail; import org.cloudfoundry.operations.applications.PushApplicationManifestRequest; import org.cloudfoundry.operations.applications.Route; import org.cloudfoundry.operations.applications.StartApplicationRequest; import org.cloudfoundry.operations.services.BindServiceInstanceRequest; import org.cloudfoundry.operations.services.Services; import org.cloudfoundry.util.FluentMap; import org.junit.Before; import org.junit.Test; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import reactor.core.publisher.Mono; import org.springframework.cloud.deployer.resource.docker.DockerResource; import org.springframework.cloud.deployer.spi.app.AppDeployer; import org.springframework.cloud.deployer.spi.app.AppStatus; import org.springframework.cloud.deployer.spi.app.DeploymentState; import org.springframework.cloud.deployer.spi.core.AppDefinition; import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.cloud.deployer.spi.app.AppDeployer.COUNT_PROPERTY_KEY; import static org.springframework.cloud.deployer.spi.app.AppDeployer.GROUP_PROPERTY_KEY; import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY; import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.DOMAIN_PROPERTY; import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY; import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.HOST_PROPERTY; import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY; import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY; import static org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY; /** * Unit tests for the {@link CloudFoundryAppDeployer}. * * @author Greg Turnquist * @author Eric Bottard * @author Ilayaperumal Gopinathan * @author Ben Hale * @author David Turanski */ public class CloudFoundryAppDeployerTests { private final CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties(); @Mock(answer = Answers.RETURNS_SMART_NULLS) private AppNameGenerator applicationNameGenerator; @Mock(answer = Answers.RETURNS_SMART_NULLS) private Applications applications; private CloudFoundryAppDeployer deployer; @Mock(answer = Answers.RETURNS_SMART_NULLS) private CloudFoundryOperations operations; @Mock(answer = Answers.RETURNS_SMART_NULLS) private Services services; @Mock(answer = Answers.RETURNS_SMART_NULLS) private RuntimeEnvironmentInfo runtimeEnvironmentInfo; @Before public void setUp() { MockitoAnnotations.initMocks(this); given(this.operations.applications()).willReturn(this.applications); given(this.operations.services()).willReturn(this.services); this.deploymentProperties.setServices(new HashSet<>(Arrays.asList("test-service-1", "test-service-2"))); this.deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator, this.deploymentProperties, this.operations, this.runtimeEnvironmentInfo); } @SuppressWarnings("unchecked") @Test public void deploy() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack(deploymentProperties.getBuildpack()).disk(1024) .environmentVariables(defaultEnvironmentVariables()).instances(1).memory(1024) .name("test-application-id").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.emptyMap()), resource, Collections.EMPTY_MAP)); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test public void deployWithServiceParameters() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); given(this.services.bind(any(BindServiceInstanceRequest.class))).willReturn(Mono.empty()); given(this.applications.start(any(StartApplicationRequest.class))).willReturn(Mono.empty()); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack(deploymentProperties.getBuildpack()).disk(1024) .environmentVariables(defaultEnvironmentVariables()).instances(1).memory(1024) .name("test-application-id").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer .deploy(new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource, Collections.singletonMap(SERVICES_PROPERTY_KEY, "'test-service-3 foo:bar'"))); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test public void deployWithAdditionalProperties() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); Map<String, String> environmentVariables = new HashMap<>(); environmentVariables.put("test-key-1", "test-value-1"); addGuidAndIndex(environmentVariables); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack(deploymentProperties.getBuildpack()).disk(1024) .environmentVariables(environmentVariables).instances(1).memory(1024) .name("test-application-id").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.singletonMap("test-key-1", "test-value-1")), resource, FluentMap.<String, String>builder().entry("test-key-2", "test-value-2") .entry(CloudFoundryDeploymentProperties.USE_SPRING_APPLICATION_JSON_KEY, String.valueOf(false)) .build())); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test public void deployWithAdditionalPropertiesInSpringApplicationJson() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); Map<String, String> environmentVariables = new HashMap<>(); environmentVariables.put("SPRING_APPLICATION_JSON", "{\"test-key-1\":\"test-value-1\"}"); addGuidAndIndex(environmentVariables); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack(deploymentProperties.getBuildpack()).disk(1024) .environmentVariables(environmentVariables).instances(1).memory(1024) .name("test-application-id").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.singletonMap("test-key-1", "test-value-1")), resource, Collections.singletonMap("test-key-2", "test-value-2"))); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test public void deployWithApplicationDeploymentProperties() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack("test-buildpack").disk(0).environmentVariables(defaultEnvironmentVariables()) .healthCheckType(ApplicationHealthCheck.NONE).instances(0).memory(0) .name("test-application-id").noRoute(false).host("test-host").domain("test-domain") .routePath("/test-route-path").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.emptyMap()), resource, FluentMap.<String, String>builder().entry(BUILDPACK_PROPERTY_KEY, "test-buildpack") .entry(AppDeployer.DISK_PROPERTY_KEY, "0").entry(DOMAIN_PROPERTY, "test-domain") .entry(HEALTHCHECK_PROPERTY_KEY, "none").entry(HOST_PROPERTY, "test-host") .entry(COUNT_PROPERTY_KEY, "0").entry(AppDeployer.MEMORY_PROPERTY_KEY, "0") .entry(NO_ROUTE_PROPERTY, "false").entry(ROUTE_PATH_PROPERTY, "/test-route-path").build())); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test public void deployWithInvalidRoutePathProperty() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack("test-buildpack").disk(0).environmentVariables(defaultEnvironmentVariables()) .healthCheckType(ApplicationHealthCheck.NONE).instances(0).memory(0) .name("test-application-id").noRoute(false).host("test-host").domain("test-domain") .routePath("/test-route-path").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); try { String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.emptyMap()), resource, FluentMap.<String, String>builder().entry(BUILDPACK_PROPERTY_KEY, "test-buildpack") .entry(AppDeployer.DISK_PROPERTY_KEY, "0").entry(DOMAIN_PROPERTY, "test-domain") .entry(HEALTHCHECK_PROPERTY_KEY, "none").entry(HOST_PROPERTY, "test-host") .entry(COUNT_PROPERTY_KEY, "0").entry(AppDeployer.MEMORY_PROPERTY_KEY, "0") .entry(NO_ROUTE_PROPERTY, "false").entry(ROUTE_PATH_PROPERTY, "test-route-path") .build())); fail("Illegal Argument exception is expected."); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), equalTo("Cloud Foundry routes must start with \"/\". Route passed = [test-route-path].")); } } @SuppressWarnings("unchecked") @Test public void deployWithCustomDeploymentProperties() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); this.deploymentProperties.setBuildpack("test-buildpack"); this.deploymentProperties.setDisk("0"); this.deploymentProperties.setDomain("test-domain"); this.deploymentProperties.setHealthCheck(ApplicationHealthCheck.NONE); this.deploymentProperties.setHost("test-host"); this.deploymentProperties.setInstances(0); this.deploymentProperties.setMemory("0"); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack("test-buildpack").disk(0).domain("test-domain") .environmentVariables(defaultEnvironmentVariables()) .healthCheckType(ApplicationHealthCheck.NONE).host("test-host").instances(0).memory(0) .name("test-application-id").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.emptyMap()), resource, Collections.emptyMap())); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test public void deployWithMultipleRoutes() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); this.deploymentProperties.setBuildpack("test-buildpack"); this.deploymentProperties.setDisk("0"); this.deploymentProperties.setHealthCheck(ApplicationHealthCheck.NONE); this.deploymentProperties.setRoutes(Sets.newHashSet("route1.test-domain", "route2.test-domain")); this.deploymentProperties.setInstances(0); this.deploymentProperties.setMemory("0"); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder().path(resource.getFile().toPath()) .buildpack("test-buildpack").disk(0) .routes(Sets.newHashSet(Route.builder().route("route1.test-domain").build(), Route.builder().route("route2.test-domain").build())) .environmentVariables(defaultEnvironmentVariables()) .healthCheckType(ApplicationHealthCheck.NONE).instances(0).memory(0) .name("test-application-id").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.emptyMap()), resource, Collections.emptyMap())); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test(expected = IllegalStateException.class) public void deployWithMultipleRoutesAndHostOrDomainMutuallyExclusive() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().id("test-application-id").name("test-application").build())); this.deploymentProperties.setHost("route0"); this.deploymentProperties.setDomain("test-domain"); this.deploymentProperties.setRoutes(Sets.newHashSet("route1.test-domain", "route2.test-domain")); this.deployer.deploy(new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource, Collections.emptyMap())); fail("routes and hosts cannot be set, expect cf java client to throw an exception"); } @SuppressWarnings("unchecked") @Test public void deployWithGroup() throws IOException { Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar"); given(this.applicationNameGenerator.generateAppName("test-group-test-application")) .willReturn("test-group-test-application-id"); givenRequestGetApplication("test-group-test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-group-test-application-id").instances(1) .memoryLimit(0).name("test-group-test-application").requestedState("RUNNING") .runningInstances(0).stack("test-stack").build())); givenRequestPushApplication(PushApplicationManifestRequest.builder().manifest(ApplicationManifest.builder() .path(resource.getFile().toPath()).buildpack(deploymentProperties.getBuildpack()).disk(1024) .environmentVariable("SPRING_CLOUD_APPLICATION_GROUP", "test-group") .environmentVariable("SPRING_APPLICATION_JSON", "{}") .environmentVariable("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}") .environmentVariable("SPRING_CLOUD_APPLICATION_GUID", "${vcap.application.name}:${vcap.application.instance_index}") .instances(1).memory(1024).name("test-group-test-application-id").service("test-service-2") .service("test-service-1").build()).stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer .deploy(new AppDeploymentRequest(new AppDefinition("test-application", Collections.emptyMap()), resource, Collections.singletonMap(GROUP_PROPERTY_KEY, "test-group"))); assertThat(deploymentId, equalTo("test-group-test-application-id")); } @SuppressWarnings("unchecked") @Test public void deployDockerResource() throws IOException { Resource resource = new DockerResource("somecorp/someimage:latest"); given(this.applicationNameGenerator.generateAppName("test-application")).willReturn("test-application-id"); givenRequestGetApplication("test-application-id", Mono.error(new IllegalArgumentException()), Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(0) .stack("test-stack").build())); givenRequestPushApplication(PushApplicationManifestRequest.builder() .manifest(ApplicationManifest.builder() .docker(Docker.builder().image("somecorp/someimage:latest").build()).disk(1024) .environmentVariables(defaultEnvironmentVariables()).instances(1).memory(1024) .name("test-application-id").service("test-service-2").service("test-service-1").build()) .stagingTimeout(this.deploymentProperties.getStagingTimeout()) .startupTimeout(this.deploymentProperties.getStartupTimeout()).build(), Mono.empty()); String deploymentId = this.deployer.deploy(new AppDeploymentRequest( new AppDefinition("test-application", Collections.emptyMap()), resource, Collections.emptyMap())); assertThat(deploymentId, equalTo("test-application-id")); } @SuppressWarnings("unchecked") @Test public void statusOfCrashedApplicationIsFailed() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("CRASHED").index("1").build()).build())); AppStatus status = this.deployer.status("test-application-id"); assertThat(status.getState(), equalTo(DeploymentState.failed)); } @SuppressWarnings("unchecked") @Test public void statusOfDownApplicationIsDeploying() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("DOWN").index("1").build()).build())); AppStatus status = this.deployer.status("test-application-id"); assertThat(status.getState(), equalTo(DeploymentState.deploying)); } @SuppressWarnings("unchecked") @Test public void statusOfFlappingApplicationIsDeployed() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("FLAPPING").index("1").build()).build())); AppStatus status = deployer.status("test-application-id"); assertThat(status.getState(), equalTo(DeploymentState.deployed)); } @SuppressWarnings("unchecked") @Test public void statusOfRunningApplicationIsDeployed() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("RUNNING").index("1").build()).build())); AppStatus status = this.deployer.status("test-application-id"); assertThat(status.getState(), equalTo(DeploymentState.deployed)); assertThat(status.getInstances().get("test-application-0").toString(), equalTo("CloudFoundryAppInstanceStatus[test-application-0 : deployed]")); assertThat(status.getInstances().get("test-application-0").getAttributes(), equalTo(Collections.singletonMap("guid", "test-application:0"))); } @SuppressWarnings("unchecked") @Test public void statusOfStartingApplicationIsDeploying() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("STARTING").index("1").build()).build())); AppStatus status = this.deployer.status("test-application-id"); assertThat(status.getState(), equalTo(DeploymentState.deploying)); } @SuppressWarnings("unchecked") @Test public void statusOfUnknownApplicationIsUnknown() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build()).build())); AppStatus status = this.deployer.status("test-application-id"); assertThat(status.getState(), equalTo(DeploymentState.unknown)); } @SuppressWarnings("unchecked") @Test public void statusWithAbnormalInstanceStateThrowsException() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("ABNORMAL").index("1").build()).build())); try { this.deployer.status("test-application-id").getState(); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage(), containsString("Unsupported CF state")); } } @SuppressWarnings("unchecked") @Test public void statusWithFailingCAPICallRetries() throws Exception { AtomicInteger i = new AtomicInteger(); Mono<ApplicationDetail> m = Mono.create(s -> { if (i.incrementAndGet() == 2) { s.success(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build()).build()); } else { s.error(new RuntimeException("Simulated Server Side error")); } }); givenRequestGetApplication("test-application-id", m); DeploymentState state = this.deployer.status("test-application-id").getState(); assertThat(state, is(DeploymentState.unknown)); } @SuppressWarnings("unchecked") @Test public void statusWithFailingCAPICallRetriesEventualError() throws Exception { AtomicInteger i = new AtomicInteger(); Mono<ApplicationDetail> m = Mono.create(s -> { if (i.incrementAndGet() == 12) { // 12 is more than the number of retries s.success(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build()).build()); } else { s.error(new RuntimeException("Simulated Server Side error")); } }); givenRequestGetApplication("test-application-id", m); this.deployer.deploymentProperties.setStatusTimeout(200); // Will cause wait of 20ms then 40ms,80ms DeploymentState state = this.deployer.status("test-application-id").getState(); assertThat(state, is(DeploymentState.error)); } @SuppressWarnings("unchecked") @Test public void statusWithErrorThrownOnBlocking() throws Exception { AtomicInteger i = new AtomicInteger(); Mono<ApplicationDetail> m = Mono.delay(Duration.ofSeconds(5)).then(Mono.create(s -> { i.incrementAndGet(); s.success(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1).memoryLimit(0) .name("test-application").requestedState("RUNNING").runningInstances(1).stack("test-stack") .instanceDetail(InstanceDetail.builder().state("UNKNOWN").index("1").build()).build()); })); givenRequestGetApplication("test-application-id", m); this.deployer.deploymentProperties.setApiTimeout(1);// Is less than the delay() above DeploymentState state = this.deployer.status("test-application-id").getState(); assertThat(state, is(DeploymentState.error)); assertThat(i.get(), is(0)); } @Test public void undeploy() { givenRequestGetApplication("test-application-id", Mono.just(ApplicationDetail.builder().diskQuota(0).id("test-application-id").instances(1) .memoryLimit(0).name("test-application").requestedState("RUNNING").runningInstances(1) .stack("test-stack") .instanceDetail(InstanceDetail.builder().state("RUNNING").index("1").build()).build())); givenRequestDeleteApplication("test-application-id", Mono.empty()); this.deployer.undeploy("test-application-id"); } private void givenRequestDeleteApplication(String id, Mono<Void> response) { given(this.operations.applications() .delete(DeleteApplicationRequest.builder().deleteRoutes(true).name(id).build())) .willReturn(response); } @SuppressWarnings("unchecked") private void givenRequestGetApplication(String id, Mono<ApplicationDetail> response, Mono<ApplicationDetail>... responses) { given(this.operations.applications().get(GetApplicationRequest.builder().name(id).build())) .willReturn(response, responses); } private void givenRequestPushApplication(PushApplicationManifestRequest request, Mono<Void> response) { given(this.operations.applications().pushManifest(any(PushApplicationManifestRequest.class))) .willReturn(response); } private Map<String, String> defaultEnvironmentVariables() { Map<String, String> environmentVariables = new HashMap<>(); environmentVariables.put("SPRING_APPLICATION_JSON", "{}"); addGuidAndIndex(environmentVariables); return environmentVariables; } private void addGuidAndIndex(Map<String, String> environmentVariables) { environmentVariables.put("SPRING_APPLICATION_INDEX", "${vcap.application.instance_index}"); environmentVariables.put("SPRING_CLOUD_APPLICATION_GUID", "${vcap.application.name}:${vcap.application.instance_index}"); } }