Skip to content

Commit

Permalink
#1039 Using Redis as a cache manager
Browse files Browse the repository at this point in the history
  • Loading branch information
dcoraboeuf committed Oct 30, 2022
1 parent 006002f commit 288724b
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 194 deletions.
6 changes: 6 additions & 0 deletions compose/docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ services:
ports:
- "${POSTGRES_PORT:-5432}:5432"

redis:
image: redis/redis-stack:6.2.4-v3
ports:
- "6379:6379"
- "8001:8001"

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.4
ports:
Expand Down
6 changes: 0 additions & 6 deletions ontrack-docs/src/docs/asciidoc/configuration-properties.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,6 @@ ontrack.config.key-store = file
# Optional. If not filled in, will use a subdirectory of the working directory
# ontrack.config.file-key-store.directory =
# Cache configuration
# Caffeine spec strings per cache type
# See http://static.javadoc.io/com.github.ben-manes.caffeine/caffeine/2.6.0/com/github/benmanes/caffeine/cache/CaffeineSpec.html
# For example, for the `properties` cache:
ontrack.config.cache.specs.properties = maximumSize=1000,expireAfterWrite=1d,recordStats
#################################
# CasC properties
#################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ package net.nemerosa.ontrack.extension.api
interface CacheConfigExtension {
/**
* List of configurable caches.
*
* Returns a map whose key is the cache name, and the value is a
* `com.github.benmanes.caffeine.cache.CaffeineSpec` string specification.
*/
val caches: Map<String, String>
val caches: Map<String, CacheConfigExtensionData>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.nemerosa.ontrack.extension.api

import java.time.Duration

/**
* Cache definition
*
* @property ttl TTL for a given entry in a cache
*/
data class CacheConfigExtensionData(
val ttl: Duration,
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package net.nemerosa.ontrack.extension.git

import net.nemerosa.ontrack.extension.api.CacheConfigExtension
import net.nemerosa.ontrack.extension.api.CacheConfigExtensionData
import org.springframework.stereotype.Component
import java.time.Duration

/**
* Configuration of caching for the Git module
Expand All @@ -10,7 +12,9 @@ import org.springframework.stereotype.Component
class GitCacheConfigExtension(
gitConfigProperties: GitConfigProperties,
) : CacheConfigExtension {
override val caches: Map<String, String> = mapOf(
CACHE_GIT_CHANGE_LOG to "maximumSize=20,expireAfterWrite=10m,recordStats",
override val caches: Map<String, CacheConfigExtensionData> = mapOf(
CACHE_GIT_CHANGE_LOG to CacheConfigExtensionData(
ttl = Duration.ofMinutes(10)
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,33 +176,6 @@ class OntrackConfigProperties {
* Allows the token to be used as passwords.
*/
var password: Boolean = true

/**
* Cache properties
*/
var cache = TokensCacheProperties()
}

/**
* Token cache properties
*/
class TokensCacheProperties {
/**
* Is caching of the tokens enabled?
*/
var enabled = true

/**
* Cache validity period
*/
@DurationUnit(ChronoUnit.MINUTES)
var validity: Duration = Duration.ofDays(30)

/**
* Maximum number of items in the cache. Should be aligned with the
* number of sessions. Note that the objects stored in the cache are tiny.
*/
var maxCount: Long = 1_000
}

companion object {
Expand Down
2 changes: 1 addition & 1 deletion ontrack-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation(project(":ontrack-model"))
implementation(project(":ontrack-repository"))
implementation(project(":ontrack-extension-api"))
Expand All @@ -17,7 +18,6 @@ dependencies {
implementation("commons-io:commons-io")
implementation("org.apache.commons:commons-lang3")
implementation("org.jgrapht:jgrapht-core")
implementation("com.github.ben-manes.caffeine:caffeine")
implementation("org.elasticsearch.client:elasticsearch-rest-high-level-client")
implementation("org.flywaydb:flyway-core")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,8 @@
package net.nemerosa.ontrack.service

import com.github.benmanes.caffeine.cache.Caffeine
import net.nemerosa.ontrack.common.Caches
import net.nemerosa.ontrack.extension.api.CacheConfigExtension
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.caffeine.CaffeineCache
import org.springframework.cache.support.SimpleCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.TimeUnit

@Configuration
@EnableCaching
class CacheConfig(
private val cacheConfigProperties: CacheConfigProperties,
private val cacheConfigExtensions: List<CacheConfigExtension>
) {
@Bean
fun cacheManager(): CacheManager {
val manager = SimpleCacheManager()

manager.setCaches(
// Built in caches
listOf(
// Cache for settings
CaffeineCache(
Caches.SETTINGS,
Caffeine.newBuilder()
.maximumSize(1)
.expireAfterWrite(10, TimeUnit.HOURS)
.build()
)
) + cacheConfigExtensions.flatMap {
it.caches.map { (name, spec) -> toCache(name, spec) }
}
)

return manager
}

private fun toCache(name: String, defaultSpec: String) = CaffeineCache(
name,
Caffeine.from(
cacheConfigProperties.specs[name] ?: defaultSpec
).build()
)

}
class CacheConfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.nemerosa.ontrack.service

import net.nemerosa.ontrack.common.Caches
import net.nemerosa.ontrack.extension.api.CacheConfigExtension
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class CacheConfigCustomizer(
private val cacheConfigExtensions: List<CacheConfigExtension>,
) : RedisCacheManagerBuilderCustomizer {
override fun customize(builder: RedisCacheManagerBuilder) {
// Core caches
builder.withCacheConfiguration(
Caches.SETTINGS,
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
)
// Extensions
cacheConfigExtensions.forEach { extension ->
extension.caches.forEach { (name, config) ->
builder.withCacheConfiguration(
name,
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(config.ttl)
)
}
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package net.nemerosa.ontrack.service.security

import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import net.nemerosa.ontrack.common.Time
import net.nemerosa.ontrack.model.security.Account
import net.nemerosa.ontrack.model.security.AccountManagement
Expand All @@ -17,18 +15,13 @@ import java.time.Duration
@Service
@Transactional
class TokensServiceImpl(
private val tokensRepository: TokensRepository,
private val securityService: SecurityService,
private val tokenGenerator: TokenGenerator,
private val ontrackConfigProperties: OntrackConfigProperties,
private val accountService: AccountService
private val tokensRepository: TokensRepository,
private val securityService: SecurityService,
private val tokenGenerator: TokenGenerator,
private val ontrackConfigProperties: OntrackConfigProperties,
private val accountService: AccountService,
) : TokensService {

private val cache: Cache<String, Boolean> = Caffeine.newBuilder()
.maximumSize(ontrackConfigProperties.security.tokens.cache.maxCount)
.expireAfterAccess(ontrackConfigProperties.security.tokens.cache.validity)
.build()

override val currentToken: Token?
get() {
// Gets the current account
Expand All @@ -42,9 +35,9 @@ class TokensServiceImpl(
override fun generateNewToken(): Token {
// Gets the current account
val account = securityService.currentAccount?.account
?: throw TokenGenerationNoAccountException()
?: throw TokenGenerationNoAccountException()
// Generates a new token
return securityService.asAdmin { generateToken(account.id(), null, false) }
return securityService.asAdmin { generateToken(account.id(), null, false) }
}

override fun generateToken(accountId: Int, validity: Duration?, forceUnlimited: Boolean): Token {
Expand All @@ -71,9 +64,7 @@ class TokensServiceImpl(
val account = securityService.currentAccount?.account
// Revokes its token
account?.apply {
val token = tokensRepository.invalidate(id())
// Removes any cache token
token?.let { cache.invalidate(token) }
tokensRepository.invalidate(id())
}
}

Expand All @@ -87,50 +78,37 @@ class TokensServiceImpl(
}

override fun isValid(token: String): Boolean {
if (ontrackConfigProperties.security.tokens.cache.enabled) {
val valid = cache.getIfPresent(token)
return if (valid != null) {
valid
} else {
val stillValid = internalValidityCheck(token)
cache.put(token, stillValid)
return stillValid
}
} else {
return internalValidityCheck(token)
}
return internalValidityCheck(token)
}

private fun internalValidityCheck(token: String): Boolean =
tokensRepository
.findAccountByToken(token)
?.let { (_, result) ->
result.isValid()
}
?: false
tokensRepository
.findAccountByToken(token)
?.let { (_, result) ->
result.isValid()
}
?: false

override fun findAccountByToken(token: String): TokenAccount? {
// Find the account ID
val result = tokensRepository.findAccountByToken(token)
return result?.let { (accountId, token) ->
TokenAccount(
securityService.asAdmin {
accountService.getAccount(ID.of(accountId))
},
token
securityService.asAdmin {
accountService.getAccount(ID.of(accountId))
},
token
)
}
}

override fun revokeAll(): Int {
securityService.checkGlobalFunction(AccountManagement::class.java)
cache.invalidateAll()
return tokensRepository.revokeAll()
}

override fun revokeToken(accountId: Int) {
securityService.checkGlobalFunction(AccountManagement::class.java)
val token = tokensRepository.invalidate(accountId)
token?.let { cache.invalidate(token) }
tokensRepository.invalidate(accountId)
}
}
Loading

0 comments on commit 288724b

Please sign in to comment.