-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added module embedded aerospike enterprise with configured mand…
…atory durable deletes by default
- Loading branch information
Showing
20 changed files
with
570 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
=== embedded-aerospike-enterprise | ||
|
||
TIP: This module provides integration with https://github.com/Shopify/toxiproxy[ToxiProxy] out of the box. | ||
ToxiProxy is a great tool for simulating network conditions, meaning that you can test your application's resiliency. | ||
|
||
==== Difference with `embedded-aerospike` module | ||
|
||
* Aerospike Enterprise container version must be not less then 6.3.0, because of option for disallow non-durable deletes. | ||
* By default disallow [non-durable deletes](#Disallow non-durable deletes). | ||
|
||
==== Maven dependency | ||
|
||
.pom.xml | ||
[source,xml] | ||
---- | ||
<dependency> | ||
<groupId>com.playtika.testcontainers</groupId> | ||
<artifactId>embedded-aerospike-enterprise</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
---- | ||
|
||
==== Consumes (via `bootstrap.properties`) | ||
|
||
* `embedded.aerospike.enabled` `(true|false, default is 'true')` | ||
* `embedded.aerospike.reuseContainer` `(true|false, default is 'false')` | ||
* `embedded.aerospike.dockerImage` `(default is set to 'aerospike/aerospike-server-enterprise:6.3.0.16_1')` | ||
** Aerospike Enterprise version must be not less then 6.3.0 | ||
* `embedded.aerospike.featureKey` base64 of a feature-key-file https://aerospike.com/docs/server/operations/configure/feature-key, default is null. | ||
**Warning: if not provided, the Aerospike Database EE evaluation feature key file will be used. That means you can use it internally only for Evaluation | ||
purposes only during the Evaluation Period**. See https://github.com/aerospike/aerospike-server.docker/blob/master/enterprise/ENTERPRISE_LICENSE` | ||
* `embedded.aerospike.waitTimeoutInSeconds` `(default is 60 seconds)` | ||
* `embedded.toxiproxy.proxies.aerospike.enabled` Enables both creation of the container with ToxiProxy TCP proxy and a proxy to the `embedded-aerospike` container. | ||
* `embedded.aerospike.time-travel.enabled` Enables time travel to clean expired documents by time. Does not work on ARM(mac m1) because of LUA scripts are not supported on ARM. | ||
* `embedded.aerospike.enterprise.durableDeletes` Enables disallow non-durable deletes for Aerospike Enterprise Server. By default is true. | ||
* https://mvnrepository.com/artifact/com.aerospike/aerospike-client[aerospike client library] | ||
|
||
==== Produces | ||
|
||
* `embedded.aerospike.host` | ||
* `embedded.aerospike.port` | ||
* `embedded.aerospike.namespace` | ||
* `embedded.aerospike.toxiproxy.host` | ||
* `embedded.aerospike.toxiproxy.port` | ||
* `embedded.aerospike.networkAlias` | ||
* `embedded.aerospike.internalPort` | ||
* Bean `AerospikeTestOperations aerospikeTestOperations` | ||
* Bean `ToxiproxyContainer.ContainerProxy aerospikeContainerProxy` | ||
|
||
==== Example | ||
|
||
See `embedded-aerospike` module readme for examples. | ||
|
||
==== Enterprise features | ||
|
||
===== Disallow non-durable deletes | ||
|
||
Aerospike server never delete record from disk(SSD), but the index in memory that points to the record is removed. | ||
If the location on disk of the deleted record was not overwritten prior to reboot, the record will be indexed during cold restart. | ||
The record then returns from the disk to the database as a zombie record. | ||
|
||
To avoid this, Aerospike provide a feature called https://aerospike.com/docs/server/guide/durable_deletes[Durable Deletes]. | ||
Durable deletes typically free storage when they generate a tombstone, a record without any bins that contains all metadata including the key. | ||
Tombstones correctly resolve conflicts and prevent previously persisted versions of deleted objects from resurrecting when the index is repopulated. | ||
This feature is available only for Aerospike Enterprise Edition. | ||
|
||
This library reconfigure com.playtika.testcontainers:embedded-aerospike | ||
to use Aerospike Enterprise Edition docker image, and set up Durable Deletes feature as mandatory. | ||
If the client call delete operation without setting WritePolicy.durableDeletes to true, the operation | ||
will fail with Aerospike Error 22 (forbidden). This is done by configuring aerospike server with option | ||
https://aerospike.com/docs/server/reference/configuration#disallow-expunge[disallow-expunge]. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xmlns="http://maven.apache.org/POM/4.0.0" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<artifactId>testcontainers-spring-boot-parent</artifactId> | ||
<groupId>com.playtika.testcontainers</groupId> | ||
<version>3.1.1</version> | ||
<relativePath>../testcontainers-spring-boot-parent</relativePath> | ||
</parent> | ||
|
||
<artifactId>embedded-aerospike-enterprise</artifactId> | ||
|
||
<properties> | ||
<aerospike-client.version>7.2.0</aerospike-client.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<!-- | ||
aerospike client is provided since we want users to pick own version, | ||
and not rely on test library | ||
--> | ||
<dependency> | ||
<groupId>com.aerospike</groupId> | ||
<artifactId>aerospike-client</artifactId> | ||
<version>${aerospike-client.version}</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.playtika.testcontainers</groupId> | ||
<artifactId>embedded-aerospike</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
40 changes: 40 additions & 0 deletions
40
.../java/com/playtika/testcontainers/aerospike/enterprise/AerospikeEnterpriseConfigurer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.playtika.testcontainers.aerospike.enterprise; | ||
|
||
import com.playtika.testcontainer.aerospike.AerospikeProperties; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.testcontainers.containers.Container; | ||
import org.testcontainers.containers.GenericContainer; | ||
|
||
import java.io.IOException; | ||
|
||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class AerospikeEnterpriseConfigurer { | ||
|
||
private final AerospikeProperties aerospikeProperties; | ||
private final AerospikeEnterpriseProperties enterpriseProperties; | ||
|
||
public void configure(GenericContainer<?> aerospikeContainer) throws IOException, InterruptedException { | ||
if (aerospikeProperties.getFeatureKey() == null || aerospikeProperties.getFeatureKey().isBlank()) { | ||
log.warn("Evaluation feature key file not provided by 'embedded.aerospike.featureKey' property. " + | ||
"Pay attention to license details: https://github.com/aerospike/aerospike-server.docker/blob/master/enterprise/ENTERPRISE_LICENSE"); | ||
} | ||
|
||
setupDisallowExpunge(aerospikeContainer); | ||
} | ||
|
||
private void setupDisallowExpunge(GenericContainer<?> aerospikeContainer) throws IOException, InterruptedException { | ||
if (!enterpriseProperties.isDurableDeletes()) { | ||
return; | ||
} | ||
log.info("Setting up 'disallow-expunge' to true..."); | ||
String namespace = aerospikeProperties.getNamespace(); | ||
Container.ExecResult result = aerospikeContainer.execInContainer("asadm", "-e", | ||
String.format("enable; manage config namespace %s param disallow-expunge to true", namespace)); | ||
if (result.getStderr().length() > 0) { | ||
throw new IllegalStateException("Failed to set up 'disallow-expunge' to true: " + result.getStderr()); | ||
} | ||
log.info("Set up 'disallow-expunge' to true: {}", result.getStdout()); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
.../java/com/playtika/testcontainers/aerospike/enterprise/AerospikeEnterpriseProperties.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.playtika.testcontainers.aerospike.enterprise; | ||
|
||
import lombok.Data; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
@Data | ||
@ConfigurationProperties("embedded.aerospike.enterprise") | ||
public class AerospikeEnterpriseProperties { | ||
|
||
boolean durableDeletes = true; | ||
} |
28 changes: 28 additions & 0 deletions
28
...stcontainers/aerospike/enterprise/EnterpriseAerospikeTestOperationsAutoConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.playtika.testcontainers.aerospike.enterprise; | ||
|
||
import com.aerospike.client.AerospikeClient; | ||
import com.playtika.testcontainer.aerospike.AerospikeExpiredDocumentsCleaner; | ||
import com.playtika.testcontainer.aerospike.AerospikeProperties; | ||
import com.playtika.testcontainer.aerospike.EmbeddedAerospikeTestOperationsAutoConfiguration; | ||
import com.playtika.testcontainer.aerospike.ExpiredDocumentsCleaner; | ||
import org.springframework.boot.autoconfigure.AutoConfiguration; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
import org.springframework.context.annotation.Bean; | ||
|
||
@AutoConfiguration(afterName = "org.springframework.boot.autoconfigure.aerospike.AerospikeAutoConfiguration", | ||
before = EmbeddedAerospikeTestOperationsAutoConfiguration.class) | ||
@ConditionalOnExpression("${embedded.containers.enabled:true}") | ||
@ConditionalOnBean({AerospikeClient.class, AerospikeProperties.class}) | ||
@ConditionalOnProperty(value = "embedded.aerospike.enabled", matchIfMissing = true) | ||
public class EnterpriseAerospikeTestOperationsAutoConfiguration { | ||
|
||
@Bean | ||
@ConditionalOnProperty(value = "embedded.aerospike.time-travel.enabled", havingValue = "true", matchIfMissing = true) | ||
public ExpiredDocumentsCleaner expiredDocumentsCleaner(AerospikeClient client, | ||
AerospikeProperties properties) { | ||
return new AerospikeExpiredDocumentsCleaner(client, properties.getNamespace(), true); | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
...terprise/src/main/java/com/playtika/testcontainers/aerospike/enterprise/ImageVersion.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.playtika.testcontainers.aerospike.enterprise; | ||
|
||
import lombok.NonNull; | ||
|
||
import java.util.Comparator; | ||
|
||
record ImageVersion (int major, int minor) implements Comparable<ImageVersion> { | ||
|
||
static ImageVersion parse(String version) { | ||
String[] parts = version.split("\\."); | ||
if (parts.length < 2) { | ||
throw new IllegalArgumentException("Invalid version: " + version); | ||
} | ||
try { | ||
int major = Integer.parseInt(parts[0]); | ||
int minor = Integer.parseInt(parts[1]); | ||
return new ImageVersion(major, minor); | ||
} catch (NumberFormatException e) { | ||
throw new IllegalArgumentException("Invalid version: " + version, e); | ||
} | ||
} | ||
|
||
@Override | ||
public int compareTo(@NonNull ImageVersion o) { | ||
return Comparator.comparingInt(ImageVersion::major) | ||
.thenComparingInt(ImageVersion::minor) | ||
.compare(this, o); | ||
} | ||
} |
104 changes: 104 additions & 0 deletions
104
...a/testcontainers/aerospike/enterprise/SetupEnterpriseAerospikeBootstrapConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package com.playtika.testcontainers.aerospike.enterprise; | ||
|
||
import com.playtika.testcontainer.aerospike.AerospikeProperties; | ||
import com.playtika.testcontainer.aerospike.EmbeddedAerospikeBootstrapConfiguration; | ||
import jakarta.annotation.PostConstruct; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Qualifier; | ||
import org.springframework.boot.autoconfigure.AutoConfiguration; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
import org.springframework.context.annotation.PropertySource; | ||
import org.springframework.core.env.Environment; | ||
import org.testcontainers.containers.GenericContainer; | ||
|
||
import java.io.IOException; | ||
|
||
@Slf4j | ||
@AutoConfiguration(after = EmbeddedAerospikeBootstrapConfiguration.class) | ||
@ConditionalOnExpression("${embedded.containers.enabled:true}") | ||
@ConditionalOnProperty(value = "embedded.aerospike.enabled", matchIfMissing = true) | ||
@EnableConfigurationProperties(AerospikeEnterpriseProperties.class) | ||
@PropertySource("classpath:/embedded-enterprise-aerospike.properties") | ||
public class SetupEnterpriseAerospikeBootstrapConfiguration { | ||
|
||
private static final String DOCKER_IMAGE = "aerospike/aerospike-server-enterprise:6.3.0.16_1"; | ||
private static final String AEROSPIKE_DOCKER_IMAGE_PROPERTY = "embedded.aerospike.dockerImage"; | ||
private static final ImageVersion SUITABLE_IMAGE_VERSION = new ImageVersion(6, 3); | ||
private static final String TEXT_TO_DOCUMENTATION = "Documentation: https://github.com/PlaytikaOSS/testcontainers-spring-boot/blob/develop/embedded-aerospike-enterprise/README.adoc"; | ||
|
||
private GenericContainer<?> aerospikeContainer; | ||
private AerospikeProperties aerospikeProperties; | ||
private AerospikeEnterpriseProperties aerospikeEnterpriseProperties; | ||
private Environment environment; | ||
|
||
@Autowired | ||
public void setEnvironment(Environment environment) { | ||
this.environment = environment; | ||
} | ||
|
||
@Autowired | ||
@Qualifier(AerospikeProperties.BEAN_NAME_AEROSPIKE) | ||
public void setAerospikeContainer(GenericContainer<?> aerospikeContainer) { | ||
this.aerospikeContainer = aerospikeContainer; | ||
} | ||
|
||
@Autowired | ||
public void setAerospikeProperties(AerospikeProperties aerospikeProperties) { | ||
this.aerospikeProperties = aerospikeProperties; | ||
} | ||
|
||
@Autowired | ||
public void setAerospikeEnterpriseProperties(AerospikeEnterpriseProperties aerospikeEnterpriseProperties) { | ||
this.aerospikeEnterpriseProperties = aerospikeEnterpriseProperties; | ||
} | ||
|
||
@PostConstruct | ||
public void setupEnterpriseAerospike() throws IOException, InterruptedException { | ||
verifyAerospikeImage(); | ||
AerospikeEnterpriseConfigurer aerospikeEnterpriseConfigurer = new AerospikeEnterpriseConfigurer(aerospikeProperties, aerospikeEnterpriseProperties); | ||
aerospikeEnterpriseConfigurer.configure(aerospikeContainer); | ||
} | ||
|
||
private void verifyAerospikeImage() { | ||
log.info("Verify Aerospike Enterprise Image"); | ||
|
||
String dockerImage = environment.getProperty(AEROSPIKE_DOCKER_IMAGE_PROPERTY); | ||
if (dockerImage == null) { | ||
throw new IllegalStateException("Aerospike enterprise docker image not provided, set up 'embedded.aerospike.dockerImage' property.\n" | ||
+ TEXT_TO_DOCUMENTATION); | ||
} | ||
|
||
if (!isEnterpriseImage(dockerImage)) { | ||
throw illegalAerospikeImageException(); | ||
} | ||
} | ||
|
||
private IllegalStateException illegalAerospikeImageException() { | ||
return new IllegalStateException( | ||
"You should use enterprise image for the Aerospike container with equal or higher version: " + DOCKER_IMAGE + ". " | ||
+ "Container enable 'disallow-expunge' config option to prevent non-durable deletes, and this config option is available starting with version 6.3. " | ||
+ "Enterprise image is required, as non-durable deletes are not available in Community." | ||
+ TEXT_TO_DOCUMENTATION | ||
); | ||
} | ||
|
||
private boolean isEnterpriseImage(String dockerImage) { | ||
return dockerImage.contains("enterprise") | ||
&& isSuitableVersion(dockerImage); | ||
} | ||
|
||
private boolean isSuitableVersion(String dockerImage) { | ||
int index = dockerImage.indexOf(":"); | ||
if (index == -1) { | ||
throw new IllegalStateException("Invalid docker image version format: " + dockerImage + ".\n" | ||
+ TEXT_TO_DOCUMENTATION); | ||
} | ||
String version = dockerImage.substring(index + 1); | ||
ImageVersion imageVersion = ImageVersion.parse(version); | ||
return imageVersion.compareTo(SUITABLE_IMAGE_VERSION) >= 0; | ||
} | ||
|
||
} |
2 changes: 2 additions & 0 deletions
2
embedded-aerospike-enterprise/src/main/resources/META-INF/spring.factories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ | ||
com.playtika.testcontainers.aerospike.enterprise.SetupEnterpriseAerospikeBootstrapConfiguration |
1 change: 1 addition & 0 deletions
1
...esources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
com.playtika.testcontainers.aerospike.enterprise.EnterpriseAerospikeTestOperationsAutoConfiguration |
1 change: 1 addition & 0 deletions
1
embedded-aerospike-enterprise/src/main/resources/embedded-enterprise-aerospike.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
embedded.aerospike.dockerImage=aerospike/aerospike-server-enterprise:6.3.0.16_1 |
Oops, something went wrong.