Skip to content

Commit 341ce28

Browse files
committed
Merge pull request 'Next generation metrics' (#52) from feature/25811_ng_metrics into develop
2 parents 4b780a4 + 70f6707 commit 341ce28

12 files changed

Lines changed: 788 additions & 75 deletions

src/main/java/eu/openanalytics/containerproxy/ContainerProxyApplication.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import eu.openanalytics.containerproxy.util.ProxyMappingManager;
2626
import io.undertow.Handlers;
2727
import io.undertow.servlet.api.ServletSessionConfig;
28+
import io.undertow.servlet.api.SessionManagerFactory;
2829
import org.apache.logging.log4j.LogManager;
2930
import org.apache.logging.log4j.Logger;
31+
import org.springframework.beans.factory.annotation.Autowired;
3032
import org.springframework.boot.SpringApplication;
3133
import org.springframework.boot.actuate.health.Health;
3234
import org.springframework.boot.actuate.health.HealthIndicator;
@@ -108,6 +110,9 @@ public void init() {
108110
defaultCookieSerializer.setSameSite(sameSiteCookie);
109111
}
110112

113+
@Autowired(required = false)
114+
private SessionManagerFactory sessionManagerFactory;
115+
111116
@Bean
112117
public UndertowServletWebServerFactory servletContainer() {
113118
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
@@ -123,6 +128,9 @@ public UndertowServletWebServerFactory servletContainer() {
123128
sessionConfig.setHttpOnly(true);
124129
sessionConfig.setSecure(Boolean.valueOf(environment.getProperty("server.secureCookies", "false")));
125130
info.setServletSessionConfig(sessionConfig);
131+
if (sessionManagerFactory != null) {
132+
info.setSessionManagerFactory(sessionManagerFactory);
133+
}
126134
});
127135
try {
128136
factory.setAddress(InetAddress.getByName(environment.getProperty("proxy.bind-address", "0.0.0.0")));
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2021 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
21+
package eu.openanalytics.containerproxy;
22+
23+
import eu.openanalytics.containerproxy.service.IdentifierService;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
27+
import org.springframework.boot.autoconfigure.session.RedisSessionProperties;
28+
import org.springframework.boot.autoconfigure.session.SessionProperties;
29+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.core.env.Environment;
33+
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
34+
import org.springframework.session.data.redis.config.ConfigureRedisAction;
35+
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
36+
37+
import javax.inject.Inject;
38+
import java.time.Duration;
39+
40+
@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis")
41+
@Configuration(proxyBeanMethods = false)
42+
@EnableConfigurationProperties(RedisSessionProperties.class)
43+
public class RedisSessionConfig extends RedisHttpSessionConfiguration {
44+
45+
private String redisNamespace;
46+
47+
@Inject
48+
private IdentifierService identifierService;
49+
50+
@Bean
51+
@ConditionalOnMissingBean
52+
public ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) {
53+
switch (redisSessionProperties.getConfigureAction()) {
54+
case NOTIFY_KEYSPACE_EVENTS:
55+
return new ConfigureNotifyKeyspaceEventsAction();
56+
case NONE:
57+
return ConfigureRedisAction.NO_OP;
58+
}
59+
throw new IllegalStateException(
60+
"Unsupported redis configure action '" + redisSessionProperties.getConfigureAction() + "'.");
61+
62+
}
63+
64+
@Autowired
65+
public void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties) {
66+
Duration timeout = sessionProperties.getTimeout();
67+
if (timeout != null) {
68+
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
69+
}
70+
setFlushMode(redisSessionProperties.getFlushMode());
71+
setSaveMode(redisSessionProperties.getSaveMode());
72+
setCleanupCron(redisSessionProperties.getCleanupCron());
73+
74+
if (identifierService.realmId != null) {
75+
redisNamespace = String.format("shinyproxy__%s__%s", identifierService.realmId, redisSessionProperties.getNamespace());
76+
} else {
77+
redisNamespace = String.format("shinyproxy__%s", redisSessionProperties.getNamespace());
78+
}
79+
80+
setRedisNamespace(redisNamespace);
81+
}
82+
83+
public String getNamespace() {
84+
return redisNamespace;
85+
}
86+
87+
}

src/main/java/eu/openanalytics/containerproxy/backend/AbstractContainerBackend.java

Lines changed: 6 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.UserGroupsKey;
4444
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.UserIdKey;
4545
import eu.openanalytics.containerproxy.model.spec.ContainerSpec;
46+
import eu.openanalytics.containerproxy.service.IdentifierService;
4647
import eu.openanalytics.containerproxy.service.UserService;
4748
import eu.openanalytics.containerproxy.spec.expression.ExpressionAwareContainerSpec;
4849
import eu.openanalytics.containerproxy.spec.expression.SpecExpressionResolver;
@@ -101,22 +102,14 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
101102
// Note: lazy needed to work around early initialization conflict
102103
protected IAuthenticationBackend authBackend;
103104

104-
protected String realmId;
105-
106-
protected String instanceId = null;
105+
@Inject
106+
protected IdentifierService identifierService;
107107

108108
@Override
109109
public void initialize() throws ContainerProxyException {
110110
// If this application runs as a container itself, things like port publishing can be omitted.
111111
useInternalNetwork = Boolean.valueOf(getProperty(PROPERTY_INTERNAL_NETWORKING, "false"));
112112
privileged = Boolean.valueOf(getProperty(PROPERTY_PRIVILEGED, "false"));
113-
realmId = environment.getProperty("proxy.realm-id");
114-
try {
115-
instanceId = calculateInstanceId();
116-
log.info("Hash of config is: " + instanceId);
117-
} catch(Exception e) {
118-
throw new RuntimeException("Cannot compute hash of config", e);
119-
}
120113
}
121114

122115
@Override
@@ -258,53 +251,6 @@ protected boolean isPrivileged() {
258251
return privileged;
259252
}
260253

261-
262-
private File getPathToConfigFile() {
263-
String path = environment.getProperty("spring.config.location");
264-
if (path != null) {
265-
return Paths.get(path).toFile();
266-
}
267-
268-
File file = Paths.get(ContainerProxyApplication.CONFIG_FILENAME).toFile();
269-
if (file.exists()) {
270-
return file;
271-
}
272-
273-
return null;
274-
}
275-
276-
/**
277-
* Calculates a hash of the config file (i.e. application.yaml).
278-
*/
279-
private String calculateInstanceId() throws IOException, NoSuchAlgorithmException {
280-
/**
281-
* We need a hash of some "canonical" version of the config file.
282-
* The hash should not change when e.g. comments are added to the file.
283-
* Therefore we read the application.yml file into an Object and then
284-
* dump it again into YAML. We also sort the keys of maps and properties so that
285-
* the order does not matter for the resulting hash.
286-
*/
287-
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
288-
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
289-
objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
290-
291-
File file = getPathToConfigFile();
292-
if (file == null) {
293-
// this should only happen in tests
294-
instanceId = "unknown-instance-id";
295-
return instanceId;
296-
}
297-
298-
Object parsedConfig = objectMapper.readValue(file, Object.class);
299-
String canonicalConfigFile = objectMapper.writeValueAsString(parsedConfig);
300-
301-
MessageDigest digest = MessageDigest.getInstance("SHA-1");
302-
digest.reset();
303-
digest.update(canonicalConfigFile.getBytes(Charsets.UTF_8));
304-
instanceId = String.format("%040x", new BigInteger(1, digest.digest()));
305-
return instanceId;
306-
}
307-
308254
/**
309255
* Computes the correct targetPath to use, to make the configuration of the targetPath easier.
310256
* - Removes any double slashes (can happen when using SpeL surrounded with static paths)
@@ -334,11 +280,11 @@ public static String computeTargetPath(String targetPath) {
334280
private void setRuntimeValues(Proxy proxy) {
335281
proxy.addRuntimeValue(new RuntimeValue(ProxiedAppKey.inst, "true"));
336282
proxy.addRuntimeValue(new RuntimeValue(ProxyIdKey.inst, proxy.getId()));
337-
proxy.addRuntimeValue(new RuntimeValue(InstanceIdKey.inst, instanceId));
283+
proxy.addRuntimeValue(new RuntimeValue(InstanceIdKey.inst, identifierService.instanceId));
338284
proxy.addRuntimeValue(new RuntimeValue(ProxySpecIdKey.inst, proxy.getSpec().getId()));
339285

340-
if (realmId != null) {
341-
proxy.addRuntimeValue(new RuntimeValue(RealmIdKey.inst, realmId));
286+
if (identifierService.realmId != null) {
287+
proxy.addRuntimeValue(new RuntimeValue(RealmIdKey.inst, identifierService.realmId));
342288
}
343289

344290
proxy.addRuntimeValue(new RuntimeValue(UserIdKey.inst, proxy.getUserId()));

src/main/java/eu/openanalytics/containerproxy/backend/docker/AbstractDockerBackend.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,6 @@
2020
*/
2121
package eu.openanalytics.containerproxy.backend.docker;
2222

23-
import java.io.IOException;
24-
import java.io.OutputStream;
25-
import java.net.URI;
26-
import java.nio.file.Paths;
27-
import java.util.HashMap;
28-
import java.util.ArrayList;
29-
import java.util.List;
30-
import java.util.Map;
31-
import java.util.function.BiConsumer;
32-
3323
import com.google.common.collect.ImmutableMap;
3424
import com.spotify.docker.client.DefaultDockerClient;
3525
import com.spotify.docker.client.DockerCertificates;
@@ -53,9 +43,9 @@
5343
import java.net.URI;
5444
import java.nio.file.Paths;
5545
import java.util.ArrayList;
46+
import java.util.HashMap;
5647
import java.util.List;
5748
import java.util.Map;
58-
import java.util.Optional;
5949
import java.util.function.BiConsumer;
6050

6151

@@ -161,7 +151,7 @@ protected Map<RuntimeValueKey<?>, RuntimeValue> parseLabelsAsRuntimeValues(Strin
161151
}
162152

163153
String containerInstanceId = labels.get(InstanceIdKey.inst.getKeyAsLabel());
164-
if (containerInstanceId == null || !containerInstanceId.equals(instanceId)) {
154+
if (containerInstanceId == null || !containerInstanceId.equals(identifierService.instanceId)) {
165155
log.warn("Ignoring container {} because instanceId {} is not correct", containerId, containerInstanceId);
166156
return null;
167157
}

src/main/java/eu/openanalytics/containerproxy/backend/kubernetes/KubernetesBackend.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ public List<ExistingContainerInfo> scanExistingContainers() {
508508
for (String namespace : namespaces) {
509509
List<Pod> pods = kubeClient.pods().inNamespace(namespace)
510510
.withLabel(ProxiedAppKey.inst.getKeyAsLabel(), "true")
511-
.withLabel(InstanceIdKey.inst.getKeyAsLabel(), instanceId)
511+
.withLabel(InstanceIdKey.inst.getKeyAsLabel(), identifierService.instanceId)
512512
.list().getItems();
513513

514514
for (Pod pod : pods) {
@@ -558,7 +558,7 @@ private Map<RuntimeValueKey<?>, RuntimeValue> parseLabelsAndAnnotationsAsRuntime
558558
Map<String, String> annotations) {
559559

560560
String containerInstanceId = labels.get(InstanceIdKey.inst.getKeyAsLabel());
561-
if (containerInstanceId == null || !containerInstanceId.equals(instanceId)) {
561+
if (containerInstanceId == null || !containerInstanceId.equals(identifierService.instanceId)) {
562562
log.warn("Ignoring container {} because instanceId {} is not correct", containerId, containerInstanceId);
563563
return null;
564564
}

0 commit comments

Comments
 (0)