Skip to content

Commit 9051682

Browse files
committed
Merge pull request 'Refactor label and annotations containg metadata' (#22) from feature/cleanup_labels into develop
2 parents 8a11f5f + 5db2f7e commit 9051682

File tree

7 files changed

+170
-149
lines changed

7 files changed

+170
-149
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@
239239
<groupId>mysql</groupId>
240240
<artifactId>mysql-connector-java</artifactId>
241241
</dependency>
242-
243-
242+
243+
244244

245245
<!-- Kubernetes -->
246246
<dependency>

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

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

23-
import java.io.FileInputStream;
24-
import java.io.IOException;
25-
import java.io.OutputStream;
26-
import java.nio.file.Files;
27-
import java.nio.file.Paths;
28-
import java.util.ArrayList;
29-
import java.util.Arrays;
30-
import java.util.List;
31-
import java.util.Map;
32-
import java.util.Properties;
33-
import java.util.UUID;
34-
import java.util.function.BiConsumer;
35-
import java.util.regex.Matcher;
36-
import java.util.regex.Pattern;
37-
import java.util.stream.Collectors;
38-
39-
import javax.inject.Inject;
40-
41-
import org.apache.logging.log4j.LogManager;
42-
import org.apache.logging.log4j.Logger;
43-
import org.springframework.context.annotation.Lazy;
44-
import org.springframework.core.env.Environment;
45-
23+
import com.fasterxml.jackson.databind.MapperFeature;
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import com.fasterxml.jackson.databind.SerializationFeature;
26+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
27+
import com.google.common.base.Charsets;
28+
import eu.openanalytics.containerproxy.ContainerProxyApplication;
4629
import eu.openanalytics.containerproxy.ContainerProxyException;
4730
import eu.openanalytics.containerproxy.auth.IAuthenticationBackend;
4831
import eu.openanalytics.containerproxy.backend.strategy.IProxyTargetMappingStrategy;
@@ -54,6 +37,26 @@
5437
import eu.openanalytics.containerproxy.service.UserService;
5538
import eu.openanalytics.containerproxy.spec.expression.ExpressionAwareContainerSpec;
5639
import eu.openanalytics.containerproxy.spec.expression.SpecExpressionResolver;
40+
import org.apache.logging.log4j.LogManager;
41+
import org.apache.logging.log4j.Logger;
42+
import org.springframework.context.annotation.Lazy;
43+
import org.springframework.core.env.Environment;
44+
45+
import javax.inject.Inject;
46+
import java.io.File;
47+
import java.io.FileInputStream;
48+
import java.io.IOException;
49+
import java.io.OutputStream;
50+
import java.math.BigInteger;
51+
import java.nio.file.Files;
52+
import java.nio.file.Paths;
53+
import java.security.MessageDigest;
54+
import java.security.NoSuchAlgorithmException;
55+
import java.util.*;
56+
import java.util.function.BiConsumer;
57+
import java.util.regex.Matcher;
58+
import java.util.regex.Pattern;
59+
import java.util.stream.Collectors;
5760

5861
public abstract class AbstractContainerBackend implements IContainerBackend {
5962

@@ -70,9 +73,14 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
7073
protected static final String ENV_VAR_USER_GROUPS = "SHINYPROXY_USERGROUPS";
7174
protected static final String ENV_VAR_REALM_ID = "SHINYPROXY_REALM_ID";
7275

73-
protected static final String LABEL_PROXY_ID = "openanalytics.eu/sp-proxy-id";
74-
protected static final String LABEL_PROXY_SPEC_ID = "openanalytics.eu/sp-spec-id";
75-
protected static final String LABEL_STARTUP_TIMESTAMP = "openanalytics.eu/sp-proxy-startup-timestamp";
76+
protected static final String RUNTIME_LABEL_PROXY_ID = "openanalytics.eu/sp-proxy-id";
77+
protected static final String RUNTIME_LABEL_USER_ID = "openanalytics.eu/sp-user-id";
78+
protected static final String RUNTIME_LABEL_USER_GROUPS = "openanalytics.eu/sp-user-groups";
79+
protected static final String RUNTIME_LABEL_REALM_ID = "openanalytics.eu/sp-realm-id";
80+
protected static final String RUNTIME_LABEL_PROXY_SPEC_ID = "openanalytics.eu/sp-spec-id";
81+
protected static final String RUNTIME_LABEL_STARTUP_TIMESTAMP = "openanalytics.eu/sp-proxy-startup-timestamp";
82+
protected static final String RUNTIME_LABEL_PROXIED_APP = "openanalytics.eu/sp-proxied-app";
83+
protected static final String RUNTIME_LABEL_INSTANCE = "openanalytics.eu/sp-instance";
7684

7785
protected final Logger log = LogManager.getLogger(getClass());
7886

@@ -98,19 +106,31 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
98106
@Lazy
99107
// Note: lazy needed to work around early initialization conflict
100108
protected IAuthenticationBackend authBackend;
101-
109+
110+
protected String realmId;
111+
112+
protected String instanceId = null;
113+
102114
@Override
103115
public void initialize() throws ContainerProxyException {
104116
// If this application runs as a container itself, things like port publishing can be omitted.
105117
useInternalNetwork = Boolean.valueOf(getProperty(PROPERTY_INTERNAL_NETWORKING, "false"));
106118
privileged = Boolean.valueOf(getProperty(PROPERTY_PRIVILEGED, "false"));
119+
realmId = environment.getProperty("proxy.realm-id");
120+
try {
121+
instanceId = calculateInstanceId();
122+
log.info("Hash of config is: " + instanceId);
123+
} catch(Exception e) {
124+
throw new RuntimeException("Cannot compute hash of config", e);
125+
}
107126
}
108127

109128
@Override
110129
public void startProxy(Proxy proxy) throws ContainerProxyException {
111130
proxy.setId(UUID.randomUUID().toString());
112131
proxy.setStatus(ProxyStatus.Starting);
113-
132+
proxy.setStartupTimestamp(System.currentTimeMillis());
133+
114134
try {
115135
doStartProxy(proxy);
116136
} catch (Throwable t) {
@@ -123,7 +143,6 @@ public void startProxy(Proxy proxy) throws ContainerProxyException {
123143
throw new ContainerProxyException("Container did not respond in time");
124144
}
125145

126-
proxy.setStartupTimestamp(System.currentTimeMillis());
127146
proxy.setStatus(ProxyStatus.Up);
128147
}
129148

@@ -132,19 +151,23 @@ protected void doStartProxy(Proxy proxy) throws Exception {
132151
if (authBackend != null) authBackend.customizeContainer(spec);
133152

134153
// add labels need for App Recovery and maintenance
135-
spec.addLabel(LABEL_PROXY_ID, proxy.getId());
136-
spec.addLabel(LABEL_PROXY_SPEC_ID, proxy.getSpec().getId());
137-
spec.addLabel(LABEL_STARTUP_TIMESTAMP, String.valueOf(proxy.getStartupTimestamp()));
154+
spec.addRuntimeLabel(RUNTIME_LABEL_PROXIED_APP, true, "true");
155+
spec.addRuntimeLabel(RUNTIME_LABEL_INSTANCE, true, instanceId);
156+
157+
spec.addRuntimeLabel(RUNTIME_LABEL_PROXY_ID, false, proxy.getId());
158+
spec.addRuntimeLabel(RUNTIME_LABEL_PROXY_SPEC_ID, false, proxy.getSpec().getId());
159+
if (realmId != null) {
160+
spec.addRuntimeLabel(RUNTIME_LABEL_REALM_ID, false, realmId);
161+
}
162+
spec.addRuntimeLabel(RUNTIME_LABEL_USER_ID, false, proxy.getUserId());
163+
String[] groups = userService.getGroups(userService.getCurrentAuth());
164+
spec.addRuntimeLabel(RUNTIME_LABEL_USER_GROUPS, false, String.join(",", groups));
165+
spec.addRuntimeLabel(RUNTIME_LABEL_STARTUP_TIMESTAMP, false, String.valueOf(proxy.getStartupTimestamp()));
138166

139167
ExpressionAwareContainerSpec eSpec = new ExpressionAwareContainerSpec(spec, proxy, expressionResolver);
140168
Container c = startContainer(eSpec, proxy);
141169
c.setSpec(spec);
142-
143-
// remove labels needed for App Recovery since they do not really belong to the spec
144-
spec.removeLabel(LABEL_PROXY_ID);
145-
spec.removeLabel(LABEL_PROXY_SPEC_ID);
146-
spec.removeLabel(LABEL_STARTUP_TIMESTAMP);
147-
170+
148171
proxy.getContainers().add(c);
149172
}
150173
}
@@ -241,4 +264,40 @@ protected boolean isUseInternalNetwork() {
241264
protected boolean isPrivileged() {
242265
return privileged;
243266
}
267+
268+
269+
/**
270+
* Calculates a hash of the config file (i.e. application.yaml).
271+
*/
272+
private String calculateInstanceId() throws IOException, NoSuchAlgorithmException {
273+
/**
274+
* We need a hash of some "canonical" version of the config file.
275+
* The hash should not change when e.g. comments are added to the file.
276+
* Therefore we read the application.yml file into an Object and then
277+
* dump it again into YAML. We also sort the keys of maps and properties so that
278+
* the order does not matter for the resulting hash.
279+
*/
280+
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
281+
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
282+
objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
283+
284+
File file = Paths.get(ContainerProxyApplication.CONFIG_FILENAME).toFile();
285+
if (!file.exists()) {
286+
file = Paths.get(ContainerProxyApplication.CONFIG_DEMO_PROFILE).toFile();
287+
}
288+
if (!file.exists()) {
289+
// this should only happen in tests
290+
instanceId = "unknown-instance-id";
291+
return instanceId;
292+
}
293+
294+
Object parsedConfig = objectMapper.readValue(file, Object.class);
295+
String canonicalConfigFile = objectMapper.writeValueAsString(parsedConfig);
296+
297+
MessageDigest digest = MessageDigest.getInstance("SHA-1");
298+
digest.reset();
299+
digest.update(canonicalConfigFile.getBytes(Charsets.UTF_8));
300+
instanceId = String.format("%040x", new BigInteger(1, digest.digest()));
301+
return instanceId;
302+
}
244303
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,14 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
7272
Optional.ofNullable(spec.getDns()).ifPresent(dns -> hostConfigBuilder.dns(dns));
7373
Optional.ofNullable(spec.getVolumes()).ifPresent(v -> hostConfigBuilder.binds(v));
7474
hostConfigBuilder.privileged(isPrivileged() || spec.isPrivileged());
75-
75+
76+
Map<String, String> labels = spec.getLabels();
77+
spec.getRuntimeLabels().forEach((key, value) -> labels.put(key, value.getSecond()));
78+
7679
ContainerConfig containerConfig = ContainerConfig.builder()
7780
.hostConfig(hostConfigBuilder.build())
7881
.image(spec.getImage())
79-
.labels(spec.getLabels())
82+
.labels(labels)
8083
.exposedPorts(portBindings.keySet())
8184
.cmd(spec.getCmd())
8285
.env(buildEnv(spec, proxy))

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@
2222

2323
import java.net.URI;
2424
import java.net.URL;
25-
import java.util.ArrayList;
26-
import java.util.Arrays;
27-
import java.util.List;
28-
import java.util.Optional;
29-
import java.util.UUID;
25+
import java.util.*;
3026

3127
import com.spotify.docker.client.messages.mount.Mount;
3228
import com.spotify.docker.client.messages.swarm.DnsConfig;
@@ -67,11 +63,13 @@ protected Container startContainer(ContainerSpec spec, Proxy proxy) throws Excep
6763
.map(b -> b.split(":"))
6864
.map(fromTo -> Mount.builder().source(fromTo[0]).target(fromTo[1]).type("bind").build())
6965
.toArray(i -> new Mount[i]);
66+
Map<String, String> labels = spec.getLabels();
67+
spec.getRuntimeLabels().forEach((key, value) -> labels.put(key, value.getSecond()));
7068

7169
com.spotify.docker.client.messages.swarm.ContainerSpec containerSpec =
7270
com.spotify.docker.client.messages.swarm.ContainerSpec.builder()
7371
.image(spec.getImage())
74-
.labels(spec.getLabels())
72+
.labels(labels)
7573
.command(spec.getCmd())
7674
.env(buildEnv(spec, proxy))
7775
.dnsConfig(DnsConfig.builder().nameServers(spec.getDns()).build())

0 commit comments

Comments
 (0)