3

I want spring-vault configuration marked with VaultPropertySource to be able to retry the requests to the vault if they fail. What should i mark as retryable ? I'm using Spring-Retry and i was looking over http://www.baeldung.com/spring-retry .

There is no visible method to mark as retryable. Should I change the implementation of the vaultTemplate and mark the vaultOperations as retryable ?

ProvisioningSecrets.java

    @Configuration
    @VaultPropertySource(
            value="secret/provisioning",
            propertyNamePrefix = "provisioning.",
            renewal = Renewal.RENEW
            )
    @EnableRetry
    @Lazy
    @Profile("!test")
    public class ProvisioningSecrets {
        private static final Logger logger = LoggerFactory.getLogger(ProvisioningSecrets.class);

        @Autowired
        public void setPassword(@Value("${provisioning.password}") final String password) throws Exception {
            logger.info("We successfully set the provisioning db password.");
            EnvVars.changeSetting(Setting.PROVISIONING_PASS, password);
        }

        @Autowired
        public void setHost(@Value("${provisioning.host}") final String host) throws Exception {
            logger.info("We successfully set the provisioning db host.");
            EnvVars.changeSetting(Setting.PROVISIONING_HOST, host);
        }

        @Autowired
        public void setPort(@Value("${provisioning.port}") final int port) throws Exception {
            logger.info("We successfully set the provisioning db port.");
            EnvVars.changeSetting(Setting.PROVISIONING_PORT, Integer.toString(port));
        }

        @Autowired
        public void setUsername(@Value("${provisioning.username}") final String username) throws Exception {
            logger.info("We successfully set the provisioning db username.");
            EnvVars.changeSetting(Setting.PROVISIONING_USER, username);
        }

        @Autowired
        public void setDbName(@Value("${provisioning.name}") final String name) throws Exception {
            logger.info("We successfully set the provisioning db name.");
            EnvVars.changeSetting(Setting.PROVISIONING_DB_NAME, name);
        }
    }

VaultConfiguration.java

@Configuration
@Profile("!test")
public class VaultConfiguration extends AbstractVaultConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(VaultConfiguration.class);

    private URI vaultHost;

    private String vaultToken;

    /**
     * Configure the Client Authentication.
     *
     * @return A configured ClientAuthentication Object.
     * @see ClientAuthentication
     */
    @Override
    public ClientAuthentication clientAuthentication() {
        // testing out environment variable value injection
        logger.debug("Vault Token configuration done.");
        return new TokenAuthentication(vaultToken);
    }

    @Override
    @Bean
    @DependsOn("vaultToken")
    public SessionManager sessionManager() {
        return super.sessionManager();
    }

    @Override
    public SslConfiguration sslConfiguration() {
        logger.info("Configuring Vault SSL with NONE.");
        return SslConfiguration.NONE;
    }

    /**
     * Specify an endpoint for connecting to Vault.
     *
     * @return A configured VaultEndpoint.
     * @see VaultEndpoint
     */
    @Override
    public VaultEndpoint vaultEndpoint() {
        logger.debug("Vault Host:" + vaultHost.toString());

        if (vaultHost.toString().isEmpty()) {
            logger.info("Creating default Vault Endpoint.");
            return new VaultEndpoint();
        }

        logger.info("Creating Vault Endpoint based on address: " + vaultHost.toString());
        final VaultEndpoint endpoint = VaultEndpoint.from(vaultHost);
        logger.info("Created Vault Endpoint: " + endpoint.toString());

        return endpoint;
    }

    @Bean("vaultHost")
    public URI vaultHost(@Value("${spring.vault.host}") final URI vaultHost) {
        this.vaultHost = vaultHost;
        return vaultHost;
    }

    @Override
    @Bean
    @DependsOn("vaultHost")
    public VaultTemplate vaultTemplate() {
        return super.vaultTemplate();
    }

    @Bean("vaultToken")
    public String vaultToken(@Value("${spring.vault.token}") final String vaultToken) {
        this.vaultToken = vaultToken;
        return vaultToken;
    }
}
Daniel Mann
  • 57,011
  • 13
  • 100
  • 120
Daniel Colceag
  • 190
  • 1
  • 3
  • 15

1 Answers1

2

How about creating a custom VaultTemplate bean class using RetryTemplate?

public class RetryableVaultTemplate extends VaultTemplate {

    private final RetryTemplate retryTemplate;

    public RetryableVaultTemplate(VaultEndpointProvider endpointProvider,
            ClientHttpRequestFactory clientHttpRequestFactory,
            SessionManager sessionManager, RetryTemplate retryTemplate) {
        super(endpointProvider, clientHttpRequestFactory, sessionManager);
        this.retryTemplate = retryTemplate;
    }

    @Override
    public VaultResponse read(final String path) {

        return retryTemplate
                .execute(new RetryCallback<VaultResponse, RuntimeException>() {
                    @Override
                    public VaultResponse doWithRetry(RetryContext context) {
                        System.out.println("doWithRetry");
                        return RetryableVaultTemplate.super.read(path);
                    }
                });
    }

    @Override
    public <T> VaultResponseSupport<T> read(final String path, final Class<T> responseType) {

        return retryTemplate
                .execute(new RetryCallback<VaultResponseSupport<T>, RuntimeException>() {
                    @Override
                    public VaultResponseSupport<T> doWithRetry(RetryContext context) {
                        return RetryableVaultTemplate.super.read(path, responseType);
                    }
                });
    }
}

Make sure to register this bean class as vaultTemplate bean instead of VaultTemplate.

mp911de
  • 17,546
  • 2
  • 55
  • 95
  • I annotate the class with @Component("vaultTemplate") and it will be detected and used automatically by Spring ? Should I have an @Autowired VaultTemplate vaultTemplate inside my VaultConfiguration class? Thank you for your extensive response, welcome back from holidays and Happy New Year! ;) – Daniel Colceag Jan 03 '18 at 13:05
  • what have I achieved until now: I have configured and used a SimpleRetryPolicy and an ExponentialBackOffPolicy. I have to know if my implementation is correct and if it's properly done. Could you have a look at my implementation, please ? Please ignore the hardcoded configuration of the Retry and BackOff Policies and if you could suggest how could I configure it from application.yml properties, I would be most grateful. I'll post the code on pastebin. RetryableVaultTemplate: https://pastebin.com/43ge5ekY . VaultConfiguration: https://pastebin.com/5SM4AiML . – Daniel Colceag Jan 03 '18 at 18:44
  • I see spring-retry does not handle 404 errors, how can we configure the RestTemplate or HttpClient to retry on these kind of HTTP statuses ? as in http://hc.apache.org/httpclient-3.x/exception-handling.html http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/HttpMethodRetryHandler.html . How can we provide a custom HttpMethodRetryHandler to the RetryableVaultTemplate clientHttpRequestFactory ? – Daniel Colceag Jan 04 '18 at 00:04
  • I managed to enable the retry mechanism for 404 from ``` @Override public VaultResponse doWithRetry(RetryContext context) { logger.debug("log me doWithRetry"); VaultResponse response = RetryableVaultTemplate.super.read(path); if (response != null) { return response; } throw new RuntimeException("Empty response received. Probably 404."); } }); }``` – Daniel Colceag Jan 04 '18 at 01:08
  • In the VaultTemplate the read method was checking for HttpStatus.NOT_FOUND and returned null. I checked for null and I've thrown a RuntimeException with "Empty response received. Probably 404" message. It would be nice to be able to catch the actual Exception. How can I do that? What is an elegant method to do this? – Daniel Colceag Jan 04 '18 at 01:09
  • You need to distinguish between states that you enter because of an HTTP status and states caused by network behavior (timeout, connection rejected). HTTP Status 404 is a well-defined status code meaning the entry is not found (absence). In any case, this discussion is off-topic and not related to the actual question. – mp911de Jan 04 '18 at 20:37
  • I had the 404 as an example, because it was one of the easy to reproduce errors. Does implementing the doRead function from VaultTemplate make sense in the doWithRetry override and handle some of the http status codes? Do you want me to post another question on SO regarding the subject? – Daniel Colceag Jan 04 '18 at 23:07