Skip to content

Commit

Permalink
Support Redis support for SSL and password (#717)
Browse files Browse the repository at this point in the history

Signed-off-by: munishchouhan <[email protected]>
Signed-off-by: Paolo Di Tommaso <[email protected]>
Co-authored-by: Paolo Di Tommaso <[email protected]>
  • Loading branch information
munishchouhan and pditommaso authored Nov 22, 2024
1 parent 63ff591 commit bf63599
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 8 deletions.
10 changes: 10 additions & 0 deletions configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ Rate limit configuration controls the limits of anonymous and authenticated user

- **`redis.pool.enabled`**: whether to enable the Redis pool. It is set to `true` by default, enabling the use of a connection pool for efficient management of connections to the Redis server. *Optional*.

- **`redis.pool.minIdle`**: Specifies the minimum number of idle connections to maintain in the Redis connection pool. The default value is `0`. This ensures that connections are readily available for use.  *Optional*.

- **`redis.pool.maxIdle`**: Specifies the maximum number of idle connections to maintain in the Redis connection pool. The default value is `10`.  *Optional*.

- **`redis.pool.maxTotal`**: Specifies the maximum number of connections that can be maintained in the Redis connection pool. The default value is `50`. This helps to manage resource usage efficiently while supporting high demand.  *Optional*.

- **`redis.client.timeout`**: Defines the timeout duration (in milliseconds) for Redis client operations. The default value is `5000` (5 seconds).  *Optional*.

- **`redis.password`**: Specifies the password used to authenticate with the Redis server. This is needed when redis authentication is enabled.  *Optional*.

- **`surreal.default.ns`**: the namespace for the Surreal database. It can be set using `${SURREALDB_NS}` environment variable. *Mandatory*.

- **`surreal.default.db`**: the name of the Surreal database. It can be set using`${SURREALDB_DB}` environment variable. This setting defines the target database within the Surreal database system that Wave should interact with. *Mandatory*.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Requires
import io.seqera.wave.configuration.RedisConfig
import jakarta.inject.Inject
import jakarta.inject.Singleton
import jakarta.validation.constraints.NotNull
import redis.clients.jedis.JedisPool
Expand All @@ -51,9 +52,7 @@ class SpillWayStorageFactory {

@Singleton
@Requires(property = 'redis.uri')
LimitUsageStorage redisStorage(@NotNull RedisConfig redisConfig){
log.info "Using redis $redisConfig.uri as storage for rate limit"
def jedisPool = new JedisPool(redisConfig.uri)
return RedisStorage.builder().withJedisPool(jedisPool).build()
LimitUsageStorage redisStorage(JedisPool pool){
return RedisStorage.builder().withJedisPool(pool).build()
}
}
37 changes: 33 additions & 4 deletions src/main/groovy/io/seqera/wave/redis/RedisFactory.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Requires
import io.micronaut.context.annotation.Value
import io.micronaut.core.annotation.Nullable
import jakarta.inject.Singleton
import redis.clients.jedis.DefaultJedisClientConfig
import redis.clients.jedis.JedisClientConfig
import redis.clients.jedis.JedisPool
import redis.clients.jedis.JedisPoolConfig
import redis.clients.jedis.exceptions.InvalidURIException
import redis.clients.jedis.util.JedisURIHelper
/**
* Redis connection pool factory
*
Expand All @@ -39,17 +44,41 @@ class RedisFactory {

@Singleton
JedisPool createRedisPool(
@Value('${redis.uri}') String uri,
@Value('${redis.uri}') String connection,
@Value('${redis.pool.minIdle:0}') int minIdle,
@Value('${redis.pool.maxIdle:10}') int maxIdle,
@Value('${redis.pool.maxTotal:50}') int maxTotal
@Value('${redis.pool.maxTotal:50}') int maxTotal,
@Value('${redis.client.timeout:5000}') int timeout,
@Nullable @Value('${redis.password}') String password
) {
log.info "Using redis $uri as storage for rate limit - pool minIdle: ${minIdle}; maxIdle: ${maxIdle}; maxTotal: ${maxTotal}"
log.info "Using redis ${connection} as storage for rate limit - pool minIdle: ${minIdle}; maxIdle: ${maxIdle}; maxTotal: ${maxTotal}; timeout: ${timeout}"

final uri = URI.create(connection)
// pool config
final config = new JedisPoolConfig()
config.setMinIdle(minIdle)
config.setMaxIdle(maxIdle)
config.setMaxTotal(maxTotal)
return new JedisPool(config, URI.create(uri))
// client config
final clientConfig = clientConfig(uri, password, timeout)
// create the jedis pool
return new JedisPool(config, JedisURIHelper.getHostAndPort(uri), clientConfig)
}

protected JedisClientConfig clientConfig(URI uri, String password, int timeout) {
if (!JedisURIHelper.isValid(uri)) {
throw new InvalidURIException("Invalid Redis connection URI: ${uri}")
}

return DefaultJedisClientConfig.builder().connectionTimeoutMillis(timeout)
.socketTimeoutMillis(timeout)
.blockingSocketTimeoutMillis(timeout)
.user(JedisURIHelper.getUser(uri))
.password(password?:JedisURIHelper.getPassword(uri))
.database(JedisURIHelper.getDBIndex(uri))
.protocol(JedisURIHelper.getRedisProtocol(uri))
.ssl(JedisURIHelper.isRedisSSLScheme(uri))
.build()
}

}
62 changes: 62 additions & 0 deletions src/test/groovy/io/seqera/wave/redis/RedisFactoryTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Wave, containers provisioning service
* Copyright (c) 2023-2024, Seqera Labs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package io.seqera.wave.redis

import spock.lang.Specification

import redis.clients.jedis.exceptions.InvalidURIException
/**
*
* @author Munish Chouhan <[email protected]>
*/
class RedisFactoryTest extends Specification {
def 'should create redis pool with valid URI'() {
given:
def factory = new RedisFactory()

when:
def pool = factory.createRedisPool(URI_STRING, MIN_IDLE, MAX_IDLE, MAX_TOTAL, TIMEOUT, 'password')

then:
pool != null

where:
URI_STRING | MIN_IDLE | MAX_IDLE | MAX_TOTAL | TIMEOUT
'redis://localhost:6379' | 0 | 10 | 50 | 5000
'rediss://localhost:6379'| 1 | 5 | 20 | 3000
}

def 'should throw exception for invalid URI'() {
given:
def factory = new RedisFactory()

when:
factory.createRedisPool(URI_STRING, MIN_IDLE, MAX_IDLE, MAX_TOTAL, TIMEOUT, null)

then:
def e = thrown(InvalidURIException)
e.message.contains("Invalid Redis connection URI: $URI_STRING")

where:
URI_STRING | MIN_IDLE | MAX_IDLE | MAX_TOTAL | TIMEOUT
'redis://localhost' | 0 | 10 | 50 | 5000
'localhost:6379' | 1 | 5 | 20 | 3000
}

}

0 comments on commit bf63599

Please sign in to comment.