org.cloudfoundry.client.v2.ApplicationsTest.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.client.v2.ApplicationsTest.java

Source

/*
 * Copyright 2013-2017 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.cloudfoundry.client.v2;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.applications.AbstractApplicationResource;
import org.cloudfoundry.client.v2.applications.ApplicationEnvironmentRequest;
import org.cloudfoundry.client.v2.applications.ApplicationInstanceInfo;
import org.cloudfoundry.client.v2.applications.ApplicationInstancesRequest;
import org.cloudfoundry.client.v2.applications.ApplicationInstancesResponse;
import org.cloudfoundry.client.v2.applications.ApplicationStatisticsRequest;
import org.cloudfoundry.client.v2.applications.AssociateApplicationRouteRequest;
import org.cloudfoundry.client.v2.applications.AssociateApplicationRouteResponse;
import org.cloudfoundry.client.v2.applications.CopyApplicationRequest;
import org.cloudfoundry.client.v2.applications.CreateApplicationRequest;
import org.cloudfoundry.client.v2.applications.CreateApplicationResponse;
import org.cloudfoundry.client.v2.applications.DeleteApplicationRequest;
import org.cloudfoundry.client.v2.applications.DownloadApplicationDropletRequest;
import org.cloudfoundry.client.v2.applications.DownloadApplicationRequest;
import org.cloudfoundry.client.v2.applications.GetApplicationRequest;
import org.cloudfoundry.client.v2.applications.ListApplicationRoutesRequest;
import org.cloudfoundry.client.v2.applications.ListApplicationServiceBindingsRequest;
import org.cloudfoundry.client.v2.applications.ListApplicationsRequest;
import org.cloudfoundry.client.v2.applications.RemoveApplicationRouteRequest;
import org.cloudfoundry.client.v2.applications.RemoveApplicationServiceBindingRequest;
import org.cloudfoundry.client.v2.applications.RestageApplicationRequest;
import org.cloudfoundry.client.v2.applications.SummaryApplicationRequest;
import org.cloudfoundry.client.v2.applications.SummaryApplicationResponse;
import org.cloudfoundry.client.v2.applications.TerminateApplicationInstanceRequest;
import org.cloudfoundry.client.v2.applications.UpdateApplicationRequest;
import org.cloudfoundry.client.v2.applications.UpdateApplicationResponse;
import org.cloudfoundry.client.v2.applications.UploadApplicationRequest;
import org.cloudfoundry.client.v2.applications.UploadApplicationResponse;
import org.cloudfoundry.client.v2.domains.CreateDomainRequest;
import org.cloudfoundry.client.v2.routes.CreateRouteRequest;
import org.cloudfoundry.client.v2.routes.CreateRouteResponse;
import org.cloudfoundry.client.v2.routes.RouteResource;
import org.cloudfoundry.client.v2.servicebindings.CreateServiceBindingRequest;
import org.cloudfoundry.client.v2.servicebindings.CreateServiceBindingResponse;
import org.cloudfoundry.client.v2.servicebindings.ServiceBindingEntity;
import org.cloudfoundry.client.v2.servicebindings.ServiceBindingResource;
import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceRequest;
import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceResponse;
import org.cloudfoundry.util.DelayUtils;
import org.cloudfoundry.util.JobUtils;
import org.cloudfoundry.util.OperationUtils;
import org.cloudfoundry.util.PaginationUtils;
import org.cloudfoundry.util.ResourceUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.util.function.Tuple2;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.zip.GZIPInputStream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.cloudfoundry.client.ZipExpectations.zipEquality;
import static org.cloudfoundry.util.OperationUtils.thenKeep;
import static org.cloudfoundry.util.tuple.TupleUtils.consumer;
import static org.cloudfoundry.util.tuple.TupleUtils.function;

public final class ApplicationsTest extends AbstractIntegrationTest {

    @Autowired
    private CloudFoundryClient cloudFoundryClient;

    @Autowired
    private Mono<String> organizationId;

    @Autowired
    private Mono<String> spaceId;

    @Autowired
    private Mono<String> stackId;

    @Test
    public void associateRoute() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String domainName = this.nameFactory.getDomainName();

        Mono.when(this.organizationId, this.spaceId)
                .then(function((organizationId, spaceId) -> Mono.when(
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName),
                        createRouteWithDomain(this.cloudFoundryClient, organizationId, spaceId, domainName,
                                "test-host", "/test/path").map(ResourceUtils::getId))))
                .as(thenKeep(function((applicationId, routeId) -> requestAssociateRoute(this.cloudFoundryClient,
                        applicationId, routeId))))
                .then(function((applicationId, routeId) -> Mono
                        .when(getSingleRouteId(this.cloudFoundryClient, applicationId), Mono.just(routeId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void copy() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String copyApplicationName = this.nameFactory.getApplicationName();

        this.spaceId
                .then(spaceId -> Mono.when(
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName).as(thenKeep(
                                applicationId -> uploadApplication(this.cloudFoundryClient, applicationId))),
                        requestCreateApplication(this.cloudFoundryClient, spaceId, copyApplicationName)
                                .map(ResourceUtils::getId)))
                .as(thenKeep(function((sourceId, targetId) -> this.cloudFoundryClient.applicationsV2()
                        .copy(CopyApplicationRequest.builder().applicationId(targetId).sourceApplicationId(sourceId)
                                .build())
                        .then(job -> JobUtils.waitForCompletion(this.cloudFoundryClient, job)))))
                .then(function(
                        (sourceId, targetId) -> Mono.when(downloadApplication(this.cloudFoundryClient, sourceId),
                                downloadApplication(this.cloudFoundryClient, targetId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void create() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId
                .then(spaceId -> Mono.when(Mono.just(spaceId),
                        requestCreateApplication(this.cloudFoundryClient, spaceId, applicationName)
                                .map(ResourceUtils::getEntity)))
                .as(StepVerifier::create).consumeNextWith(consumer((spaceId, entity) -> {
                    assertThat(entity.getSpaceId()).isEqualTo(spaceId);
                    assertThat(entity.getName()).isEqualTo(applicationName);
                })).expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void delete() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .as(thenKeep(applicationId -> this.cloudFoundryClient.applicationsV2()
                        .delete(DeleteApplicationRequest.builder().applicationId(applicationId).build())))
                .then(applicationId -> requestGetApplication(this.cloudFoundryClient, applicationId))
                .as(StepVerifier::create)
                .consumeErrorWith(t -> assertThat(t).isInstanceOf(ClientV2Exception.class)
                        .hasMessageMatching("CF-AppNotFound\\([0-9]+\\): The app could not be found: .*"))
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void downloadDroplet() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .as(thenKeep(applicationId -> uploadAndStartApplication(this.cloudFoundryClient, applicationId)))
                .flatMap(
                        applicationId -> this.cloudFoundryClient.applicationsV2()
                                .downloadDroplet(DownloadApplicationDropletRequest.builder()
                                        .applicationId(applicationId).build())
                                .as(OperationUtils::collectByteArray))
                .as(StepVerifier::create).consumeNextWith(isTestApplicationDroplet()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void environment() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .then(applicationId -> Mono.when(Mono.just(applicationId), this.cloudFoundryClient.applicationsV2()
                        .environment(ApplicationEnvironmentRequest.builder().applicationId(applicationId).build())
                        .map(response -> getStringApplicationEnvValue(response.getApplicationEnvironmentJsons(),
                                "VCAP_APPLICATION", "application_id"))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void get() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .then(applicationId -> Mono.when(Mono.just(applicationId),
                        requestGetApplication(this.cloudFoundryClient, applicationId)))
                .as(StepVerifier::create).consumeNextWith(applicationIdAndNameEquality(applicationName))
                .expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void list() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId
                .then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId,
                        applicationName))
                .then(applicationId -> Mono.when(Mono.just(applicationId),
                        PaginationUtils
                                .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                        .list(ListApplicationsRequest.builder().page(page).build()))
                                .filter(resource -> ResourceUtils.getId(resource).equals(applicationId)).single()
                                .cast(AbstractApplicationResource.class)))
                .as(StepVerifier::create).consumeNextWith(applicationIdAndNameEquality(applicationName))
                .expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void listFilterByDiego() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .then(applicationId -> Mono
                        .when(Mono.just(applicationId),
                                PaginationUtils
                                        .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                                .list(ListApplicationsRequest.builder().diego(true).page(page)
                                                        .build()))
                                        .filter(resource -> ResourceUtils.getId(resource).equals(applicationId))
                                        .single().cast(AbstractApplicationResource.class)))
                .as(StepVerifier::create).consumeNextWith(applicationIdAndNameEquality(applicationName))
                .expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void listFilterByName() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .then(applicationId -> Mono
                        .when(Mono.just(applicationId),
                                PaginationUtils
                                        .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                                .list(ListApplicationsRequest.builder().name(applicationName)
                                                        .page(page).build()))
                                        .filter(resource -> ResourceUtils.getId(resource).equals(applicationId))
                                        .single().cast(AbstractApplicationResource.class)))
                .as(StepVerifier::create).consumeNextWith(applicationIdAndNameEquality(applicationName))
                .expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void listFilterByOrganizationId() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        Mono.when(this.organizationId,
                this.spaceId.then(
                        spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName)))
                .then(function((organizationId, applicationId) -> Mono.when(Mono.just(applicationId),
                        PaginationUtils
                                .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                        .list(ListApplicationsRequest.builder().organizationId(organizationId)
                                                .page(page).build()))
                                .filter(resource -> ResourceUtils.getId(resource).equals(applicationId)).single()
                                .cast(AbstractApplicationResource.class))))
                .as(StepVerifier::create).consumeNextWith(applicationIdAndNameEquality(applicationName))
                .expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void listFilterBySpaceId() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId
                .then(spaceId -> Mono.when(Mono.just(spaceId),
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName)))
                .then(function(
                        (spaceId, applicationId) -> Mono.when(Mono.just(applicationId),
                                PaginationUtils
                                        .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                                .list(ListApplicationsRequest.builder().spaceId(spaceId).page(page)
                                                        .build()))
                                        .filter(resource -> ResourceUtils.getId(resource).equals(applicationId))
                                        .single().cast(AbstractApplicationResource.class))))
                .as(StepVerifier::create).consumeNextWith(applicationIdAndNameEquality(applicationName))
                .expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void listFilterByStackId() {
        String applicationName = this.nameFactory.getApplicationName();

        Mono.when(this.stackId,
                this.spaceId
                        .then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName)))
                .then(function(
                        (stackId, applicationId) -> Mono.when(Mono.just(applicationId),
                                PaginationUtils
                                        .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                                .list(ListApplicationsRequest.builder().stackId(stackId).page(page)
                                                        .build()))
                                        .filter(resource -> ResourceUtils.getId(resource).equals(applicationId))
                                        .single().cast(AbstractApplicationResource.class))))
                .as(StepVerifier::create).consumeNextWith(applicationIdAndNameEquality(applicationName))
                .expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void listRoutes() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String domainName = this.nameFactory.getDomainName();

        Mono.when(this.organizationId, this.spaceId)
                .then(function((organizationId, spaceId) -> Mono.when(Mono.just(organizationId), Mono.just(spaceId),
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName))))
                .then(function((organizationId, spaceId, applicationId) -> Mono.when(Mono.just(applicationId),
                        createApplicationRoute(this.cloudFoundryClient, organizationId, spaceId, domainName,
                                applicationId))))
                .then(function(
                        (applicationId, routeResponse) -> Mono.when(Mono.just(ResourceUtils.getId(routeResponse)),
                                PaginationUtils
                                        .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                                .listRoutes(ListApplicationRoutesRequest.builder()
                                                        .applicationId(applicationId).page(page).build()))
                                        .single().map(ResourceUtils::getId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void listRoutesFilterByDomainId() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String domainName = this.nameFactory.getDomainName();

        Mono.when(this.organizationId, this.spaceId)
                .then(function((organizationId, spaceId) -> Mono.when(Mono.just(organizationId), Mono.just(spaceId),
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName))))
                .then(function((organizationId, spaceId, applicationId) -> Mono.when(Mono.just(applicationId),
                        createApplicationRoute(this.cloudFoundryClient, organizationId, spaceId, domainName,
                                applicationId))))
                .then(function(
                        (applicationId, routeResponse) -> Mono.when(Mono.just(ResourceUtils.getId(routeResponse)),
                                PaginationUtils
                                        .requestClientV2Resources(page -> this.cloudFoundryClient.applicationsV2()
                                                .listRoutes(ListApplicationRoutesRequest.builder()
                                                        .applicationId(applicationId)
                                                        .domainId(ResourceUtils.getEntity(routeResponse)
                                                                .getDomainId())
                                                        .page(page).build()))
                                        .single().map(ResourceUtils::getId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void listRoutesFilterByHost() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String domainName = this.nameFactory.getDomainName();

        Mono.when(this.organizationId, this.spaceId)
                .then(function((organizationId, spaceId) -> Mono.when(Mono.just(organizationId), Mono.just(spaceId),
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName))))
                .then(function((organizationId, spaceId, applicationId) -> Mono.when(Mono.just(applicationId),
                        createApplicationRoute(this.cloudFoundryClient, organizationId, spaceId, domainName,
                                applicationId))))
                .then(function(
                        (applicationId, routeResponse) -> Mono
                                .when(Mono.just(ResourceUtils.getId(routeResponse)),
                                        PaginationUtils
                                                .requestClientV2Resources(
                                                        page -> this.cloudFoundryClient.applicationsV2()
                                                                .listRoutes(ListApplicationRoutesRequest.builder()
                                                                        .applicationId(applicationId)
                                                                        .host(ResourceUtils.getEntity(routeResponse)
                                                                                .getHost())
                                                                        .page(page).build()))
                                                .single().map(ResourceUtils::getId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void listRoutesFilterByPath() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String domainName = this.nameFactory.getDomainName();

        Mono.when(this.organizationId, this.spaceId)
                .then(function((organizationId, spaceId) -> Mono.when(Mono.just(organizationId), Mono.just(spaceId),
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName))))
                .then(function((organizationId, spaceId, applicationId) -> Mono.when(Mono.just(applicationId),
                        createApplicationRoute(this.cloudFoundryClient, organizationId, spaceId, domainName,
                                applicationId))))
                .then(function(
                        (applicationId, routeResponse) -> Mono
                                .when(Mono.just(ResourceUtils.getId(routeResponse)),
                                        PaginationUtils
                                                .requestClientV2Resources(
                                                        page -> this.cloudFoundryClient.applicationsV2()
                                                                .listRoutes(ListApplicationRoutesRequest.builder()
                                                                        .applicationId(applicationId)
                                                                        .path(ResourceUtils.getEntity(routeResponse)
                                                                                .getPath())
                                                                        .page(page).build()))
                                                .single().map(ResourceUtils::getId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void listRoutesFilterByPort() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String domainName = this.nameFactory.getDomainName();

        Mono.when(this.organizationId, this.spaceId)
                .then(function((organizationId, spaceId) -> Mono.when(Mono.just(organizationId), Mono.just(spaceId),
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName))))
                .then(function((organizationId, spaceId, applicationId) -> Mono.when(Mono.just(applicationId),
                        createApplicationRoute(this.cloudFoundryClient, organizationId, spaceId, domainName,
                                applicationId))))
                .then(function(
                        (applicationId, routeResponse) -> Mono.when(Mono.just(ResourceUtils.getId(routeResponse)),
                                PaginationUtils.requestClientV2Resources(page -> {
                                    ListApplicationRoutesRequest.Builder builder = ListApplicationRoutesRequest
                                            .builder().applicationId(applicationId).page(page);

                                    Optional.ofNullable(ResourceUtils.getEntity(routeResponse).getPort())
                                            .ifPresent(builder::port);

                                    return this.cloudFoundryClient.applicationsV2().listRoutes(builder.build());
                                }).single().map(ResourceUtils::getId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void listServiceBindings() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String serviceInstanceName = this.nameFactory.getServiceInstanceName();

        this.spaceId
                .then(spaceId -> Mono.when(createApplicationId(this.cloudFoundryClient, spaceId, applicationName),
                        createUserServiceInstanceId(this.cloudFoundryClient, spaceId, serviceInstanceName)))
                .as(thenKeep(function((applicationId, serviceInstanceId) -> createServiceBindingId(
                        this.cloudFoundryClient, applicationId, serviceInstanceId))))
                .then(function((applicationId, serviceInstanceId) -> Mono.when(Mono.just(serviceInstanceId),
                        getSingleServiceBindingInstanceId(this.cloudFoundryClient, applicationId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void listServiceBindingsFilterByServiceInstanceId() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String serviceInstanceName = this.nameFactory.getServiceInstanceName();

        this.spaceId
                .then(spaceId -> Mono.when(createApplicationId(this.cloudFoundryClient, spaceId, applicationName),
                        createUserServiceInstanceId(this.cloudFoundryClient, spaceId, serviceInstanceName)))
                .then(function((applicationId, serviceInstanceId) -> Mono.when(Mono.just(applicationId),
                        Mono.just(serviceInstanceId),
                        createServiceBindingId(this.cloudFoundryClient, applicationId, serviceInstanceId))))
                .then(function(
                        (applicationId, serviceInstanceId,
                                serviceBindingId) -> Mono
                                        .when(Mono.just(serviceBindingId),
                                                PaginationUtils
                                                        .requestClientV2Resources(page -> this.cloudFoundryClient
                                                                .applicationsV2().listServiceBindings(
                                                                        ListApplicationServiceBindingsRequest
                                                                                .builder()
                                                                                .applicationId(applicationId)
                                                                                .serviceInstanceId(
                                                                                        serviceInstanceId)
                                                                                .page(page).build()))
                                                        .single().map(ResourceUtils::getId))))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void removeRoute() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String domainName = this.nameFactory.getDomainName();

        Mono.when(this.organizationId, this.spaceId)
                .then(function((organizationId, spaceId) -> Mono.when(Mono.just(organizationId), Mono.just(spaceId),
                        createApplicationId(this.cloudFoundryClient, spaceId, applicationName))))
                .then(function((organizationId, spaceId, applicationId) -> Mono.when(Mono.just(applicationId),
                        createApplicationRoute(this.cloudFoundryClient, organizationId, spaceId, domainName,
                                applicationId))))
                .as(thenKeep(function((applicationId, routeResponse) -> this.cloudFoundryClient.applicationsV2()
                        .removeRoute(RemoveApplicationRouteRequest.builder().applicationId(applicationId)
                                .routeId(ResourceUtils.getId(routeResponse)).build()))))
                .flatMap(function(
                        (applicationId, routeResponse) -> requestRoutes(this.cloudFoundryClient, applicationId)))
                .as(StepVerifier::create).expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void removeServiceBinding() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String serviceInstanceName = this.nameFactory.getServiceInstanceName();

        this.spaceId
                .then(spaceId -> Mono.when(createApplicationId(this.cloudFoundryClient, spaceId, applicationName),
                        createUserServiceInstanceId(this.cloudFoundryClient, spaceId, serviceInstanceName)))
                .then(function((applicationId, serviceInstanceId) -> Mono.when(Mono.just(applicationId),
                        createServiceBindingId(this.cloudFoundryClient, applicationId, serviceInstanceId))))
                .as(thenKeep(function((applicationId, serviceBindingId) -> this.cloudFoundryClient.applicationsV2()
                        .removeServiceBinding(RemoveApplicationServiceBindingRequest.builder()
                                .applicationId(applicationId).serviceBindingId(serviceBindingId).build()))))
                .flatMap(function((applicationId,
                        serviceBindingId) -> requestServiceBindings(this.cloudFoundryClient, applicationId)))
                .as(StepVerifier::create).expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void restage() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .as(thenKeep(applicationId -> uploadAndStartApplication(this.cloudFoundryClient, applicationId)))
                .as(thenKeep(applicationId -> this.cloudFoundryClient.applicationsV2()
                        .restage(RestageApplicationRequest.builder().applicationId(applicationId).build())))
                .then(applicationId -> waitForStagingApplication(this.cloudFoundryClient, applicationId))
                .map(resource -> ResourceUtils.getEntity(resource).getName()).as(StepVerifier::create)
                .expectNext(applicationName).expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void statistics() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .as(thenKeep(applicationId -> uploadAndStartApplication(this.cloudFoundryClient, applicationId)))
                .then(applicationId -> this.cloudFoundryClient.applicationsV2()
                        .statistics(ApplicationStatisticsRequest.builder().applicationId(applicationId).build())
                        .map(instanceStatistics -> instanceStatistics.getInstances().get("0").getStatistics()
                                .getName()))
                .as(StepVerifier::create).expectNext(applicationName).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void summary() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .then(applicationId -> this.cloudFoundryClient.applicationsV2()
                        .summary(SummaryApplicationRequest.builder().applicationId(applicationId).build())
                        .map(SummaryApplicationResponse::getId).and(Mono.just(applicationId)))
                .as(StepVerifier::create).consumeNextWith(tupleEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void terminateInstance() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .as(thenKeep(applicationId -> uploadAndStartApplication(this.cloudFoundryClient, applicationId)))
                .then(applicationId -> Mono.when(Mono.just(applicationId),
                        getInstanceInfo(this.cloudFoundryClient, applicationId, "0")
                                .map(info -> Optional.ofNullable(info.getSince()))))
                .as(thenKeep(function((applicationId, optionalSince) -> this.cloudFoundryClient.applicationsV2()
                        .terminateInstance(TerminateApplicationInstanceRequest.builder()
                                .applicationId(applicationId).index("0").build()))))
                .then(function((applicationId, optionalSince) -> waitForInstanceRestart(this.cloudFoundryClient,
                        applicationId, "0", optionalSince)))
                .as(StepVerifier::create).expectNextCount(1).expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void update() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();
        String applicationName2 = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .then(applicationId -> this.cloudFoundryClient.applicationsV2()
                        .update(UpdateApplicationRequest.builder().applicationId(applicationId)
                                .name(applicationName2).environmentJson("test-var", "test-value").build())
                        .map(ResourceUtils::getId))
                .then(applicationId -> Mono.when(Mono.just(applicationId),
                        requestGetApplication(this.cloudFoundryClient, applicationId)
                                .map(ResourceUtils::getEntity)))
                .then(function((applicationId, entity1) -> Mono.when(Mono.just(entity1),
                        this.cloudFoundryClient.applicationsV2()
                                .update(UpdateApplicationRequest.builder().applicationId(applicationId)
                                        .environmentJsons(Collections.emptyMap()).build())
                                .then(requestGetApplication(this.cloudFoundryClient, applicationId)
                                        .map(ResourceUtils::getEntity)))))
                .as(StepVerifier::create).consumeNextWith(consumer((entity1, entity2) -> {
                    assertThat(entity1.getName()).isEqualTo(applicationName2);
                    assertThat(entity1.getEnvironmentJsons()).containsEntry("test-var", "test-value");
                    assertThat(entity2.getEnvironmentJsons()).isEmpty();
                })).expectComplete().verify(Duration.ofMinutes(5));
    }

    @Test
    public void uploadAndDownload() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .as(thenKeep(applicationId -> uploadApplication(this.cloudFoundryClient, applicationId)))
                .then(applicationId -> Mono.when(downloadApplication(this.cloudFoundryClient, applicationId),
                        getBytes("test-application.zip")))
                .as(StepVerifier::create).consumeNextWith(zipEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    @Test
    public void uploadAndDownloadAsyncFalse() throws TimeoutException, InterruptedException {
        String applicationName = this.nameFactory.getApplicationName();

        this.spaceId.then(spaceId -> createApplicationId(this.cloudFoundryClient, spaceId, applicationName))
                .as(thenKeep(applicationId -> uploadApplicationAsyncFalse(this.cloudFoundryClient, applicationId)))
                .then(applicationId -> Mono.when(downloadApplication(this.cloudFoundryClient, applicationId),
                        getBytes("test-application.zip")))
                .as(StepVerifier::create).consumeNextWith(zipEquality()).expectComplete()
                .verify(Duration.ofMinutes(5));
    }

    private static Consumer<Tuple2<String, AbstractApplicationResource>> applicationIdAndNameEquality(String name) {
        Assert.notNull(name, "name must not be null");

        return consumer((applicationId, resource) -> {
            assertThat(ResourceUtils.getId(resource)).isEqualTo(applicationId);
            assertThat(ResourceUtils.getEntity(resource).getName()).isEqualTo(name);
        });
    }

    private static Mono<String> createApplicationId(CloudFoundryClient cloudFoundryClient, String spaceId,
            String applicationName) {
        return requestCreateApplication(cloudFoundryClient, spaceId, applicationName, "staticfile_buildpack", true,
                512, 64).map(ResourceUtils::getId);
    }

    private static Mono<CreateRouteResponse> createApplicationRoute(CloudFoundryClient cloudFoundryClient,
            String organizationId, String spaceId, String domainName, String applicationId) {
        return createRouteWithDomain(cloudFoundryClient, organizationId, spaceId, domainName, "test-host",
                "/test-path")
                        .then(createRouteResponse -> requestAssociateRoute(cloudFoundryClient, applicationId,
                                createRouteResponse.getMetadata().getId()).map(response -> createRouteResponse));
    }

    private static Mono<CreateRouteResponse> createRouteWithDomain(CloudFoundryClient cloudFoundryClient,
            String organizationId, String spaceId, String domainName, String host, String path) {
        return cloudFoundryClient.domains()
                .create(CreateDomainRequest.builder().name(domainName).owningOrganizationId(organizationId)
                        .wildcard(true).build())
                .map(ResourceUtils::getId).then(domainId -> cloudFoundryClient.routes().create(CreateRouteRequest
                        .builder().domainId(domainId).host(host).path(path).spaceId(spaceId).build()));
    }

    private static Mono<String> createServiceBindingId(CloudFoundryClient cloudFoundryClient, String applicationId,
            String serviceInstanceId) {
        return requestCreateServiceBinding(cloudFoundryClient, applicationId, serviceInstanceId)
                .map(ResourceUtils::getId);
    }

    private static Mono<String> createUserServiceInstanceId(CloudFoundryClient cloudFoundryClient, String spaceId,
            String serviceInstanceName) {
        return requestCreateUserServiceInstance(cloudFoundryClient, spaceId, serviceInstanceName)
                .map(ResourceUtils::getId);
    }

    private static Mono<ApplicationInstanceInfo> getInstanceInfo(CloudFoundryClient cloudFoundryClient,
            String applicationId, String instanceName) {
        return requestInstances(cloudFoundryClient, applicationId)
                .filter(response -> response.getInstances().containsKey(instanceName))
                .map(response -> response.getInstances().get(instanceName));
    }

    private static Mono<String> getSingleRouteId(CloudFoundryClient cloudFoundryClient, String applicationId) {
        return requestRoutes(cloudFoundryClient, applicationId).single().map(ResourceUtils::getId);
    }

    private static Mono<String> getSingleServiceBindingInstanceId(CloudFoundryClient cloudFoundryClient,
            String applicationId) {
        return requestServiceBindings(cloudFoundryClient, applicationId).single().map(ResourceUtils::getEntity)
                .map(ServiceBindingEntity::getServiceInstanceId);
    }

    @SuppressWarnings("unchecked")
    private static String getStringApplicationEnvValue(Map<String, Object> environment, String... keys) {
        for (int i = 0; i < keys.length - 1; ++i) {
            environment = (Map<String, Object>) environment.get(keys[i]);
        }
        return (String) environment.get(keys[keys.length - 1]);
    }

    private static boolean isIdentical(Double expected, Double actual) {
        return expected == null ? actual == null : expected.equals(actual);
    }

    private static Consumer<byte[]> isTestApplicationDroplet() {
        return bytes -> {
            Set<String> names = new HashSet<>();

            try (TarArchiveInputStream in = new TarArchiveInputStream(
                    new GZIPInputStream(new ByteArrayInputStream(bytes)))) {
                TarArchiveEntry entry;
                while ((entry = in.getNextTarEntry()) != null) {
                    names.add(entry.getName());
                }
            } catch (IOException e) {
                throw Exceptions.propagate(e);
            }

            assertThat(names).contains("./app/Staticfile", "./app/public/index.html");
        };
    }

    private static Mono<AssociateApplicationRouteResponse> requestAssociateRoute(
            CloudFoundryClient cloudFoundryClient, String applicationId, String routeId) {
        return cloudFoundryClient.applicationsV2().associateRoute(
                AssociateApplicationRouteRequest.builder().applicationId(applicationId).routeId(routeId).build());
    }

    private static Mono<CreateApplicationResponse> requestCreateApplication(CloudFoundryClient cloudFoundryClient,
            String spaceId, String applicationName) {
        return requestCreateApplication(cloudFoundryClient, spaceId, applicationName, null, null, null, null);
    }

    private static Mono<CreateApplicationResponse> requestCreateApplication(CloudFoundryClient cloudFoundryClient,
            String spaceId, String applicationName, String buildpack, Boolean diego, Integer diskQuota,
            Integer memory) {
        return cloudFoundryClient.applicationsV2().create(CreateApplicationRequest.builder().buildpack(buildpack)
                .diego(diego).diskQuota(diskQuota).memory(memory).name(applicationName).spaceId(spaceId).build());
    }

    private static Mono<CreateServiceBindingResponse> requestCreateServiceBinding(
            CloudFoundryClient cloudFoundryClient, String applicationId, String serviceInstanceId) {
        return cloudFoundryClient.serviceBindingsV2().create(CreateServiceBindingRequest.builder()
                .applicationId(applicationId).serviceInstanceId(serviceInstanceId).build());
    }

    private static Mono<CreateUserProvidedServiceInstanceResponse> requestCreateUserServiceInstance(
            CloudFoundryClient cloudFoundryClient, String spaceId, String name) {
        return cloudFoundryClient.userProvidedServiceInstances()
                .create(CreateUserProvidedServiceInstanceRequest.builder().name(name).spaceId(spaceId).build());
    }

    private static Mono<AbstractApplicationResource> requestGetApplication(CloudFoundryClient cloudFoundryClient,
            String applicationId) {
        return cloudFoundryClient.applicationsV2()
                .get(GetApplicationRequest.builder().applicationId(applicationId).build())
                .cast(AbstractApplicationResource.class);
    }

    private static Mono<ApplicationInstancesResponse> requestInstances(CloudFoundryClient cloudFoundryClient,
            String applicationId) {
        return cloudFoundryClient.applicationsV2()
                .instances(ApplicationInstancesRequest.builder().applicationId(applicationId).build());
    }

    private static Flux<RouteResource> requestRoutes(CloudFoundryClient cloudFoundryClient, String applicationId) {
        return PaginationUtils.requestClientV2Resources(page -> cloudFoundryClient.applicationsV2().listRoutes(
                ListApplicationRoutesRequest.builder().page(page).applicationId(applicationId).build()));
    }

    private static Flux<ServiceBindingResource> requestServiceBindings(CloudFoundryClient cloudFoundryClient,
            String applicationId) {
        return PaginationUtils.requestClientV2Resources(page -> cloudFoundryClient.applicationsV2()
                .listServiceBindings(ListApplicationServiceBindingsRequest.builder().applicationId(applicationId)
                        .page(page).build()));
    }

    private static Mono<UpdateApplicationResponse> requestUpdateApplicationState(
            CloudFoundryClient cloudFoundryClient, String applicationId, String state) {
        return cloudFoundryClient.applicationsV2()
                .update(UpdateApplicationRequest.builder().applicationId(applicationId).state(state).build());
    }

    private static Mono<ApplicationInstanceInfo> startApplication(CloudFoundryClient cloudFoundryClient,
            String applicationId) {
        return requestUpdateApplicationState(cloudFoundryClient, applicationId, "STARTED")
                .then(waitForStagingApplication(cloudFoundryClient, applicationId))
                .then(waitForStartingInstanceInfo(cloudFoundryClient, applicationId));
    }

    private static Mono<ApplicationInstanceInfo> uploadAndStartApplication(CloudFoundryClient cloudFoundryClient,
            String applicationId) {
        return uploadApplication(cloudFoundryClient, applicationId)
                .then(startApplication(cloudFoundryClient, applicationId));
    }

    private static Mono<Void> uploadApplication(CloudFoundryClient cloudFoundryClient, String applicationId) {
        try {
            return cloudFoundryClient.applicationsV2()
                    .upload(UploadApplicationRequest.builder()
                            .application(new ClassPathResource("test-application.zip").getFile().toPath())
                            .async(true).applicationId(applicationId).build())
                    .then(job -> JobUtils.waitForCompletion(cloudFoundryClient, job));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static Mono<UploadApplicationResponse> uploadApplicationAsyncFalse(
            CloudFoundryClient cloudFoundryClient, String applicationId) {
        try {
            return cloudFoundryClient.applicationsV2()
                    .upload(UploadApplicationRequest.builder()
                            .application(new ClassPathResource("test-application.zip").getFile().toPath())
                            .async(false).applicationId(applicationId).build());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static Mono<ApplicationInstanceInfo> waitForInstanceRestart(CloudFoundryClient cloudFoundryClient,
            String applicationId, String instanceName, Optional<Double> optionalSince) {
        return getInstanceInfo(cloudFoundryClient, applicationId, instanceName)
                .filter(info -> !isIdentical(info.getSince(), optionalSince.orElse(null)))
                .repeatWhenEmpty(DelayUtils.exponentialBackOff(Duration.ofSeconds(1), Duration.ofSeconds(15),
                        Duration.ofMinutes(5)));
    }

    private static Mono<AbstractApplicationResource> waitForStagingApplication(
            CloudFoundryClient cloudFoundryClient, String applicationId) {
        return requestGetApplication(cloudFoundryClient, applicationId)
                .filter(response -> "STAGED".equals(response.getEntity().getPackageState()))
                .repeatWhenEmpty(DelayUtils.exponentialBackOff(Duration.ofSeconds(1), Duration.ofSeconds(15),
                        Duration.ofMinutes(5)));
    }

    private static Mono<ApplicationInstanceInfo> waitForStartingInstanceInfo(CloudFoundryClient cloudFoundryClient,
            String applicationId) {
        return cloudFoundryClient.applicationsV2()
                .instances(ApplicationInstancesRequest.builder().applicationId(applicationId).build())
                .flatMap(response -> Flux.fromIterable(response.getInstances().values()))
                .filter(applicationInstanceInfo -> "RUNNING".equals(applicationInstanceInfo.getState())).next()
                .repeatWhenEmpty(DelayUtils.exponentialBackOff(Duration.ofSeconds(1), Duration.ofSeconds(15),
                        Duration.ofMinutes(5)));
    }

    private Mono<byte[]> downloadApplication(CloudFoundryClient cloudFoundryClient, String applicationId) {
        return cloudFoundryClient.applicationsV2()
                .download(DownloadApplicationRequest.builder().applicationId(applicationId).build())
                .as(OperationUtils::collectByteArray);
    }

}