20
20
*/
21
21
package eu .openanalytics .containerproxy .backend ;
22
22
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 ;
46
29
import eu .openanalytics .containerproxy .ContainerProxyException ;
47
30
import eu .openanalytics .containerproxy .auth .IAuthenticationBackend ;
48
31
import eu .openanalytics .containerproxy .backend .strategy .IProxyTargetMappingStrategy ;
54
37
import eu .openanalytics .containerproxy .service .UserService ;
55
38
import eu .openanalytics .containerproxy .spec .expression .ExpressionAwareContainerSpec ;
56
39
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 ;
57
60
58
61
public abstract class AbstractContainerBackend implements IContainerBackend {
59
62
@@ -70,9 +73,14 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
70
73
protected static final String ENV_VAR_USER_GROUPS = "SHINYPROXY_USERGROUPS" ;
71
74
protected static final String ENV_VAR_REALM_ID = "SHINYPROXY_REALM_ID" ;
72
75
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" ;
76
84
77
85
protected final Logger log = LogManager .getLogger (getClass ());
78
86
@@ -98,19 +106,31 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
98
106
@ Lazy
99
107
// Note: lazy needed to work around early initialization conflict
100
108
protected IAuthenticationBackend authBackend ;
101
-
109
+
110
+ protected String realmId ;
111
+
112
+ protected String instanceId = null ;
113
+
102
114
@ Override
103
115
public void initialize () throws ContainerProxyException {
104
116
// If this application runs as a container itself, things like port publishing can be omitted.
105
117
useInternalNetwork = Boolean .valueOf (getProperty (PROPERTY_INTERNAL_NETWORKING , "false" ));
106
118
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
+ }
107
126
}
108
127
109
128
@ Override
110
129
public void startProxy (Proxy proxy ) throws ContainerProxyException {
111
130
proxy .setId (UUID .randomUUID ().toString ());
112
131
proxy .setStatus (ProxyStatus .Starting );
113
-
132
+ proxy .setStartupTimestamp (System .currentTimeMillis ());
133
+
114
134
try {
115
135
doStartProxy (proxy );
116
136
} catch (Throwable t ) {
@@ -123,7 +143,6 @@ public void startProxy(Proxy proxy) throws ContainerProxyException {
123
143
throw new ContainerProxyException ("Container did not respond in time" );
124
144
}
125
145
126
- proxy .setStartupTimestamp (System .currentTimeMillis ());
127
146
proxy .setStatus (ProxyStatus .Up );
128
147
}
129
148
@@ -132,19 +151,23 @@ protected void doStartProxy(Proxy proxy) throws Exception {
132
151
if (authBackend != null ) authBackend .customizeContainer (spec );
133
152
134
153
// 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 ()));
138
166
139
167
ExpressionAwareContainerSpec eSpec = new ExpressionAwareContainerSpec (spec , proxy , expressionResolver );
140
168
Container c = startContainer (eSpec , proxy );
141
169
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
+
148
171
proxy .getContainers ().add (c );
149
172
}
150
173
}
@@ -241,4 +264,40 @@ protected boolean isUseInternalNetwork() {
241
264
protected boolean isPrivileged () {
242
265
return privileged ;
243
266
}
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
+ }
244
303
}
0 commit comments