Skip to content

Commit 4c549fe

Browse files
committed
Add IdentifierService
1 parent b53b991 commit 4c549fe

5 files changed

Lines changed: 155 additions & 68 deletions

File tree

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121
package eu.openanalytics.containerproxy;
2222

23+
import eu.openanalytics.containerproxy.service.IdentifierService;
2324
import org.springframework.beans.factory.annotation.Autowired;
2425
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -44,7 +45,7 @@ public class RedisSessionConfig extends RedisHttpSessionConfiguration {
4445
private String redisNamespace;
4546

4647
@Inject
47-
private Environment environment;
48+
private IdentifierService identifierService;
4849

4950
@Bean
5051
@ConditionalOnMissingBean
@@ -70,10 +71,8 @@ public void customize(SessionProperties sessionProperties, RedisSessionPropertie
7071
setSaveMode(redisSessionProperties.getSaveMode());
7172
setCleanupCron(redisSessionProperties.getCleanupCron());
7273

73-
String realmId = environment.getProperty("proxy.realm-id");
74-
75-
if (realmId != null) {
76-
redisNamespace = String.format("shinyproxy__%s__%s", realmId, redisSessionProperties.getNamespace());
74+
if (identifierService.realmId != null) {
75+
redisNamespace = String.format("shinyproxy__%s__%s", identifierService.realmId, redisSessionProperties.getNamespace());
7776
} else {
7877
redisNamespace = String.format("shinyproxy__%s", redisSessionProperties.getNamespace());
7978
}

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ protected Map<RuntimeValueKey<?>, RuntimeValue> parseLabelsAsRuntimeValues(Strin
151151
}
152152

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

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
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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.service;
22+
23+
24+
import com.fasterxml.jackson.databind.MapperFeature;
25+
import com.fasterxml.jackson.databind.ObjectMapper;
26+
import com.fasterxml.jackson.databind.SerializationFeature;
27+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
28+
import com.google.common.base.Charsets;
29+
import eu.openanalytics.containerproxy.ContainerProxyApplication;
30+
import org.apache.commons.lang.StringUtils;
31+
import org.apache.logging.log4j.LogManager;
32+
import org.apache.logging.log4j.Logger;
33+
import org.springframework.core.env.Environment;
34+
import org.springframework.stereotype.Service;
35+
36+
import javax.annotation.PostConstruct;
37+
import javax.inject.Inject;
38+
import java.io.File;
39+
import java.io.IOException;
40+
import java.math.BigInteger;
41+
import java.nio.file.Paths;
42+
import java.security.MessageDigest;
43+
import java.security.NoSuchAlgorithmException;
44+
import java.util.UUID;
45+
46+
/**
47+
* Very simple service that keeps track of the identifiers of this ShinyProxy instance + server.
48+
*/
49+
@Service
50+
public class IdentifierService {
51+
52+
/**
53+
* String that identifies this "run" or "server" of ShinyProxy.
54+
* This is unique every time ShinyProxy starts and independent of the InstanceId.
55+
*/
56+
public String runtimeId = null;
57+
58+
/**
59+
* String that identifies this "instance" of ShinyProxy Configuration.
60+
* This value is determined by the configuration (i.e. application.yml) of ShinyProxy.
61+
* It is not unique across multiple runs or multiple servers.
62+
* This value only changes when the configuration changes.
63+
*/
64+
public String instanceId = null;
65+
66+
/**
67+
* String identifying the realm ShinyProxy operatores in.
68+
*/
69+
public String realmId = null;
70+
71+
private final Logger logger = LogManager.getLogger(getClass());
72+
73+
@Inject
74+
private Environment environment;
75+
76+
@PostConstruct
77+
public void init() throws IOException, NoSuchAlgorithmException {
78+
String podName = environment.getProperty("SP_KUBE_POD_NAME");
79+
if (podName != null) {
80+
runtimeId = StringUtils.right(podName, 4);
81+
} else {
82+
runtimeId = UUID.randomUUID().toString();
83+
}
84+
85+
logger.info("ShinyProxy runtimeId: " + runtimeId);
86+
87+
instanceId = calculateInstanceId();
88+
logger.info("ShinyProxy instanceID (hash of config): " + instanceId);
89+
90+
realmId = environment.getProperty("proxy.realm-id");
91+
if (realmId != null) {
92+
logger.info("ShinyProxy realmId: " + realmId);
93+
}
94+
}
95+
96+
private File getPathToConfigFile() {
97+
String path = environment.getProperty("spring.config.location");
98+
if (path != null) {
99+
return Paths.get(path).toFile();
100+
}
101+
102+
File file = Paths.get(ContainerProxyApplication.CONFIG_FILENAME).toFile();
103+
if (file.exists()) {
104+
return file;
105+
}
106+
107+
return null;
108+
}
109+
110+
/**
111+
* Calculates a hash of the config file (i.e. application.yaml).
112+
*/
113+
private String calculateInstanceId() throws IOException, NoSuchAlgorithmException {
114+
/**
115+
* We need a hash of some "canonical" version of the config file.
116+
* The hash should not change when e.g. comments are added to the file.
117+
* Therefore we read the application.yml file into an Object and then
118+
* dump it again into YAML. We also sort the keys of maps and properties so that
119+
* the order does not matter for the resulting hash.
120+
*/
121+
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
122+
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
123+
objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
124+
125+
File file = getPathToConfigFile();
126+
if (file == null) {
127+
// this should only happen in tests
128+
instanceId = "unknown-instance-id";
129+
return instanceId;
130+
}
131+
132+
Object parsedConfig = objectMapper.readValue(file, Object.class);
133+
String canonicalConfigFile = objectMapper.writeValueAsString(parsedConfig);
134+
135+
MessageDigest digest = MessageDigest.getInstance("SHA-1");
136+
digest.reset();
137+
digest.update(canonicalConfigFile.getBytes(Charsets.UTF_8));
138+
instanceId = String.format("%040x", new BigInteger(1, digest.digest()));
139+
return instanceId;
140+
}
141+
142+
}

0 commit comments

Comments
 (0)