mirror of
https://github.com/google/nomulus
synced 2026-05-23 08:11:48 +00:00
Add Jedis (for Valkey) caches for domains and hosts (#3013)
We add optional Valkey caching of hosts and domains for future use. Eventually, this will allow us to pre-warm large amounts of data in Valkey for quick retrieval during actions like RDAP. Note: this doesn't actually use the caches yet. We use Jedis instead of Redisson for speed purposes (https://www.instaclustr.com/blog/redis-java-clients-and-client-side-caching/) which means that we have to implement our own multilayer cache but that's not the worst thing in the world. Tested on crash with logging and RDAP code that's not included in this PR -- it behaves as you'd expect, where the local cache works for immediate re-lookups and the remote cache works after a restart.
This commit is contained in:
123
console-webapp/package-lock.json
generated
123
console-webapp/package-lock.json
generated
@@ -628,18 +628,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/@types/node": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/@types/node/-/node-25.5.0.tgz",
|
||||
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz",
|
||||
@@ -653,24 +641,6 @@
|
||||
"vite": "^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -694,22 +664,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
@@ -743,15 +697,6 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/vite": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
|
||||
@@ -971,24 +916,6 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/cli-spinners": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz",
|
||||
@@ -1092,22 +1019,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli/node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
@@ -4695,24 +4606,6 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/cli-spinners": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz",
|
||||
@@ -4816,22 +4709,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@schematics/angular/node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
|
||||
@@ -190,6 +190,9 @@ dependencies {
|
||||
testRuntimeOnly deps['guru.nidi:graphviz-java-all-j2v8']
|
||||
testImplementation deps['io.github.classgraph:classgraph']
|
||||
testRuntimeOnly deps['io.github.java-diff-utils:java-diff-utils']
|
||||
testImplementation deps['io.github.ss-bhatt:testcontainers-valkey']
|
||||
implementation deps['io.protostuff:protostuff-core']
|
||||
implementation deps['io.protostuff:protostuff-runtime']
|
||||
implementation deps['jakarta.inject:jakarta.inject-api']
|
||||
implementation deps['jakarta.mail:jakarta.mail-api']
|
||||
implementation deps['jakarta.persistence:jakarta.persistence-api']
|
||||
@@ -243,6 +246,7 @@ dependencies {
|
||||
implementation deps['org.testcontainers:postgresql']
|
||||
testImplementation deps['org.testcontainers:selenium']
|
||||
testImplementation deps['org.testcontainers:testcontainers']
|
||||
implementation deps['redis.clients:jedis']
|
||||
implementation deps['us.fatehi:schemacrawler']
|
||||
implementation deps['us.fatehi:schemacrawler-api']
|
||||
implementation deps['us.fatehi:schemacrawler-diagram']
|
||||
|
||||
@@ -173,7 +173,7 @@ com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.25.1=compileClasspath,
|
||||
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.26.5=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.code.gson:gson:2.10.1=soy
|
||||
com.google.code.gson:gson:2.12.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.code.gson:gson:2.13.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.common.html.types:types:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
|
||||
com.google.dagger:dagger-compiler:2.59.2=annotationProcessor,testAnnotationProcessor
|
||||
com.google.dagger:dagger-spi:2.59.2=annotationProcessor,testAnnotationProcessor
|
||||
@@ -290,6 +290,7 @@ io.github.classgraph:classgraph:4.8.162=compileClasspath,deploy_jar,nonprodCompi
|
||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.16=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.github.ss-bhatt:testcontainers-valkey:1.0.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-alts:1.70.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
io.grpc:grpc-alts:1.80.0=testCompileClasspath,testRuntimeClasspath
|
||||
io.grpc:grpc-api:1.70.0=compileClasspath,nonprodCompileClasspath
|
||||
@@ -396,6 +397,10 @@ io.opentelemetry:opentelemetry-sdk:1.60.1=testCompileClasspath,testRuntimeClassp
|
||||
io.opentelemetry:opentelemetry-semconv:1.26.0-alpha=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.outfoxx:swiftpoet:1.3.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.perfmark:perfmark-api:0.27.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-api:1.8.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-collectionschema:1.8.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-core:1.8.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-runtime:1.8.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta-regexp:jakarta-regexp:1.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.activation:jakarta.activation-api:2.1.4=jaxb
|
||||
jakarta.activation:jakarta.activation-api:2.2.0-M1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
@@ -456,6 +461,7 @@ org.apache.commons:commons-exec:1.3=testRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
|
||||
org.apache.commons:commons-lang3:3.20.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.8.1=checkstyle
|
||||
org.apache.commons:commons-pool2:2.12.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-text:1.15.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-text:1.3=checkstyle
|
||||
org.apache.ftpserver:ftplet-api:1.2.1=testCompileClasspath,testRuntimeClasspath
|
||||
@@ -563,7 +569,7 @@ org.jetbrains:annotations:17.0.0=compileClasspath,deploy_jar,nonprodCompileClass
|
||||
org.jline:jline:3.30.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.joda:joda-money:2.0.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.json:json:20230618=soy
|
||||
org.json:json:20240303=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.json:json:20251224=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jsoup:jsoup:1.22.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit-pioneer:junit-pioneer:2.3.0=testCompileClasspath,testRuntimeClasspath
|
||||
@@ -635,6 +641,8 @@ org.webjars.npm:viz.js-graphviz-java:2.1.3=testRuntimeClasspath
|
||||
org.xerial.snappy:snappy-java:1.1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.xmlresolver:xmlresolver:5.2.2=checkstyle
|
||||
org.yaml:snakeyaml:2.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
redis.clients.authentication:redis-authx-core:0.1.1-beta2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
redis.clients:jedis:7.4.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-core:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
|
||||
163
core/src/main/java/google/registry/cache/CacheModule.java
vendored
Normal file
163
core/src/main/java/google/registry/cache/CacheModule.java
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.CredentialModule.ApplicationDefaultCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import jakarta.inject.Singleton;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import redis.clients.jedis.DefaultJedisClientConfig;
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisClientConfig;
|
||||
import redis.clients.jedis.RedisClient;
|
||||
import redis.clients.jedis.RedisClusterClient;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
/** Dagger module to provide the {@link Jedis}-based cache for Valkey. */
|
||||
@Module
|
||||
public final class CacheModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public static Optional<UnifiedJedis> provideJedis(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("valkeyHostsAndPorts") Optional<ImmutableList<String>> valkeyHostsAndPorts,
|
||||
@Config("valkeySslSocketFactory") SSLSocketFactory valkeySslSocketFactory) {
|
||||
if (valkeyHostsAndPorts.map(ImmutableList::isEmpty).orElse(true)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
ImmutableSet<HostAndPort> hostsAndPorts =
|
||||
valkeyHostsAndPorts.get().stream().map(HostAndPort::from).collect(toImmutableSet());
|
||||
JedisClientConfig clientConfig =
|
||||
DefaultJedisClientConfig.builder()
|
||||
.ssl(true)
|
||||
.sslSocketFactory(valkeySslSocketFactory)
|
||||
.credentialsProvider(new ValkeyCredentialsProvider(credentialsBundle))
|
||||
.build();
|
||||
if (hostsAndPorts.size() > 1) {
|
||||
return Optional.of(
|
||||
RedisClusterClient.builder().clientConfig(clientConfig).nodes(hostsAndPorts).build());
|
||||
}
|
||||
return Optional.of(
|
||||
RedisClient.builder()
|
||||
.clientConfig(clientConfig)
|
||||
.hostAndPort(Iterables.getOnlyElement(hostsAndPorts))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public static DomainCache provideDomainCache(Optional<UnifiedJedis> jedis, Clock clock) {
|
||||
if (jedis.isEmpty()) {
|
||||
return domainName ->
|
||||
ForeignKeyUtils.loadResourceByCache(Domain.class, domainName, clock.now());
|
||||
}
|
||||
SimplifiedJedisClient<Domain> jedisClient =
|
||||
SimplifiedJedisClient.create(Domain.class, jedis.get());
|
||||
return new MultilayerDomainCache(jedisClient, clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public static HostCache provideHostCache(Optional<UnifiedJedis> jedis) {
|
||||
if (jedis.isEmpty()) {
|
||||
return repoId ->
|
||||
Optional.ofNullable(EppResource.loadByCache(VKey.create(Host.class, repoId)));
|
||||
}
|
||||
SimplifiedJedisClient<Host> jedisClient = SimplifiedJedisClient.create(Host.class, jedis.get());
|
||||
return new MultilayerHostCache(jedisClient);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Config("valkeySslSocketFactory")
|
||||
static SSLSocketFactory provideValkeySslSocketFactory(
|
||||
@Config("valkeyCertificateAuthority") String valkeyCertificateAuthority) {
|
||||
try {
|
||||
ImmutableList<X509Certificate> trustedCerts =
|
||||
CertificateFactory.getInstance("X.509")
|
||||
.generateCertificates(
|
||||
new ByteArrayInputStream(
|
||||
valkeyCertificateAuthority.getBytes(StandardCharsets.UTF_8)))
|
||||
.stream()
|
||||
.map(X509Certificate.class::cast)
|
||||
.collect(toImmutableList());
|
||||
|
||||
// This is a roundabout way to trust the Cloud Memorystore-issued certificate authority even
|
||||
// though it's not a root cert (it's an intermediate cert).
|
||||
TrustManager x509TrustManager =
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return trustedCerts.toArray(new X509Certificate[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType)
|
||||
throws CertificateException {
|
||||
Exception lastException = null;
|
||||
for (X509Certificate cert : certs) {
|
||||
for (X509Certificate trustedCert : trustedCerts) {
|
||||
try {
|
||||
cert.verify(trustedCert.getPublicKey());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// Verification failed, try the next one
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new CertificateException(
|
||||
"None of the server certificates were signed by the provided CA", lastException);
|
||||
}
|
||||
};
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, new TrustManager[] {x509TrustManager}, null);
|
||||
return sslContext.getSocketFactory();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException("Could not create X.509 certificate from provided PEM", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
core/src/main/java/google/registry/cache/DomainCache.java
vendored
Normal file
23
core/src/main/java/google/registry/cache/DomainCache.java
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import google.registry.model.domain.Domain;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Interface for some type of cache that loads {@link Domain}s by domain name. */
|
||||
public interface DomainCache {
|
||||
Optional<Domain> loadByDomainName(String domainName);
|
||||
}
|
||||
23
core/src/main/java/google/registry/cache/HostCache.java
vendored
Normal file
23
core/src/main/java/google/registry/cache/HostCache.java
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import google.registry.model.host.Host;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Interface for some type of cache that loads {@link Host}s by repo ID. */
|
||||
public interface HostCache {
|
||||
Optional<Host> loadByRepoId(String repoId);
|
||||
}
|
||||
62
core/src/main/java/google/registry/cache/MultilayerDomainCache.java
vendored
Normal file
62
core/src/main/java/google/registry/cache/MultilayerDomainCache.java
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.util.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A multi-layer cache for {@link Domain} objects.
|
||||
*
|
||||
* <p>It uses a local Caffeine cache, a remote Jedis cache, and finally the database.
|
||||
*/
|
||||
public class MultilayerDomainCache extends MultilayerEppResourceCache<Domain>
|
||||
implements DomainCache {
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
public MultilayerDomainCache(SimplifiedJedisClient<Domain> jedisClient, Clock clock) {
|
||||
super(jedisClient);
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Domain> loadByDomainName(String domainName) {
|
||||
return loadFromCaches(domainName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Domain> loadFromDatabase(String domainName) {
|
||||
// Don't use the cache (avoid caching the same domain twice). Do use the replica SQL instance.
|
||||
Optional<Domain> possibleDomain =
|
||||
Optional.ofNullable(
|
||||
ForeignKeyUtils.loadMostRecentResourceObjects(
|
||||
Domain.class, ImmutableList.of(domainName), true)
|
||||
.get(domainName));
|
||||
Instant now = clock.now();
|
||||
return possibleDomain
|
||||
.filter(domain -> now.isBefore(domain.getDeletionTime()))
|
||||
.map(domain -> domain.cloneProjectedAtInstant(now));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJedisPrefix() {
|
||||
return "Domain__";
|
||||
}
|
||||
}
|
||||
73
core/src/main/java/google/registry/cache/MultilayerEppResourceCache.java
vendored
Normal file
73
core/src/main/java/google/registry/cache/MultilayerEppResourceCache.java
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.EppResource;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A multi-layer cache for {@link EppResource}s.
|
||||
*
|
||||
* <p>It uses a local Caffeine cache, a remote Jedis cache, and finally the database.
|
||||
*/
|
||||
public abstract class MultilayerEppResourceCache<V extends EppResource> {
|
||||
|
||||
// Don't use a loading cache; it'd complicate the nesting
|
||||
private final Cache<String, V> localCache =
|
||||
Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofHours(1))
|
||||
.maximumSize(RegistryConfig.getEppResourceMaxCachedEntries())
|
||||
.build();
|
||||
|
||||
private final SimplifiedJedisClient<V> jedisClient;
|
||||
|
||||
protected MultilayerEppResourceCache(SimplifiedJedisClient<V> jedisClient) {
|
||||
this.jedisClient = jedisClient;
|
||||
}
|
||||
|
||||
protected abstract Optional<V> loadFromDatabase(String key);
|
||||
|
||||
protected abstract String getJedisPrefix();
|
||||
|
||||
protected Optional<V> loadFromCaches(String key) {
|
||||
// hopefully the resource is in the local cache
|
||||
Optional<V> possibleValue = Optional.ofNullable(localCache.getIfPresent(key));
|
||||
if (possibleValue.isPresent()) {
|
||||
return possibleValue;
|
||||
}
|
||||
|
||||
// if not, try the remote cache
|
||||
String jedisKey = getJedisPrefix() + key;
|
||||
possibleValue = jedisClient.get(jedisKey);
|
||||
if (possibleValue.isPresent()) {
|
||||
localCache.put(key, possibleValue.get());
|
||||
return possibleValue;
|
||||
}
|
||||
|
||||
// lastly, try the DB
|
||||
return loadFromDatabase(key)
|
||||
.map(
|
||||
v -> {
|
||||
// Optional has no direct "peek" functionality to fill the caches
|
||||
jedisClient.set(jedisKey, v);
|
||||
localCache.put(key, v);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
}
|
||||
49
core/src/main/java/google/registry/cache/MultilayerHostCache.java
vendored
Normal file
49
core/src/main/java/google/registry/cache/MultilayerHostCache.java
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A multi-layer cache for {@link Host} objects.
|
||||
*
|
||||
* <p>It uses a local Caffeine cache, a remote Jedis cache, and finally the database.
|
||||
*/
|
||||
public class MultilayerHostCache extends MultilayerEppResourceCache<Host> implements HostCache {
|
||||
|
||||
public MultilayerHostCache(SimplifiedJedisClient<Host> jedisClient) {
|
||||
super(jedisClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Host> loadByRepoId(String repoId) {
|
||||
return loadFromCaches(repoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Host> loadFromDatabase(String repoId) {
|
||||
return replicaTm()
|
||||
.transact(() -> replicaTm().loadByKeyIfPresent(VKey.create(Host.class, repoId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJedisPrefix() {
|
||||
return "Host__";
|
||||
}
|
||||
}
|
||||
80
core/src/main/java/google/registry/cache/SimplifiedJedisClient.java
vendored
Normal file
80
core/src/main/java/google/registry/cache/SimplifiedJedisClient.java
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import google.registry.model.EppResource;
|
||||
import io.protostuff.LinkedBuffer;
|
||||
import io.protostuff.ProtostuffIOUtil;
|
||||
import io.protostuff.Schema;
|
||||
import io.protostuff.runtime.RuntimeSchema;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
/**
|
||||
* A {@link UnifiedJedis} client that handles serialization/deserialization.
|
||||
*
|
||||
* <p>We use protobufs for serialization to handle the immutable collections that our objects use.
|
||||
*
|
||||
* <p>{@link UnifiedJedis} pairs key-value types, so we need the key to be serialized to a byte
|
||||
* array as well.
|
||||
*/
|
||||
public class SimplifiedJedisClient<V extends EppResource> {
|
||||
|
||||
private final Schema<V> valueSchema;
|
||||
private final UnifiedJedis jedis;
|
||||
|
||||
public static <V extends EppResource> SimplifiedJedisClient<V> create(
|
||||
Class<V> valueClass, UnifiedJedis jedis) {
|
||||
Schema<V> valueSchema = RuntimeSchema.getSchema(valueClass);
|
||||
return new SimplifiedJedisClient<>(valueSchema, jedis);
|
||||
}
|
||||
|
||||
private SimplifiedJedisClient(Schema<V> valueSchema, UnifiedJedis jedis) {
|
||||
this.valueSchema = valueSchema;
|
||||
this.jedis = jedis;
|
||||
}
|
||||
|
||||
/** Gets the value from the remote cache. Returns null if it does not exist. */
|
||||
public Optional<V> get(String key) {
|
||||
checkNotNull(key, "Key cannot be null");
|
||||
byte[] data = jedis.get(key.getBytes(StandardCharsets.UTF_8));
|
||||
return Optional.ofNullable(data).map(this::deserialize);
|
||||
}
|
||||
|
||||
/** Sets the value in the remote cache. */
|
||||
public void set(String key, V value) {
|
||||
checkNotNull(key, "Key cannot be null");
|
||||
checkNotNull(value, "Value cannot be null");
|
||||
jedis.set(key.getBytes(StandardCharsets.UTF_8), serialize(value));
|
||||
}
|
||||
|
||||
private byte[] serialize(V value) {
|
||||
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
|
||||
try {
|
||||
return ProtostuffIOUtil.toByteArray(value, valueSchema, buffer);
|
||||
} finally {
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private V deserialize(byte[] data) {
|
||||
V value = valueSchema.newMessage();
|
||||
ProtostuffIOUtil.mergeFrom(data, value, valueSchema);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
46
core/src/main/java/google/registry/cache/ValkeyCredentialsProvider.java
vendored
Normal file
46
core/src/main/java/google/registry/cache/ValkeyCredentialsProvider.java
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
import redis.clients.jedis.DefaultRedisCredentials;
|
||||
import redis.clients.jedis.RedisCredentials;
|
||||
|
||||
public class ValkeyCredentialsProvider implements Supplier<RedisCredentials> {
|
||||
|
||||
private static final String MEMORYSTORE_AUTH_SCOPE =
|
||||
"https://www.googleapis.com/auth/cloud-platform";
|
||||
|
||||
private final GoogleCredentials credentials;
|
||||
|
||||
public ValkeyCredentialsProvider(GoogleCredentialsBundle credentialsBundle) {
|
||||
this.credentials =
|
||||
credentialsBundle.getGoogleCredentials().createScoped(MEMORYSTORE_AUTH_SCOPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RedisCredentials get() {
|
||||
try {
|
||||
credentials.refreshIfExpired();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to refresh IAM token for Memorystore", e);
|
||||
}
|
||||
String token = credentials.getAccessToken().getTokenValue();
|
||||
return new DefaultRedisCredentials(null, token);
|
||||
}
|
||||
}
|
||||
@@ -1463,6 +1463,14 @@ public final class RegistryConfig {
|
||||
return config.mosapi.tldThreadCount;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("valkeyHostsAndPorts")
|
||||
public static Optional<ImmutableList<String>> provideValkeyHostsAndPorts(
|
||||
RegistryConfigSettings config) {
|
||||
return Optional.ofNullable(config.valkey)
|
||||
.map(valkey -> ImmutableList.copyOf(valkey.hostsAndPorts));
|
||||
}
|
||||
|
||||
private static String formatComments(String text) {
|
||||
return Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(text).stream()
|
||||
.map(s -> "# " + s)
|
||||
|
||||
@@ -43,6 +43,7 @@ public class RegistryConfigSettings {
|
||||
public BulkPricingPackageMonitoring bulkPricingPackageMonitoring;
|
||||
public Bsa bsa;
|
||||
public MosApi mosapi;
|
||||
public Valkey valkey;
|
||||
|
||||
/** Configuration options that apply to the entire GCP project. */
|
||||
public static class GcpProject {
|
||||
@@ -267,4 +268,9 @@ public class RegistryConfigSettings {
|
||||
public List<String> services;
|
||||
public int tldThreadCount;
|
||||
}
|
||||
|
||||
/** Configuration for Valkey caching. */
|
||||
public static class Valkey {
|
||||
public List<String> hostsAndPorts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,3 +639,7 @@ mosapi:
|
||||
# ICANN MoSAPI Specification, Section 12.3</a>
|
||||
tldThreadCount: 4
|
||||
|
||||
valkey:
|
||||
# Optional: hosts and ports for remote Valkey caching, e.g.
|
||||
# - "127.0.0.1:6379"
|
||||
hostsAndPorts: []
|
||||
|
||||
@@ -52,4 +52,10 @@ public abstract class KeyringModule {
|
||||
int lastColonIndex = instanceConnectionName.lastIndexOf(':');
|
||||
return instanceConnectionName.substring(lastColonIndex + 1);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("valkeyCertificateAuthority")
|
||||
public static String provideValkeyCertificateAuthority(Keyring keyring) {
|
||||
return keyring.getValkeyCertificateAuthority();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,8 @@ public interface Keyring extends AutoCloseable {
|
||||
/** Returns the Cloud SQL connection names of the replica database instances. */
|
||||
ImmutableList<String> getSqlReplicaConnectionNames();
|
||||
|
||||
String getValkeyCertificateAuthority();
|
||||
|
||||
// Don't throw so try-with-resources works better.
|
||||
@Override
|
||||
void close();
|
||||
|
||||
@@ -69,7 +69,8 @@ public class SecretManagerKeyring implements Keyring {
|
||||
SAFE_BROWSING_API_KEY,
|
||||
SQL_PRIMARY_CONN_NAME,
|
||||
SQL_REPLICA_CONN_NAME,
|
||||
SQL_REPLICA_CONN_NAMES;
|
||||
SQL_REPLICA_CONN_NAMES,
|
||||
VALKEY_CERTIFICATE_AUTHORITY;
|
||||
|
||||
String getLabel() {
|
||||
return UPPER_UNDERSCORE.to(LOWER_HYPHEN, name());
|
||||
@@ -181,6 +182,16 @@ public class SecretManagerKeyring implements Keyring {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValkeyCertificateAuthority() {
|
||||
try {
|
||||
return getString(StringKeyLabel.VALKEY_CERTIFICATE_AUTHORITY);
|
||||
} catch (KeyringException e) {
|
||||
// this is optional
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** No persistent resources are maintained for this Keyring implementation. */
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
@@ -225,7 +225,7 @@ public final class ForeignKeyUtils {
|
||||
}
|
||||
|
||||
/** Method to load the most recent {@link EppResource}s for the given foreign keys. */
|
||||
private static <E extends EppResource> ImmutableMap<String, E> loadMostRecentResourceObjects(
|
||||
public static <E extends EppResource> ImmutableMap<String, E> loadMostRecentResourceObjects(
|
||||
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaTm) {
|
||||
String fkProperty = RESOURCE_TYPE_TO_FK_PROPERTY.get(clazz);
|
||||
JpaTransactionManager tmToUse = useReplicaTm ? replicaTm() : tm();
|
||||
|
||||
@@ -99,9 +99,7 @@ public class EppXmlTransformer {
|
||||
}
|
||||
return eppOutput.getResponse().getExtensions().stream()
|
||||
.map(EppResponse.ResponseExtension::getClass)
|
||||
.filter(EppXmlTransformer::isFeeExtension)
|
||||
.findAny()
|
||||
.isPresent();
|
||||
.anyMatch(EppXmlTransformer::isFeeExtension);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -21,6 +21,7 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.batch.BatchModule;
|
||||
import google.registry.bigquery.BigqueryModule;
|
||||
import google.registry.cache.CacheModule;
|
||||
import google.registry.config.CloudTasksUtilsModule;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
@@ -61,6 +62,7 @@ import jakarta.inject.Singleton;
|
||||
AuthModule.class,
|
||||
BatchModule.class,
|
||||
BigqueryModule.class,
|
||||
CacheModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
ConfigModule.class,
|
||||
CredentialModule.class,
|
||||
|
||||
80
core/src/test/java/google/registry/cache/MultilayerDomainCacheTest.java
vendored
Normal file
80
core/src/test/java/google/registry/cache/MultilayerDomainCacheTest.java
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Tests for {@link MultilayerDomainCache}. */
|
||||
public class MultilayerDomainCacheTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private final SimplifiedJedisClient<Domain> jedisClient = mock(SimplifiedJedisClient.class);
|
||||
private final FakeClock clock = new FakeClock();
|
||||
private MultilayerDomainCache cache;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
cache = new MultilayerDomainCache(jedisClient, clock);
|
||||
createTld("tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_fromDatabase_populatesCaches() {
|
||||
Domain domain = persistActiveDomain("example.tld");
|
||||
assertThat(cache.loadByDomainName("example.tld")).hasValue(domain);
|
||||
|
||||
// We should have filled the caches after one attempt to load from Valkey
|
||||
verify(jedisClient).get("Domain__example.tld");
|
||||
verify(jedisClient).set("Domain__example.tld", domain);
|
||||
|
||||
// Further loads hit the local cache
|
||||
assertThat(cache.loadByDomainName("example.tld")).hasValue(domain);
|
||||
verifyNoMoreInteractions(jedisClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_fromValkey() {
|
||||
// Note: we don't save the domain to SQL
|
||||
Domain domain = DatabaseHelper.newDomain("example.tld");
|
||||
// We hit the Valkey cache first
|
||||
when(jedisClient.get(eq("Domain__example.tld"))).thenReturn(Optional.of(domain));
|
||||
assertThat(cache.loadByDomainName("example.tld")).hasValue(domain);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_missing() {
|
||||
assertThat(cache.loadByDomainName("nonexistent.tld")).isEmpty();
|
||||
}
|
||||
}
|
||||
76
core/src/test/java/google/registry/cache/MultilayerHostCacheTest.java
vendored
Normal file
76
core/src/test/java/google/registry/cache/MultilayerHostCacheTest.java
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
/** Tests for {@link MultilayerHostCache}. */
|
||||
public class MultilayerHostCacheTest {
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private final SimplifiedJedisClient<Host> jedisClient = mock(SimplifiedJedisClient.class);
|
||||
private MultilayerHostCache cache;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
cache = new MultilayerHostCache(jedisClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_fromDatabase_populatesCaches() {
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
assertThat(cache.loadByRepoId(host.getRepoId())).hasValue(host);
|
||||
|
||||
// We should have filled the caches after one attempt to load from Valkey
|
||||
verify(jedisClient).get("Host__" + host.getRepoId());
|
||||
verify(jedisClient).set("Host__" + host.getRepoId(), host);
|
||||
|
||||
// Further loads hit the local cache
|
||||
assertThat(cache.loadByRepoId(host.getRepoId())).hasValue(host);
|
||||
verifyNoMoreInteractions(jedisClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_fromValkey() {
|
||||
// Note: we don't save the host to SQL
|
||||
Host host = DatabaseHelper.newHost("ns1.example.tld");
|
||||
// We hit the Valkey cache first
|
||||
when(jedisClient.get(eq("Host__" + host.getRepoId()))).thenReturn(Optional.of(host));
|
||||
assertThat(cache.loadByRepoId(host.getRepoId())).hasValue(host);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_missing() {
|
||||
assertThat(cache.loadByRepoId("nonexistent")).isEmpty();
|
||||
}
|
||||
}
|
||||
93
core/src/test/java/google/registry/cache/SimplifiedJedisClientTest.java
vendored
Normal file
93
core/src/test/java/google/registry/cache/SimplifiedJedisClientTest.java
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.cache;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.FakeClock;
|
||||
import io.github.ss_bhatt.testcontainers.valkey.ValkeyContainer;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.RedisClient;
|
||||
import redis.clients.jedis.UnifiedJedis;
|
||||
|
||||
/** Tests for {@link SimplifiedJedisClient}. */
|
||||
@Testcontainers
|
||||
public class SimplifiedJedisClientTest {
|
||||
|
||||
@Container private static final ValkeyContainer valkey = new ValkeyContainer();
|
||||
|
||||
private final FakeClock fakeClock = new FakeClock(DateTime.parse("2025-01-01T00:00:00.000Z"));
|
||||
|
||||
@RegisterExtension
|
||||
final JpaIntegrationTestExtension jpa =
|
||||
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTld("tld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClient_roundTrip_domain() {
|
||||
Domain domain = persistActiveDomain("example.tld");
|
||||
SimplifiedJedisClient<Domain> client = createSimplifiedClient(Domain.class);
|
||||
client.set("Domain__example.tld", domain);
|
||||
// dsData and gracePeriods get serialized as null instead of the empty set, which is fine
|
||||
assertAboutImmutableObjects()
|
||||
.that(client.get("Domain__example.tld").get())
|
||||
.isEqualExceptFields(domain, "dsData", "gracePeriods");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClient_roundTrip_host() {
|
||||
Host host = persistActiveHost("ns1.example.tld");
|
||||
SimplifiedJedisClient<Host> client = createSimplifiedClient(Host.class);
|
||||
client.set("Host__ns1.example.tld", host);
|
||||
assertThat(client.get("Host__ns1.example.tld")).hasValue(host);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClient_nonexistent() {
|
||||
SimplifiedJedisClient<Domain> domainClient = createSimplifiedClient(Domain.class);
|
||||
SimplifiedJedisClient<Host> hostClient = createSimplifiedClient(Host.class);
|
||||
assertThat(domainClient.get("Domain__nonexistent.tld")).isEmpty();
|
||||
assertThat(hostClient.get("Host__ns1.nonexistent.tld")).isEmpty();
|
||||
}
|
||||
|
||||
private <T extends EppResource> SimplifiedJedisClient<T> createSimplifiedClient(Class<T> clazz) {
|
||||
return SimplifiedJedisClient.create(clazz, createJedisClient());
|
||||
}
|
||||
|
||||
private UnifiedJedis createJedisClient() {
|
||||
return RedisClient.builder()
|
||||
.hostAndPort(new HostAndPort(valkey.getHost(), valkey.getFirstMappedPort()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import dagger.Component;
|
||||
import dagger.Lazy;
|
||||
import google.registry.batch.BatchModule;
|
||||
import google.registry.bigquery.BigqueryModule;
|
||||
import google.registry.cache.CacheModule;
|
||||
import google.registry.config.CloudTasksUtilsModule;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
@@ -52,6 +53,7 @@ import jakarta.inject.Singleton;
|
||||
AuthModule.class,
|
||||
BatchModule.class,
|
||||
BigqueryModule.class,
|
||||
CacheModule.class,
|
||||
CloudTasksUtilsModule.class,
|
||||
ConfigModule.class,
|
||||
CredentialModule.class,
|
||||
|
||||
@@ -170,6 +170,12 @@ public final class FakeKeyringModule {
|
||||
return ImmutableList.of(SQL_REPLICA_CONNECTION_1, SQL_REPLICA_CONNECTION_2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValkeyCertificateAuthority() {
|
||||
// This isn't necessary for keyring testing
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
};
|
||||
|
||||
@@ -135,6 +135,9 @@ ext {
|
||||
'guru.nidi:graphviz-java-all-j2v8:[0.17.0,)',
|
||||
'io.github.classgraph:classgraph:[4.8.102,)',
|
||||
'io.github.java-diff-utils:java-diff-utils:[4.9,)',
|
||||
'io.github.ss-bhatt:testcontainers-valkey:1.0.0',
|
||||
'io.protostuff:protostuff-core:1.8.0',
|
||||
'io.protostuff:protostuff-runtime:1.8.0',
|
||||
'io.netty:netty-tcnative-boringssl-static:[2.0.36.Final,)',
|
||||
'jakarta.inject:jakarta.inject-api:[2.0.0,)',
|
||||
'jakarta.mail:jakarta.mail-api:[2.1.3,)',
|
||||
@@ -209,6 +212,7 @@ ext {
|
||||
'org.testcontainers:selenium:[1.19.6,)',
|
||||
'org.testcontainers:testcontainers:[1.19.6,)',
|
||||
'org.yaml:snakeyaml:[1.17,)',
|
||||
'redis.clients:jedis:7.4.1',
|
||||
'us.fatehi:schemacrawler-api:[16.10.1,)',
|
||||
'us.fatehi:schemacrawler-diagram:[16.10.1,)',
|
||||
'us.fatehi:schemacrawler-postgresql:[16.10.1,)',
|
||||
|
||||
@@ -121,7 +121,7 @@ com.google.cloud:grpc-gcp:1.6.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:libraries-bom:26.48.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.26.5=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.code.findbugs:jsr305:3.0.2=checkstyle,deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.code.gson:gson:2.12.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.code.gson:gson:2.13.2=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.common.html.types:types:1.0.8=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.dagger:dagger:2.59.2=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
com.google.errorprone:error_prone_annotation:2.48.0=annotationProcessor,testAnnotationProcessor
|
||||
@@ -262,6 +262,10 @@ io.opentelemetry:opentelemetry-sdk:1.47.0=deploy_jar,runtimeClasspath,testRuntim
|
||||
io.opentelemetry:opentelemetry-semconv:1.26.0-alpha=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
io.outfoxx:swiftpoet:1.3.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
io.perfmark:perfmark-api:0.27.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-api:1.8.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-collectionschema:1.8.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-core:1.8.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
io.protostuff:protostuff-runtime:1.8.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
jakarta-regexp:jakarta-regexp:1.4=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
jakarta.activation:jakarta.activation-api:2.2.0-M1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
@@ -310,6 +314,7 @@ org.apache.commons:commons-compress:1.28.0=deploy_jar,runtimeClasspath,testRunti
|
||||
org.apache.commons:commons-csv:1.14.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.18.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.8.1=checkstyle
|
||||
org.apache.commons:commons-pool2:2.12.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-text:1.3=checkstyle
|
||||
org.apache.httpcomponents.client5:httpclient5:5.1.3=checkstyle
|
||||
org.apache.httpcomponents.core5:httpcore5-h2:5.1.3=checkstyle
|
||||
@@ -375,7 +380,7 @@ org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1=deploy_jar,runtimeClasspa
|
||||
org.jetbrains:annotations:17.0.0=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.jline:jline:3.30.5=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.joda:joda-money:2.0.3=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.json:json:20240303=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.json:json:20251224=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.jsoup:jsoup:1.22.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,deploy_jar,runtimeClasspath,testAnnotationProcessor,testRuntimeClasspath
|
||||
org.ogce:xpp3:1.1.6=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
@@ -403,6 +408,8 @@ org.w3c.css:sac:1.3=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.xerial.snappy:snappy-java:1.1.10.4=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
org.xmlresolver:xmlresolver:5.2.2=checkstyle
|
||||
org.yaml:snakeyaml:2.4=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
redis.clients.authentication:redis-authx-core:0.1.1-beta2=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
redis.clients:jedis:7.4.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-core:3.1.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.1.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.1.1=deploy_jar,runtimeClasspath,testRuntimeClasspath
|
||||
|
||||
Reference in New Issue
Block a user