Skip to content

Commit

Permalink
make redis cache optional, generate redis truststore #EA-3597
Browse files Browse the repository at this point in the history
  • Loading branch information
gsergiu committed Nov 10, 2023
1 parent 12d1187 commit a3c8bbf
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 88 deletions.
12 changes: 11 additions & 1 deletion k8s/overlays/cloud/deployment_patch.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,14 @@ spec:
cpu: "${CPU_REQUEST}m"
limits:
memory: "${MEMORY_LIMIT}M"
cpu: "${CPU_LIMIT}m"
cpu: "${CPU_LIMIT}m"
volumeMounts:
- name: redis-certificate
mountPath: "/opt/app/config/translation-redis-truststore.jks"
readOnly: true
subPath: translation-redis-truststore.jks
volumes:
- name: translation-redis-truststore.jks
secret:
secretName: redis-secret

9 changes: 8 additions & 1 deletion k8s/overlays/cloud/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ patchesStrategicMerge:
- deployment_patch.yaml

commonLabels:
app: translation-api
app: translation-api


# expects files to be in the same directory
secretGenerator:
- name: redis-secret
files:
- translation-redis-truststore.jks
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,5 @@ public class TranslationAppConstants {
public static final String BUILD_INFO = "build";
public static final String APP_INFO = "app";
public static final String CONFIG_INFO = "config";

public static final String RUNTIME_ENV_TEST = "test";

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
Expand All @@ -24,7 +25,9 @@
MongoMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
SolrAutoConfiguration.class,
// DataSources are manually configured (for EM and batch DBs)
DataSourceAutoConfiguration.class})
DataSourceAutoConfiguration.class,
//redis configured by application to allow disabling it
RedisAutoConfiguration.class})
public class TranslationApp{

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@ public interface BeanNames {
String BEAN_SERVICE_PROVIDER = "translationServiceProvider";
String BEAN_SERVICE_CONFIG_INFO_CONTRIBUTOR =
"translationServiceConfigInfoContributor";
String BEAN_REDIS_CACHE_TEMPLATE = "redisTemplate";
String BEAN_REDIS_CACHE_LETTUCE_CONNECTION_FACTORY = "lettuceConnectionFactory";
String BEAN_REDIS_CACHE_SERVICE = "redisCacheService";
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
Expand All @@ -28,7 +29,6 @@
import eu.europeana.api.commons.config.i18n.I18nService;
import eu.europeana.api.commons.config.i18n.I18nServiceImpl;
import eu.europeana.api.commons.oauth2.service.impl.EuropeanaClientDetailsService;
import eu.europeana.api.translation.definitions.vocabulary.TranslationAppConstants;
import eu.europeana.api.translation.model.RedisCacheTranslation;
import eu.europeana.api.translation.serialization.JsonRedisSerializer;
import eu.europeana.api.translation.service.exception.LangDetectionServiceConfigurationException;
Expand All @@ -42,12 +42,13 @@
import eu.europeana.api.translation.service.pangeanic.DummyPangTranslationService;
import eu.europeana.api.translation.service.pangeanic.PangeanicLangDetectService;
import eu.europeana.api.translation.service.pangeanic.PangeanicTranslationService;
import eu.europeana.api.translation.web.service.RedisCacheService;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.SslOptions;

@Configuration()
@PropertySource("classpath:translation.properties")
@PropertySource(value = "classpath:translation.user.properties", ignoreResourceNotFound = true)
@PropertySource(value = "translation.user.properties", ignoreResourceNotFound = true)
public class TranslationApiAutoconfig implements ApplicationListener<ApplicationStartedEvent> {

@Value("${translation.dummy.services:false}")
Expand Down Expand Up @@ -145,52 +146,58 @@ public TranslationServiceProvider getTranslationServiceProvider() {
this.translationServiceConfigProvider = new TranslationServiceProvider();
return this.translationServiceConfigProvider;
}

/*
* Help, see connect to a standalone redis server: https://medium.com/turkcell/making-first-connection-to-redis-with-java-application-spring-boot-4fc58e6fa173
* A separate connection factory bean is needed here because of the proper initialization, where some methods (e.g. afterPropertiesSet()) are
* called by spring after the bean creation. Otherwise all these methods would need to be called manually which is not the best solution.
*/
@Bean(BeanNames.BEAN_REDIS_CACHE_LETTUCE_CONNECTION_FACTORY)
public LettuceConnectionFactory redisStandAloneConnectionFactory() throws IOException {
//in case of integration tests, we do not need the SSL certificate
if(TranslationAppConstants.RUNTIME_ENV_TEST.equals(translationConfig.getRuntimeEnv())) {
return new LettuceConnectionFactory(LettuceConnectionFactory.createRedisConfiguration(translationConfig.getRedisConnectionUrl()));
}
else {
LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder = LettuceClientConfiguration.builder();
boolean sslEnabled=true;
if (sslEnabled){
SslOptions sslOptions = SslOptions.builder()
.jdkSslProvider()
.truststore(new File(translationConfig.getTruststorePath()), translationConfig.getTruststorePass())
.build();

ClientOptions clientOptions = ClientOptions
.builder()
.sslOptions(sslOptions)
.build();

lettuceClientConfigurationBuilder
.clientOptions(clientOptions)
.useSsl();
}

LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfigurationBuilder.build();

RedisConfiguration redisConf = LettuceConnectionFactory.createRedisConfiguration(translationConfig.getRedisConnectionUrl());
return new LettuceConnectionFactory(redisConf, lettuceClientConfiguration);
* Help, see connect to a standalone redis server:
* https://medium.com/turkcell/making-first-connection-to-redis-with-java-application-spring-boot-
* 4fc58e6fa173 A separate connection factory bean is needed here because of the proper
* initialization, where some methods (e.g. afterPropertiesSet()) are called by spring after the
* bean creation. Otherwise all these methods would need to be called manually which is not the
* best solution.
*/
private LettuceConnectionFactory getRedisConnectionFactory() throws IOException {
// in case of integration tests, we do not need the SSL certificate
LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder =
LettuceClientConfiguration.builder();
// if redis secure protocol is used (rediss vs. redis)
boolean sslEnabled = translationConfig.getRedisConnectionUrl().startsWith("rediss");
if (sslEnabled) {
SslOptions sslOptions = SslOptions.builder().jdkSslProvider()
.truststore(new File(translationConfig.getTruststorePath()),
translationConfig.getTruststorePass())
.build();

ClientOptions clientOptions = ClientOptions.builder().sslOptions(sslOptions).build();

lettuceClientConfigurationBuilder.clientOptions(clientOptions).useSsl();
}

LettuceClientConfiguration lettuceClientConfiguration =
lettuceClientConfigurationBuilder.build();

RedisConfiguration redisConf = LettuceConnectionFactory
.createRedisConfiguration(translationConfig.getRedisConnectionUrl());
return new LettuceConnectionFactory(redisConf, lettuceClientConfiguration);
}

@Bean(BeanNames.BEAN_REDIS_CACHE_TEMPLATE)
public RedisTemplate<String, RedisCacheTranslation> redisTemplateStandAlone(@Qualifier(BeanNames.BEAN_REDIS_CACHE_LETTUCE_CONNECTION_FACTORY)LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, RedisCacheTranslation> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;

private RedisTemplate<String, RedisCacheTranslation> getRedisTemplate(
LettuceConnectionFactory redisConnectionFactory) throws IOException {
RedisTemplate<String, RedisCacheTranslation> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}

@Bean(BeanNames.BEAN_REDIS_CACHE_SERVICE)
@ConditionalOnProperty(name = "redis.connection.url")
public RedisCacheService getRedisCacheService() throws IOException {
LettuceConnectionFactory redisConnectionFactory = getRedisConnectionFactory();
RedisTemplate<String, RedisCacheTranslation> redisTemplate =
getRedisTemplate(redisConnectionFactory);

return new RedisCacheService(redisTemplate);
}

@Override
Expand Down Expand Up @@ -223,9 +230,12 @@ private void loadServices(ApplicationStartedEvent event) {

/**
* Method for initialization of service provider using the service configurations
* @param ctx the application context holding the initialized beans
* @throws TranslationServiceConfigurationException if translations services cannot be correctly instantiated
* @throws LangDetectionServiceConfigurationException if language detection services cannot be correctly instantiated
*
* @param ctx the application context holding the initialized beans
* @throws TranslationServiceConfigurationException if translations services cannot be correctly
* instantiated
* @throws LangDetectionServiceConfigurationException if language detection services cannot be
* correctly instantiated
*/
public void initTranslationServices(ApplicationContext ctx)
throws TranslationServiceConfigurationException, LangDetectionServiceConfigurationException {
Expand All @@ -236,6 +246,7 @@ public void initTranslationServices(ApplicationContext ctx)

/**
* Method to verify required properties in translation config
*
* @param ctx the application context holding references to instantiated beans
*/
public void verifyMandatoryProperties(ApplicationContext ctx) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,15 @@ public class TranslationConfig{
@Value("${translation.google.usehttpclient: false}")
private boolean useGoogleHttpClient;

@Value("${redis.connection.url}")
@Value("${redis.connection.url:}")
private String redisConnectionUrl;

@Value("${truststore.path}")
@Value("${truststore.path:}")
private String truststorePath;

@Value("${truststore.password}")
@Value("${truststore.password:}")
private String truststorePass;

@Value("${translation.runtime}")
private String runtimeEnv;

public TranslationConfig() {
LOG.info("Initializing TranslConfigProperties bean.");
}
Expand Down Expand Up @@ -143,9 +140,5 @@ public String getTruststorePath() {
public String getTruststorePass() {
return truststorePass;
}

public String getRuntimeEnv() {
return runtimeEnv;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public class TranslationRequest {
private String service;
private String fallback;
private List<String> text;
private Boolean caching;
//caching enabled by default
private Boolean caching = Boolean.TRUE;

public TranslationRequest() {
super();
Expand Down Expand Up @@ -67,12 +68,7 @@ public void setText(List<String> text) {
}

public boolean useCaching() {
if(caching==null || caching.booleanValue()) {
return true;
}
else {
return false;
}
return caching;
}

public Boolean getCaching() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public ResponseEntity<String> translate(@RequestBody TranslationRequest translRe
verifyWriteAccess(Operations.CREATE, request);

validateRequest(translRequest);

TranslationResponse result = translationService.translate(translRequest);

String resultJson = serialize(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import eu.europeana.api.translation.config.BeanNames;
import eu.europeana.api.translation.model.RedisCacheTranslation;

@Service
public class RedisCacheService {

@Autowired
@Qualifier(BeanNames.BEAN_REDIS_CACHE_TEMPLATE)
private RedisTemplate<String, RedisCacheTranslation> redisTemplate;
private final RedisTemplate<String, RedisCacheTranslation> redisTemplate;


public RedisCacheService(RedisTemplate<String, RedisCacheTranslation> redisTemplate) {
this.redisTemplate = redisTemplate;
}

/**
* Returns a list of Objects (strings) that exist in the cache. If no cache is found for the given key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,25 @@ public class TranslationWebService extends BaseWebService {
@Autowired
private TranslationServiceProvider translationServiceProvider;

@Autowired
@Autowired(required = false)
private RedisCacheService redisCacheService;

private final Logger logger = LogManager.getLogger(getClass());

public TranslationResponse translate(TranslationRequest translationRequest) throws EuropeanaI18nApiException {
if(translationRequest.useCaching()) {
if(translationRequest.useCaching() && isCachingEnabled()) {
//only if the request requires caching and the caching service is enabled
return getCombinedCachedAndTranslatedResults(translationRequest);
}
else {
return getTranslatedResults(translationRequest);
}
}

private boolean isCachingEnabled() {
return redisCacheService != null;
}

private TranslationResponse getTranslatedResults(TranslationRequest translationRequest) throws EuropeanaI18nApiException {
LanguagePair languagePair =
new LanguagePair(translationRequest.getSource(), translationRequest.getTarget());
Expand Down
6 changes: 1 addition & 5 deletions translation-web/src/main/resources/translation.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,4 @@ auth.write.enabled=true
translation.google.usehttpclient=false

# For using dummy services
#use.dummy.services=true

truststore.path=/opt/app/config/myTrustStore.jks
truststore.password=truststorepass
translation.runtime=application
#use.dummy.services=true

0 comments on commit a3c8bbf

Please sign in to comment.