diff --git a/integration-test/src/test/java/org/cloudfoundry/CleanupCloudFoundryAfterClass.java b/integration-test/src/test/java/org/cloudfoundry/CleanupCloudFoundryAfterClass.java new file mode 100644 index 0000000000..88d8fd6303 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/CleanupCloudFoundryAfterClass.java @@ -0,0 +1,27 @@ +package org.cloudfoundry; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.context.annotation.Bean; +import org.springframework.test.annotation.DirtiesContext; + +/** + * Meta-annotation to show that a test class will add too much to the CF instance, and that a full universe + * cleanup should occur. This is important because otherwise the integration tests create too many apps and + * blow up the memory quota. We do not want to recreate the full environment for EVERY test class because + * the process takes 30~60s, so in total it could add more than an hour to the integration tests. + *

+ * Technically, this is achieved by recreating a Spring ApplicationContext with {@link DirtiesContext}. The + * {@link CloudFoundryCleaner} bean will be destroyed, which triggers {@link CloudFoundryCleaner#clean()}. + * After that, every {@link Bean} in {@link IntegrationTestConfiguration} is recreated, including users, + * clients, organizations, etc: everything required to run a test. + *

+ * We use a meta-annotation instead of a raw {@link DirtiesContext} to make it clear what it does, rather + * than having to understand complicated lifecycle issues. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@DirtiesContext +public @interface CleanupCloudFoundryAfterClass {} diff --git a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java index c4fb056ad2..d515a86b09 100644 --- a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java +++ b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java @@ -116,12 +116,16 @@ import org.cloudfoundry.util.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; import reactor.util.retry.Retry; -final class CloudFoundryCleaner { +final class CloudFoundryCleaner implements InitializingBean, DisposableBean { + + private static boolean cleanSlateEnvironment = false; private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.test"); @@ -162,6 +166,25 @@ final class CloudFoundryCleaner { this.uaaClient = uaaClient; } + /** + * Once at the beginning of the whole test suite, clean up the environment. It should only ever happen + * once, hence the static init variable. + */ + @Override + public void afterPropertiesSet() { + if (!cleanSlateEnvironment) { + LOGGER.info( + "Performing clean slate cleanup. Should happen once per integration test run."); + this.clean(); + cleanSlateEnvironment = true; + } + } + + @Override + public void destroy() { + this.clean(); + } + void clean() { Flux.empty() .thenMany( diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 12571face1..c0b3f44b30 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -37,7 +37,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; -import java.util.Random; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.info.GetInfoRequest; import org.cloudfoundry.client.v2.organizationquotadefinitions.CreateOrganizationQuotaDefinitionRequest; @@ -243,14 +242,13 @@ String clientSecret(NameFactory nameFactory) { return nameFactory.getClientSecret(); } - @Bean(initMethod = "clean", destroyMethod = "clean") + @Bean CloudFoundryCleaner cloudFoundryCleaner( @Qualifier("admin") CloudFoundryClient cloudFoundryClient, NameFactory nameFactory, @Qualifier("admin") NetworkingClient networkingClient, Version serverVersion, @Qualifier("admin") UaaClient uaaClient) { - return new CloudFoundryCleaner( cloudFoundryClient, nameFactory, networkingClient, serverVersion, uaaClient); } @@ -339,8 +337,8 @@ ReactorLogCacheClient logCacheClient( } @Bean - RandomNameFactory nameFactory(Random random) { - return new RandomNameFactory(random); + RandomNameFactory nameFactory() { + return new RandomNameFactory(new SecureRandom()); } @Bean(initMethod = "block") @@ -447,11 +445,6 @@ String planName(NameFactory nameFactory) { return nameFactory.getPlanName(); } - @Bean - SecureRandom random() { - return new SecureRandom(); - } - @Bean RoutingClient routingClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { return ReactorRoutingClient.builder() diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java index 6cfbab97d8..9774568bd0 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java @@ -36,6 +36,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -96,6 +97,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple2; +@CleanupCloudFoundryAfterClass public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java index 75486720fc..214577e4a9 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java @@ -25,6 +25,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.ApplicationUtils; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.servicebrokers.CreateServiceBrokerRequest; @@ -43,6 +44,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ServiceBrokersTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java index 6775ef1df5..d185f15dea 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java @@ -18,6 +18,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.ApplicationUtils; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -25,12 +26,18 @@ import org.cloudfoundry.util.JobUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import reactor.core.publisher.Mono; import reactor.test.StepVerifier; public final class AdminTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; + // The buildpacks cache needs to be non-empty for the DELETE call to succeed. + // We pull the "testLogCacheApp" bean, which ensures the bean will be initialized, + // the app will be pushed, and it will add some data to the cache. + @Autowired private Mono testLogCacheApp; + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10) @Test public void clearBuildpackCache() { diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java index 3354b24ed7..ef3ea073c8 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.Map; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -106,6 +107,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuples; +@CleanupCloudFoundryAfterClass public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java index 61ec02076f..d823ad7b1c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -51,6 +52,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_4) +@CleanupCloudFoundryAfterClass public final class DeploymentsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java index b509114793..e4a4184e85 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -42,6 +43,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_0) +@CleanupCloudFoundryAfterClass public final class ProcessesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java index f7c063c445..656a0b0efe 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java @@ -25,6 +25,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.ApplicationUtils; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.ServiceBrokerUtils; @@ -49,6 +50,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10) +@CleanupCloudFoundryAfterClass public final class ServiceBrokersTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java index a4c962d0f1..0e3fdada8c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -50,6 +51,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_1_12) +@CleanupCloudFoundryAfterClass public final class TasksTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java b/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java index 656f84111e..414af210bd 100644 --- a/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java @@ -21,6 +21,7 @@ import static org.cloudfoundry.util.DelayUtils.exponentialBackOff; import java.math.BigInteger; +import java.security.SecureRandom; import java.time.Duration; import java.util.Random; import java.util.function.Predicate; @@ -28,29 +29,27 @@ import org.cloudfoundry.ApplicationUtils; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_9) -public class LogCacheTest extends AbstractIntegrationTest implements InitializingBean { +public class LogCacheTest extends AbstractIntegrationTest { @Autowired LogCacheClient logCacheClient; - @Autowired Random random; - - @Autowired private Mono testLogCacheApp; - private ApplicationUtils.ApplicationMetadata testLogCacheAppMetadata; @Autowired private TestLogCacheEndpoints testLogCacheEndpoints; - @Override - public void afterPropertiesSet() { - this.testLogCacheAppMetadata = this.testLogCacheApp.block(); + private final Random random = new SecureRandom(); + + @BeforeEach + void setUp(@Autowired Mono testLogCacheApp) { + this.testLogCacheAppMetadata = testLogCacheApp.block(); } @Test diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 45addf4fb2..2ffd49b285 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.logcache.v1.Envelope; @@ -87,11 +88,13 @@ import org.cloudfoundry.util.FluentMap; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ApplicationsTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; @@ -106,6 +109,12 @@ public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private LogCacheClient logCacheClient; + // To create a service in #pushBindService, the Service Broker must be installed first. + // We ensure it is by loading the serviceBrokerId @Lazy bean. + @Qualifier("serviceBrokerId") + @Autowired + private Mono serviceBrokerId; + @Test public void copySource() throws IOException { String sourceName = this.nameFactory.getApplicationName(); diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java index 5b86191b22..19db1a53b1 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java @@ -27,6 +27,7 @@ import java.util.Optional; import java.util.function.Predicate; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; import org.cloudfoundry.operations.applications.PushApplicationRequest; import org.cloudfoundry.operations.domains.CreateDomainRequest; @@ -49,6 +50,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class RoutesTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java index 890c4c09b7..7a8f1ec378 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java @@ -21,6 +21,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.spaces.CreateSpaceRequest; @@ -33,10 +34,12 @@ import org.cloudfoundry.util.ResourceUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ServiceAdminTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; @@ -53,6 +56,12 @@ public final class ServiceAdminTest extends AbstractIntegrationTest { @Autowired private String serviceName; + // To create a service in #pushBindService, the Service Broker must be installed first. + // We ensure it is by loading the serviceBrokerId @Lazy bean. + @Qualifier("serviceBrokerId") + @Autowired + private Mono serviceBrokerId; + @Test public void disableServiceAccess() { String planName = this.nameFactory.getPlanName(); diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java index 1d3355a4ba..520027128b 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java @@ -23,6 +23,7 @@ import java.nio.file.Path; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.servicebindings.ListServiceBindingsRequest; import org.cloudfoundry.client.v2.servicebindings.ServiceBindingResource; @@ -64,6 +65,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ServicesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient;