diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39ec971f..71fdd2a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: - 2.1.0 - 2.1.1 - 2.1.2 + - 2.2.0 - latest os: - ubuntu-latest diff --git a/README.md b/README.md index 47971243..c0ff308e 100644 --- a/README.md +++ b/README.md @@ -326,16 +326,31 @@ Vault's HTTP API: credentials). * `debug()`: Health check endpoints. -The driver DSL also allows you to specify retry logic, by chaining the `withRetries()` ahead of -accessing the endpoint implementation: +By default, API operations are not retried. To retry every call, set the retries in the +configuration: ``` // Retry up to 5 times if failures occur, waiting 1000 milliseconds in between each retry attempt. -final LogicalResponse response = vault.withRetries(5, 1000) +final VaultConfig config = new VaultConfig() + .address("http://127.0.0.1:8200") + .token("3c9fd6be-7bc2-9d1f-6fb3-cd746c0fc4e8") + .retries(5, 1000) + .build(); +``` + +To retry only some calls, chain `retrying()` ahead of accessing the endpoint implementation. It +returns a new `Vault` sharing the same configuration, and leaves the original `vault` unchanged: + +``` +final LogicalResponse response = vault.retrying(5, 1000) .logical() .read("secret/hello"); ``` +`withRetries()` is deprecated: despite being chained like `retrying()`, it changes the retries in the +configuration, so they apply to every later call, and to every `Vault` sharing that configuration. +Replace it with `retrying()` or `VaultConfig.retries()`. + Only transient failures are retried: I/O errors (e.g. connection refused or timed out) and HTTP 5xx, 408, 412 and 429 responses. Other 4xx responses (e.g. 400, 403, 404) fail immediately, since retrying them would not help. @@ -407,6 +422,17 @@ patches. * Javadoc examples use the `Sys` methods instead of the deprecated ones. The `Wrapping` examples are fixed: they used `new Vault(config)` instead of `Vault.create(config)`, and a misspelled `lookupWarp()` method + * Retries can be limited to some calls [(issue #75)](https://github.com/jopenlibs/vault-java-driver/issues/75). + `Vault.retrying(maxRetries, retryIntervalMilliseconds)` returns a new `Vault` whose operations + use those retries, leaving the original instance and its configuration unchanged. + `VaultConfig.retries(maxRetries, retryIntervalMilliseconds)` sets retries for every call. + Both reject negative values with `IllegalArgumentException`. The operation classes (e.g. + `Logical`, `Sys`, `Pki`) have new constructors taking a `RetryPolicy` + * `Vault.withRetries()` is deprecated, with unchanged behaviour: it writes the retries to the + shared `VaultConfig`, so they apply to every later call and to every `Vault` sharing that + configuration, not only the chained call. Use `retrying()` or `VaultConfig.retries()` instead. + `Vault.retrying()` is a `default` method throwing `UnsupportedOperationException`, so custom + implementations of `Vault` keep compiling * **6.2.3**: This release contains the following updates: * Improved Java 11 compatibility * Fixed integration tests with Vault 2.x diff --git a/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java b/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java new file mode 100644 index 00000000..9682bc61 --- /dev/null +++ b/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java @@ -0,0 +1,73 @@ +package io.github.jopenlibs.vault; + +/** + *
An immutable retry setting for API operations: the maximum number of times a failing + * operation is retried, and the time to wait between attempts.
+ * + *A policy with maxRetries set to 0 disables retries.
logical(), auth(),
- * etc... to specify retry rules for any API operations invoked on that endpoint.
+ * Sets the retry rules for API operations, by writing them to the configuration of this + * instance.
+ * + *Despite being chained ahead of endpoints (e.g. logical(),
+ * auth()), the retries are not limited to that call: they apply to every later
+ * call on this instance, and on every other Vault sharing the same configuration.
+ * A Vault returned by {@link #retrying(int, int)} ignores them.
Returns a new Vault whose API operations are retried with the given rules,
+ * overriding the retries set in the configuration. This instance, and its configuration, are
+ * left unchanged:
+ * {@code
+ * final LogicalResponse response = vault.retrying(5, 1000).logical().read("secret/hello");
+ * }
+ *
+ *
+ * The returned instance shares the configuration of this one, so later changes to it (e.g. + * a new token) apply to both.
+ * + *The default implementation throws UnsupportedOperationException, so that
+ * existing implementations of this interface keep compiling.
Vault using the given retries
+ * @throws IllegalArgumentException If either value is negative
+ * @throws UnsupportedOperationException If this implementation does not support it
+ * @see RetryPolicy
+ * @see VaultConfig#retries(int, int)
+ */
+ default Vault retrying(final int maxRetries, final int retryIntervalMilliseconds) {
+ throw new UnsupportedOperationException(
+ getClass().getName() + " does not support retrying()");
+ }
+
/**
* Returns the implementing class for Vault's core/logical operations (e.g. read, write).
*
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index c480c487..190337d3 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -319,12 +319,38 @@ public VaultConfig httpClient(HttpClient httpClient) {
return this;
}
+ /**
+ * Sets the retry rules for every API operation using this configuration: the maximum number + * of times that a failing operation will be retried, and the time to wait between attempts. + * By default, operations are not retried.
+ * + *To retry only some calls, use Vault.retrying() instead, which overrides
+ * these values.
Sets the maximum number of times that an API operation will retry upon failure.
* *This method is not meant to be called from application-level code outside of this package
- * (hence the protected access level. It is meant to be invoked via
- * Vault.withRetries() in a builder pattern DSL-style.
Vault.withRetries(): use {@link #retries(int, int)} instead.
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs.
@@ -338,8 +364,8 @@ void setMaxRetries(final int maxRetries) {
* attempts for a failing API operation.
*
* This method is not meant to be called from application-level code outside of this package
- * (hence the protected access level. It is meant to be invoked via
- * Vault.withRetries() in a builder pattern DSL-style.
Vault.withRetries(): use {@link #retries(int, int)} instead.
*
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries.
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index 04b81768..64bce224 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -43,7 +43,7 @@
* secrets.put("other_value", "You can store multiple name/value pairs under a given key");
*
* final LogicalResponse writeResponse = vault
- * .withRetries(5, 1000) // optional
+ * .retrying(5, 1000) // optional
* .logical()
* .write("secret/hello", secrets);
*
@@ -58,6 +58,11 @@
public class VaultImpl implements Vault {
private final VaultConfig vaultConfig;
+ /**
+ * The retries for the operations of this instance, or null to use the retries
+ * currently set in {@link #vaultConfig}.
+ */
+ private final RetryPolicy retryPolicy;
private final Logger logger = Logger.getLogger(VaultImpl.class.getCanonicalName());
/**
@@ -69,6 +74,7 @@ public class VaultImpl implements Vault {
*/
public VaultImpl(final VaultConfig vaultConfig) {
this.vaultConfig = vaultConfig;
+ this.retryPolicy = null;
logNameSpace();
if (this.vaultConfig.getSecretsEnginePathMap().isEmpty()
&& this.vaultConfig.getGlobalEngineVersion() == null) {
@@ -95,6 +101,7 @@ public VaultImpl(final VaultConfig vaultConfig, final Integer engineVersion) {
}
vaultConfig.setEngineVersion(engineVersion);
this.vaultConfig = vaultConfig;
+ this.retryPolicy = null;
logNameSpace();
}
@@ -115,6 +122,7 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
final Integer globalFallbackVersion)
throws VaultException {
this.vaultConfig = vaultConfig;
+ this.retryPolicy = null;
logNameSpace();
this.vaultConfig.setEngineVersion(globalFallbackVersion);
if (Boolean.TRUE.equals(useSecretsEnginePathMap) && this.vaultConfig.getSecretsEnginePathMap().isEmpty()) {
@@ -139,6 +147,15 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
}
}
+ /**
+ * Used by {@link #retrying(int, int)}: shares the configuration of an existing instance, which
+ * has already been set up, so the engine version setup is not run again.
+ */
+ private VaultImpl(final VaultConfig vaultConfig, final RetryPolicy retryPolicy) {
+ this.vaultConfig = vaultConfig;
+ this.retryPolicy = retryPolicy;
+ }
+
private void logNameSpace() {
final String nameSpace = this.vaultConfig.getNameSpace();
if (nameSpace != null && !nameSpace.isEmpty()) {
@@ -149,22 +166,51 @@ private void logNameSpace() {
}
/**
- * This method is chained ahead of endpoints (e.g. logical(), auth(),
- * etc... to specify retry rules for any API operations invoked on that endpoint.
+ * Sets the retry rules for API operations, by writing them to the configuration of this + * instance.
+ * + *Despite being chained ahead of endpoints (e.g. logical(),
+ * auth()), the retries are not limited to that call: they apply to every later
+ * call on this instance, and on every other Vault sharing the same configuration.
+ * A Vault returned by {@link #retrying(int, int)} ignores them.
Returns a new VaultImpl whose API operations are retried with the given
+ * rules, overriding the retries set in the configuration. This instance, and its configuration,
+ * are left unchanged.
The returned instance shares the configuration of this one, so later changes to it (e.g. + * a new token) apply to both.
+ * + * @param maxRetries The number of times that API operations will be retried when a failure + * occurs + * @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in + * between retries + * @return A newVaultImpl using the given retries
+ * @throws IllegalArgumentException If either value is negative
+ */
+ @Override
+ public VaultImpl retrying(final int maxRetries, final int retryIntervalMilliseconds) {
+ return new VaultImpl(vaultConfig, new RetryPolicy(maxRetries, retryIntervalMilliseconds));
+ }
+
/**
* Returns the implementing class for Vault's core/logical operations (e.g. read, write).
*
@@ -172,7 +218,7 @@ public VaultImpl withRetries(final int maxRetries, final int retryIntervalMillis
*/
@Override
public Logical logical() {
- return new Logical(vaultConfig);
+ return new Logical(vaultConfig, retryPolicy);
}
/**
@@ -183,7 +229,7 @@ public Logical logical() {
*/
@Override
public Auth auth() {
- return new Auth(vaultConfig);
+ return new Auth(vaultConfig, retryPolicy);
}
/**
@@ -194,7 +240,7 @@ public Auth auth() {
*/
@Override
public Sys sys() {
- return new Sys(vaultConfig);
+ return new Sys(vaultConfig, retryPolicy);
}
/**
@@ -205,7 +251,7 @@ public Sys sys() {
*/
@Override
public Pki pki() {
- return new Pki(vaultConfig);
+ return new Pki(vaultConfig, "pki", retryPolicy);
}
/**
@@ -232,17 +278,17 @@ public Pki pki() {
*/
@Override
public Pki pki(final String mountPath) {
- return new Pki(vaultConfig, mountPath);
+ return new Pki(vaultConfig, mountPath, retryPolicy);
}
@Override
public Database database() {
- return new Database(vaultConfig);
+ return new Database(vaultConfig, "database", retryPolicy);
}
@Override
public Database database(final String mountPath) {
- return new Database(vaultConfig, mountPath);
+ return new Database(vaultConfig, mountPath, retryPolicy);
}
/**
@@ -255,7 +301,7 @@ public Database database(final String mountPath) {
@Override
@Deprecated
public Leases leases() {
- return new Leases(vaultConfig);
+ return new Leases(vaultConfig, retryPolicy);
}
/**
@@ -265,7 +311,7 @@ public Leases leases() {
*/
@Override
public Debug debug() {
- return new Debug(vaultConfig);
+ return new Debug(vaultConfig, retryPolicy);
}
/**
@@ -279,7 +325,7 @@ public Debug debug() {
@Override
@Deprecated
public Mounts mounts() {
- return new Mounts(vaultConfig);
+ return new Mounts(vaultConfig, retryPolicy);
}
/**
@@ -292,7 +338,7 @@ public Mounts mounts() {
@Override
@Deprecated
public Seal seal() {
- return new Seal(vaultConfig);
+ return new Seal(vaultConfig, retryPolicy);
}
/**
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
index 42017d04..55747698 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.VaultImpl;
@@ -254,7 +255,17 @@ public String getEntityAlias() {
private String nameSpace;
public Auth(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Auth(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
@@ -1153,7 +1164,7 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
*/
@Deprecated
public LogicalResponse lookupWrap() throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().lookupWrap(config.getToken(), false);
}
@@ -1163,7 +1174,7 @@ public LogicalResponse lookupWrap() throws VaultException {
*/
@Deprecated
public LogicalResponse lookupWrap(final char[] wrappedToken) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().lookupWrap(wrappedToken, true);
}
@@ -1174,7 +1185,7 @@ public LogicalResponse lookupWrap(final char[] wrappedToken) throws VaultExcepti
@Deprecated
public LogicalResponse lookupWrap(final char[] wrappedToken, boolean inBody)
throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().lookupWrap(wrappedToken, inBody);
}
@@ -1220,7 +1231,7 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
*/
@Deprecated
public UnwrapResponse unwrap() throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().unwrap(config.getToken(), false);
}
@@ -1230,7 +1241,7 @@ public UnwrapResponse unwrap() throws VaultException {
*/
@Deprecated
public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().unwrap(wrappedToken, true);
}
@@ -1240,7 +1251,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
*/
@Deprecated
public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().unwrap(wrappedToken, inBody);
}
@@ -1250,7 +1261,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws V
*/
@Deprecated
public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().wrap(jsonObject, ttlInSec);
}
@@ -1260,7 +1271,7 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
*/
@Deprecated
public WrapResponse rewrap(final char[] wrappedToken) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().rewrap(wrappedToken);
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Debug.java b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
index 52832f34..ffe4de32 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Debug.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.response.HealthResponse;
@@ -21,7 +22,17 @@ public class Debug extends OperationsBase {
private String nameSpace;
public Debug(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Debug(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Logical.java b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
index ca7c91e7..9d3fbbcb 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.json.Json;
@@ -37,7 +38,17 @@ public class Logical extends OperationsBase {
public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, listV1, listV2, listSubKeys, readV1, readV2, writeV1, writeV2, unDelete, mount}
public Logical(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Logical(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
@@ -164,8 +175,8 @@ public LogicalResponse read(final String path, Boolean shouldRetry, final Intege
return new LogicalResponse(restResponse, attempt, logicalOperations.readV2);
},
- shouldRetry ? config.getMaxRetries() : 1,
- config.getRetryIntervalMilliseconds()
+ shouldRetry ? getMaxRetries() : 1,
+ getRetryIntervalMilliseconds()
);
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
index 5a0b9f66..7c5940ed 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.rest.Rest;
@@ -14,12 +15,47 @@ public abstract class OperationsBase {
protected final VaultConfig config;
+ /**
+ * The retries for this instance, or null to use the retries currently set in
+ * {@link #config}.
+ */
+ protected final RetryPolicy retryPolicy;
+
protected OperationsBase(VaultConfig config) {
+ this(config, null);
+ }
+
+ /**
+ * @param config The configuration
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration, read on every
+ * call
+ */
+ protected OperationsBase(VaultConfig config, RetryPolicy retryPolicy) {
this.config = config;
+ this.retryPolicy = retryPolicy;
+ }
+
+ /**
+ * @return The maximum number of retries from the retry policy if set, otherwise the one
+ * currently set in the configuration
+ */
+ protected int getMaxRetries() {
+ return retryPolicy != null ? retryPolicy.getMaxRetries() : config.getMaxRetries();
+ }
+
+ /**
+ * @return The interval between retries from the retry policy if set, otherwise the one
+ * currently set in the configuration
+ */
+ protected int getRetryIntervalMilliseconds() {
+ return retryPolicy != null
+ ? retryPolicy.getRetryIntervalMilliseconds()
+ : config.getRetryIntervalMilliseconds();
}
protected /v1/ prefix (e.g. "root-ca")
*/
public Database(final VaultConfig config, final String mountPath) {
- super(config);
+ this(config, mountPath, null);
+ }
+
+ /**
+ * Constructor for use when the Database backend is mounted on some non-default custom path, with
+ * a retry policy.
+ *
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param mountPath The path on which your Vault Database backend is mounted, without the
+ * /v1/ prefix (e.g. "db123")
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Database(final VaultConfig config, final String mountPath,
+ final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.mountPath = mountPath;
this.nameSpace = this.config.getNameSpace();
diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
index 1a3148e8..9fb0463e 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.pki;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -50,7 +51,23 @@ public Pki(final VaultConfig config) {
* /v1/ prefix (e.g. "root-ca")
*/
public Pki(final VaultConfig config, final String mountPath) {
- super(config);
+ this(config, mountPath, null);
+ }
+
+ /**
+ * Constructor for use when the PKI backend is mounted on some non-default custom path, with
+ * a retry policy.
+ *
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param mountPath The path on which your Vault PKI backend is mounted, without the
+ * /v1/ prefix (e.g. "root-ca")
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Pki(final VaultConfig config, final String mountPath,
+ final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.mountPath = mountPath;
this.nameSpace = this.config.getNameSpace();
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
index 10938922..8e06d2dd 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -23,7 +24,17 @@ public class Leases extends OperationsBase {
private String nameSpace;
public Leases(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Leases(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
index d4bd8e02..976305da 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -23,7 +24,17 @@ public class Seal extends OperationsBase {
private String nameSpace;
public Seal(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Seal(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
index 98011247..f5d3dc53 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -19,7 +20,17 @@ public class Sys extends OperationsBase {
private String nameSpace;
public Sys(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Sys(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
@@ -35,7 +46,7 @@ public Sys withNameSpace(final String nameSpace) {
* @return The implementing class for wrapping operations
*/
public Wrapping wrapping() {
- return new Wrapping(this.config);
+ return new Wrapping(this.config, this.retryPolicy);
}
/**
@@ -44,7 +55,7 @@ public Wrapping wrapping() {
* @return The implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus).
*/
public Seal seal() {
- return new Seal(this.config);
+ return new Seal(this.config, this.retryPolicy);
}
/**
@@ -54,7 +65,7 @@ public Seal seal() {
* @return the implementing class for Vault's sys mounts operations
*/
public Mounts mounts() {
- return new Mounts(this.config);
+ return new Mounts(this.config, this.retryPolicy);
}
/**
@@ -64,6 +75,6 @@ public Mounts mounts() {
* @return The implementing class for Vault's lease operations
*/
public Leases leases() {
- return new Leases(this.config);
+ return new Leases(this.config, this.retryPolicy);
}
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
index b8823ff9..bb88489c 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.Logical;
@@ -27,7 +28,17 @@ public class Wrapping extends OperationsBase {
private String nameSpace;
public Wrapping(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Wrapping(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
index 42b655bd..fa2862b6 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys.mounts;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -17,7 +18,17 @@
public class Mounts extends OperationsBase {
public Mounts(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Mounts(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
}
/**
diff --git a/src/test/java/io/github/jopenlibs/vault/RetryPolicyTests.java b/src/test/java/io/github/jopenlibs/vault/RetryPolicyTests.java
new file mode 100644
index 00000000..914d5f51
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/RetryPolicyTests.java
@@ -0,0 +1,53 @@
+package io.github.jopenlibs.vault;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+
+public class RetryPolicyTests {
+
+ @Test
+ public void testValues() {
+ final var policy = new RetryPolicy(5, 1000);
+ assertEquals(5, policy.getMaxRetries());
+ assertEquals(1000, policy.getRetryIntervalMilliseconds());
+ }
+
+ @Test
+ public void testZeroValuesAllowed() {
+ final var policy = new RetryPolicy(0, 0);
+ assertEquals(0, policy.getMaxRetries());
+ assertEquals(0, policy.getRetryIntervalMilliseconds());
+ }
+
+ @Test
+ public void testNegativeMaxRetriesRejected() {
+ assertThrows(IllegalArgumentException.class, () -> new RetryPolicy(-1, 1000));
+ }
+
+ @Test
+ public void testNegativeIntervalRejected() {
+ assertThrows(IllegalArgumentException.class, () -> new RetryPolicy(5, -1));
+ }
+
+ @Test
+ public void testEqualsAndHashCode() {
+ final var policy = new RetryPolicy(5, 1000);
+ final var same = new RetryPolicy(5, 1000);
+ assertEquals(policy, policy);
+ assertEquals(policy, same);
+ assertEquals(policy.hashCode(), same.hashCode());
+ assertNotEquals(policy, new RetryPolicy(4, 1000));
+ assertNotEquals(policy, new RetryPolicy(5, 999));
+ assertNotEquals(policy, null);
+ assertNotEquals(policy, "RetryPolicy");
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("RetryPolicy{maxRetries=5, retryIntervalMilliseconds=1000}",
+ new RetryPolicy(5, 1000).toString());
+ }
+}
diff --git a/src/test/java/io/github/jopenlibs/vault/RetryTests.java b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
index 2b021f9b..cb47620d 100644
--- a/src/test/java/io/github/jopenlibs/vault/RetryTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
@@ -1,11 +1,18 @@
package io.github.jopenlibs.vault;
+import io.github.jopenlibs.vault.api.OperationsBase;
import io.github.jopenlibs.vault.mock.RetriesMockVault;
import io.github.jopenlibs.vault.response.LogicalResponse;
+import java.util.List;
import java.util.Map;
import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
/**
* Unit tests for the Vault driver, having no dependency on an actual Vault server instance @@ -117,4 +124,213 @@ public void testNoRetries_Write_400() throws Exception { VaultTestUtils.shutdownMockVault(server); } + private static final String READ_RESPONSE = + "{\"lease_id\":\"12345\",\"renewable\":false,\"lease_duration\":10000,\"data\":{\"value\":\"mock\"}}"; + + @Test + public void testRetrying_Read() throws Exception { + final var server = VaultTestUtils.initHttpMockVault( + new RetriesMockVault(5, 200, READ_RESPONSE)); + server.start(); + try { + final var vault = Vault.create(mockConfig()); + final LogicalResponse response = vault.retrying(5, 10).logical().read("secret/hello"); + assertEquals(5, response.getRetries()); + assertEquals("mock", response.getData().get("value")); + } finally { + VaultTestUtils.shutdownMockVault(server); + } + } + + @Test + public void testRetrying_LeavesOriginalUnchanged() throws Exception { + final var server = VaultTestUtils.initHttpMockVault( + new RetriesMockVault(1, 200, READ_RESPONSE)); + server.start(); + try { + final var config = mockConfig(); + final var vault = Vault.create(config); + assertNotSame(vault, vault.retrying(5, 10)); + assertEquals(0, config.getMaxRetries()); + assertEquals(0, config.getRetryIntervalMilliseconds()); + + final var e = assertThrows(VaultException.class, + () -> vault.logical().read("secret/hello")); + assertEquals(500, e.getHttpStatusCode()); + } finally { + VaultTestUtils.shutdownMockVault(server); + } + } + + @Test + public void testRetrying_OverridesConfigRetries() throws Exception { + final var server = VaultTestUtils.initHttpMockVault( + new RetriesMockVault(3, 200, READ_RESPONSE)); + server.start(); + try { + final var vault = Vault.create(mockConfig()); + vault.withRetries(5, 10); + // 1 retry is fewer than the 3 failures, while the config retries would be enough + final var e = assertThrows(VaultException.class, + () -> vault.retrying(1, 10).logical().read("secret/hello")); + assertEquals(500, e.getHttpStatusCode()); + } finally { + VaultTestUtils.shutdownMockVault(server); + } + } + + @Test + public void testRetrying_SysLeasesGetsPolicy() throws Exception { + final var server = VaultTestUtils.initHttpMockVault(new RetriesMockVault(2, 204, null)); + server.start(); + try { + final var vault = Vault.create(mockConfig()); + final var response = vault.retrying(2, 10).sys().leases().revoke("lease-id"); + assertEquals(2, response.getRetries()); + } finally { + VaultTestUtils.shutdownMockVault(server); + } + } + + @Test + public void testRetrying_SharesConfig() throws Exception { + final var config = mockConfig(); + final var retrying = Vault.create(config).retrying(5, 10); + assertSame(config, baseFieldOf(retrying.auth(), "config")); + config.token("new_token"); + assertArrayEquals("new_token".toCharArray(), + ((VaultConfig) baseFieldOf(retrying.logical(), "config")).getToken()); + } + + @Test + @SuppressWarnings("deprecation") + public void testRetrying_EveryOperationGetsPolicy() throws Exception { + final var vault = Vault.create(mockConfig()); + final var policy = new RetryPolicy(5, 10); + final var retrying = vault.retrying(5, 10); + for (final OperationsBase operations : List.of(retrying.logical(), retrying.auth(), + retrying.sys(), retrying.pki(), retrying.pki("root-ca"), retrying.database(), + retrying.database("db123"), retrying.leases(), retrying.debug(), + retrying.mounts(), retrying.seal())) { + assertEquals(operations.getClass().getName(), policy, retryPolicyOf(operations)); + } + for (final OperationsBase operations : List.of(vault.logical(), vault.pki(), + vault.database())) { + assertNull(operations.getClass().getName(), retryPolicyOf(operations)); + } + } + + @Test + public void testRetrying_PkiAndDatabaseUseDefaultMountPaths() throws Exception { + final var retrying = Vault.create(mockConfig()).retrying(5, 10); + assertEquals("pki", fieldOf(retrying.pki(), "mountPath")); + assertEquals("database", fieldOf(retrying.database(), "mountPath")); + } + + @Test + public void testRetrying_RejectsNegativeValues() throws Exception { + final var vault = Vault.create(mockConfig()); + assertThrows(IllegalArgumentException.class, () -> vault.retrying(-1, 10)); + assertThrows(IllegalArgumentException.class, () -> vault.retrying(5, -1)); + } + + @Test + public void testConfigRetries_SetsValues() throws Exception { + final var config = new VaultConfig(); + assertSame(config, config.retries(5, 1000)); + assertEquals(5, config.getMaxRetries()); + assertEquals(1000, config.getRetryIntervalMilliseconds()); + } + + @Test + public void testConfigRetries_AppliesToEveryCall() throws Exception { + final var vault = Vault.create(new VaultConfig().address("http://127.0.0.1:8999") + .token("mock_token").engineVersion(1).retries(2, 10).build()); + + final var readServer = VaultTestUtils.initHttpMockVault( + new RetriesMockVault(2, 200, READ_RESPONSE)); + readServer.start(); + try { + assertEquals(2, vault.logical().read("secret/hello").getRetries()); + } finally { + VaultTestUtils.shutdownMockVault(readServer); + } + + final var revokeServer = VaultTestUtils.initHttpMockVault( + new RetriesMockVault(2, 204, null)); + revokeServer.start(); + try { + assertEquals(2, vault.sys().leases().revoke("lease-id").getRetries()); + } finally { + VaultTestUtils.shutdownMockVault(revokeServer); + } + } + + @Test + public void testConfigRetries_RejectsNegativeValues() throws Exception { + final var config = new VaultConfig().retries(5, 1000); + assertThrows(IllegalArgumentException.class, () -> config.retries(-1, 10)); + assertThrows(IllegalArgumentException.class, () -> config.retries(3, -1)); + assertEquals(5, config.getMaxRetries()); + assertEquals(1000, config.getRetryIntervalMilliseconds()); + } + + @Test + public void testConfigRetries_OverriddenByRetrying() throws Exception { + final var server = VaultTestUtils.initHttpMockVault( + new RetriesMockVault(3, 200, READ_RESPONSE)); + server.start(); + try { + final var vault = Vault.create(mockConfig().retries(5, 10)); + final var e = assertThrows(VaultException.class, + () -> vault.retrying(1, 10).logical().read("secret/hello")); + assertEquals(500, e.getHttpStatusCode()); + } finally { + VaultTestUtils.shutdownMockVault(server); + } + } + + @Test + @SuppressWarnings("deprecation") + public void testWithRetries_Unchanged() throws Exception { + final var config = mockConfig(); + final var vault = Vault.create(config); + final var other = Vault.create(config); + final var retrying = vault.retrying(1, 10); + + assertSame(vault, vault.withRetries(5, 1000)); + assertEquals(5, config.getMaxRetries()); + assertEquals(1000, config.getRetryIntervalMilliseconds()); + // Shared by every Vault using the config, but ignored by one returned by retrying() + assertNull(retryPolicyOf(other.logical())); + assertEquals(new RetryPolicy(1, 10), retryPolicyOf(retrying.logical())); + + // Negative values are still written to the config, not validated + vault.withRetries(-1, -1); + assertEquals(-1, config.getMaxRetries()); + assertEquals(-1, config.getRetryIntervalMilliseconds()); + } + + private static VaultConfig mockConfig() throws VaultException { + return new VaultConfig().address("http://127.0.0.1:8999") + .token("mock_token").engineVersion(1).build(); + } + + private static Object retryPolicyOf(final OperationsBase operations) throws Exception { + return baseFieldOf(operations, "retryPolicy"); + } + + private static Object baseFieldOf(final OperationsBase operations, final String name) + throws Exception { + final var field = OperationsBase.class.getDeclaredField(name); + field.setAccessible(true); + return field.get(operations); + } + + private static Object fieldOf(final Object target, final String name) throws Exception { + final var field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + } diff --git a/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java b/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java index 6c5e2388..ee96719a 100644 --- a/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java +++ b/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java @@ -20,6 +20,7 @@ import org.junit.rules.ExpectedException; import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; /** @@ -152,12 +153,13 @@ public void testRead() throws VaultException { @Test public void testReadExceptionNotFound() throws VaultException { - expectedEx.expect(VaultException.class); - expectedEx.expectMessage("cannot fetch sysview for path"); - final Vault vault = container.getRootVault(); - vault.sys().mounts().read("pki-non-existing-path"); + // Vault 2.2 and later answer "invalid request" instead of "cannot fetch sysview for path", + // so only the status code is checked + final VaultException e = assertThrows(VaultException.class, + () -> vault.sys().mounts().read("pki-non-existing-path")); + assertEquals(400, e.getHttpStatusCode()); } @Test diff --git a/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java b/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java index e339f8a8..3dbb3a64 100644 --- a/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java +++ b/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java @@ -1,5 +1,8 @@ package io.github.jopenlibs.vault.api; +import io.github.jopenlibs.vault.RetryPolicy; +import io.github.jopenlibs.vault.Vault; +import io.github.jopenlibs.vault.VaultConfig; import io.github.jopenlibs.vault.VaultException; import io.github.jopenlibs.vault.rest.Rest; import io.github.jopenlibs.vault.rest.RestException; @@ -10,6 +13,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -107,6 +111,94 @@ public void testStopsRetryingWhenInterrupted() { assertTrue(Thread.currentThread().isInterrupted()); } + @Test + public void testNullPolicyUsesConfigRetries() throws Exception { + final var config = configWithRetries(2, 1); + final var operations = new TestOperations(config); + assertNull(operations.retryPolicy); + assertEquals(2, operations.getMaxRetries()); + assertEquals(1, operations.getRetryIntervalMilliseconds()); + assertEquals(3, operations.countAttempts()); + } + + @Test + public void testNullPolicyReadsConfigOnEveryCall() throws Exception { + final var config = configWithRetries(1, 1); + final var operations = new TestOperations(config, null); + assertEquals(2, operations.countAttempts()); + + Vault.create(config).withRetries(3, 1); + assertEquals(3, operations.getMaxRetries()); + assertEquals(4, operations.countAttempts()); + } + + @Test + public void testNullPolicyKeepsNegativeConfigValues() throws Exception { + final var config = configWithRetries(-1, -5); + final var operations = new TestOperations(config); + assertEquals(-1, operations.getMaxRetries()); + assertEquals(-5, operations.getRetryIntervalMilliseconds()); + assertEquals(1, operations.countAttempts()); + } + + @Test + public void testPolicyOverridesConfigRetries() throws Exception { + final var config = configWithRetries(5, 1000); + final var policy = new RetryPolicy(1, 2); + final var operations = new TestOperations(config, policy); + assertSame(policy, operations.retryPolicy); + assertEquals(1, operations.getMaxRetries()); + assertEquals(2, operations.getRetryIntervalMilliseconds()); + assertEquals(2, operations.countAttempts()); + + Vault.create(config).withRetries(3, 1); + assertEquals(2, operations.countAttempts()); + assertEquals(3, config.getMaxRetries()); + } + + @Test + public void testZeroPolicyDisablesRetries() throws Exception { + final var operations = new TestOperations(configWithRetries(5, 1), new RetryPolicy(0, 0)); + assertEquals(1, operations.countAttempts()); + } + + private static VaultConfig configWithRetries(final int maxRetries, + final int retryIntervalMilliseconds) throws VaultException { + final var config = new VaultConfig().address("http://127.0.0.1:8999") + .token("mock_token").engineVersion(1).build(); + Vault.create(config).withRetries(maxRetries, retryIntervalMilliseconds); + return config; + } + + private static final class TestOperations extends OperationsBase { + + TestOperations(final VaultConfig config) { + super(config); + } + + TestOperations(final VaultConfig config, final RetryPolicy retryPolicy) { + super(config, retryPolicy); + } + + /** + * @return The number of attempts made for an operation that always fails with a transient + * error + */ + int countAttempts() { + final var calls = new AtomicInteger(); + try { + retry(attempt -> { + calls.incrementAndGet(); + throw new VaultException("unavailable", 503); + }); + fail("Expected VaultException"); + } catch (VaultException e) { + assertEquals(503, e.getHttpStatusCode()); + } + return calls.get(); + } + } + private static RestException connectionRefused() { try { new Rest().url("http://127.0.0.1:1/").connectTimeoutSeconds(1).get(); diff --git a/src/test/java/io/github/jopenlibs/vault/api/RetryPolicyPropagationTests.java b/src/test/java/io/github/jopenlibs/vault/api/RetryPolicyPropagationTests.java new file mode 100644 index 00000000..e621cc03 --- /dev/null +++ b/src/test/java/io/github/jopenlibs/vault/api/RetryPolicyPropagationTests.java @@ -0,0 +1,105 @@ +package io.github.jopenlibs.vault.api; + +import io.github.jopenlibs.vault.RetryPolicy; +import io.github.jopenlibs.vault.VaultConfig; +import io.github.jopenlibs.vault.VaultException; +import io.github.jopenlibs.vault.VaultTestUtils; +import io.github.jopenlibs.vault.api.database.Database; +import io.github.jopenlibs.vault.api.pki.Pki; +import io.github.jopenlibs.vault.api.sys.Leases; +import io.github.jopenlibs.vault.api.sys.Seal; +import io.github.jopenlibs.vault.api.sys.Sys; +import io.github.jopenlibs.vault.api.sys.Wrapping; +import io.github.jopenlibs.vault.api.sys.mounts.Mounts; +import io.github.jopenlibs.vault.mock.RetriesMockVault; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * Tests that the operation classes keep the {@link RetryPolicy} they are given, and pass it to the + * operation classes they create. + */ +public class RetryPolicyPropagationTests { + + private static final RetryPolicy POLICY = new RetryPolicy(3, 10); + + @Test + public void testConstructorsKeepPolicy() throws VaultException { + final var config = config(); + assertSame(POLICY, new Logical(config, POLICY).retryPolicy); + assertSame(POLICY, new Auth(config, POLICY).retryPolicy); + assertSame(POLICY, new Debug(config, POLICY).retryPolicy); + assertSame(POLICY, new Sys(config, POLICY).retryPolicy); + assertSame(POLICY, new Leases(config, POLICY).retryPolicy); + assertSame(POLICY, new Seal(config, POLICY).retryPolicy); + assertSame(POLICY, new Wrapping(config, POLICY).retryPolicy); + assertSame(POLICY, new Mounts(config, POLICY).retryPolicy); + assertSame(POLICY, new Pki(config, "pki", POLICY).retryPolicy); + assertSame(POLICY, new Database(config, "database", POLICY).retryPolicy); + } + + @Test + public void testConfigOnlyConstructorsHaveNoPolicy() throws VaultException { + final var config = config(); + assertNull(new Logical(config).retryPolicy); + assertNull(new Auth(config).retryPolicy); + assertNull(new Debug(config).retryPolicy); + assertNull(new Sys(config).retryPolicy); + assertNull(new Leases(config).retryPolicy); + assertNull(new Seal(config).retryPolicy); + assertNull(new Wrapping(config).retryPolicy); + assertNull(new Mounts(config).retryPolicy); + assertNull(new Pki(config).retryPolicy); + assertNull(new Pki(config, "root-ca").retryPolicy); + assertNull(new Database(config).retryPolicy); + assertNull(new Database(config, "db123").retryPolicy); + } + + @Test + public void testPkiAndDatabaseKeepMountPathConstructorWithNull() throws VaultException { + final var config = config(); + // Must still compile: a (VaultConfig, RetryPolicy) overload would make these ambiguous + assertNull(new Pki(config, null).retryPolicy); + assertNull(new Database(config, null).retryPolicy); + } + + @Test + public void testSysPassesPolicy() throws VaultException { + final var sys = new Sys(config(), POLICY); + assertSame(POLICY, sys.wrapping().retryPolicy); + assertSame(POLICY, sys.seal().retryPolicy); + assertSame(POLICY, sys.mounts().retryPolicy); + assertSame(POLICY, sys.leases().retryPolicy); + } + + @Test + public void testSysWithoutPolicyPassesNull() throws VaultException { + final var sys = new Sys(config()); + assertNull(sys.wrapping().retryPolicy); + assertNull(sys.seal().retryPolicy); + assertNull(sys.mounts().retryPolicy); + assertNull(sys.leases().retryPolicy); + } + + @Test + public void testAuthPassesPolicyToSys() throws Exception { + // The config allows no retries, so the call only succeeds if Auth passes its policy on + final var server = VaultTestUtils.initHttpMockVault( + new RetriesMockVault(2, 200, "{\"data\":{\"token\":\"mock\"}}")); + server.start(); + try { + final var response = new Auth(config(), POLICY).lookupWrap(); + assertEquals(2, response.getRetries()); + } finally { + VaultTestUtils.shutdownMockVault(server); + } + } + + private static VaultConfig config() throws VaultException { + return new VaultConfig().address("http://127.0.0.1:8999").token("mock_token") + .engineVersion(1).build(); + } +}