diff --git a/agent/pom.xml b/agent/pom.xml
index a1cff2027d..0d3c386718 100644
--- a/agent/pom.xml
+++ b/agent/pom.xml
@@ -129,6 +129,10 @@
com.fasterxml.jackson.datatype
jackson-datatype-jdk8
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
com.google.code.findbugs
jsr305
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/Agent.java b/agent/src/main/java/com/walmartlabs/concord/agent/Agent.java
index cbf69159d1..c2c1e5c04f 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/Agent.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/Agent.java
@@ -37,18 +37,13 @@
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
-@Named
-@Singleton
public class Agent {
private static final Logger log = LoggerFactory.getLogger(Agent.class);
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/AgentModule.java b/agent/src/main/java/com/walmartlabs/concord/agent/AgentModule.java
new file mode 100644
index 0000000000..376083f01f
--- /dev/null
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/AgentModule.java
@@ -0,0 +1,81 @@
+package com.walmartlabs.concord.agent;
+
+/*-
+ * *****
+ * Concord
+ * -----
+ * Copyright (C) 2017 - 2023 Walmart Inc.
+ * -----
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =====
+ */
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import com.typesafe.config.Config;
+import com.walmartlabs.concord.agent.cfg.*;
+import com.walmartlabs.concord.agent.executors.runner.DefaultDependencies;
+import com.walmartlabs.concord.agent.executors.runner.ProcessPool;
+import com.walmartlabs.concord.agent.remote.ApiClientFactory;
+import com.walmartlabs.concord.agent.remote.QueueClientProvider;
+import com.walmartlabs.concord.server.queueclient.QueueClient;
+import com.walmartlabs.ollie.config.ConfigurationProcessor;
+import com.walmartlabs.ollie.config.Environment;
+import com.walmartlabs.ollie.config.EnvironmentSelector;
+
+import javax.inject.Named;
+
+import static com.google.inject.Scopes.SINGLETON;
+
+@Named
+public class AgentModule implements Module {
+
+ private final Config config;
+
+ public AgentModule() {
+ this(loadDefaultConfig());
+ }
+
+ public AgentModule(Config config) {
+ this.config = config;
+ }
+
+ @Override
+ public void configure(Binder binder) {
+ binder.bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class);
+ binder.bind(Config.class).toInstance(config);
+
+ binder.bind(AgentConfiguration.class).in(SINGLETON);
+ binder.bind(DockerConfiguration.class).in(SINGLETON);
+ binder.bind(GitConfiguration.class).in(SINGLETON);
+ binder.bind(ImportConfiguration.class).in(SINGLETON);
+ binder.bind(PreForkConfiguration.class).in(SINGLETON);
+ binder.bind(RepositoryCacheConfiguration.class).in(SINGLETON);
+ binder.bind(RunnerV1Configuration.class).in(SINGLETON);
+ binder.bind(RunnerV2Configuration.class).in(SINGLETON);
+ binder.bind(ServerConfiguration.class).in(SINGLETON);
+
+ binder.bind(DefaultDependencies.class).in(SINGLETON);
+ binder.bind(ProcessPool.class).in(SINGLETON);
+ binder.bind(ApiClientFactory.class).in(SINGLETON);
+ binder.bind(QueueClient.class).toProvider(QueueClientProvider.class).in(SINGLETON);
+
+ binder.bind(Agent.class).in(SINGLETON);
+ }
+
+ private static Config loadDefaultConfig() {
+ Environment env = new EnvironmentSelector().select();
+ return new ConfigurationProcessor("concord-agent", env, null, null).process();
+ }
+}
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/ObjectMapperProvider.java b/agent/src/main/java/com/walmartlabs/concord/agent/ObjectMapperProvider.java
index 857544fc15..e54760d6c1 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/ObjectMapperProvider.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/ObjectMapperProvider.java
@@ -20,18 +20,34 @@
* =====
*/
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.guava.GuavaModule;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import javax.inject.Named;
import javax.inject.Provider;
-import javax.inject.Singleton;
-@Named
-@Singleton
public class ObjectMapperProvider implements Provider {
@Override
public ObjectMapper get() {
- return new ObjectMapper();
+ ObjectMapper mapper = new ObjectMapper()
+ .registerModule(new Jdk8Module())
+ .registerModule(new GuavaModule())
+ .registerModule(new JavaTimeModule());
+
+ // Write dates as ISO-8601
+ mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+ // Ignore unknown properties
+ mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+
+ // Ignore nulls
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+ return mapper;
}
}
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/AgentConfiguration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/AgentConfiguration.java
index eadd5478fc..4c5c99a486 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/AgentConfiguration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/AgentConfiguration.java
@@ -25,8 +25,6 @@
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
@@ -37,8 +35,6 @@
import static com.walmartlabs.concord.agent.cfg.Utils.getOrCreatePath;
import static com.walmartlabs.concord.agent.cfg.Utils.getStringOrDefault;
-@Named
-@Singleton
public class AgentConfiguration {
private static final Logger log = LoggerFactory.getLogger(AgentConfiguration.class);
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ConfigProvider.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ConfigProvider.java
deleted file mode 100644
index efa3b9947e..0000000000
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ConfigProvider.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.walmartlabs.concord.agent.cfg;
-
-/*-
- * *****
- * Concord
- * -----
- * Copyright (C) 2017 - 2019 Walmart Inc.
- * -----
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * =====
- */
-
-import com.typesafe.config.Config;
-import com.walmartlabs.ollie.config.ConfigurationProcessor;
-import com.walmartlabs.ollie.config.Environment;
-import com.walmartlabs.ollie.config.EnvironmentSelector;
-
-import javax.inject.Named;
-import javax.inject.Provider;
-import javax.inject.Singleton;
-
-@Named
-@Singleton
-public class ConfigProvider implements Provider {
-
- @Override
- public Config get() {
- Environment env = new EnvironmentSelector().select();
- return new ConfigurationProcessor("concord-agent", env).process();
- }
-}
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/DockerConfiguration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/DockerConfiguration.java
index 90fde7f7f9..da7bc9cca4 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/DockerConfiguration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/DockerConfiguration.java
@@ -23,13 +23,9 @@
import com.typesafe.config.Config;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.util.List;
import java.util.concurrent.TimeUnit;
-@Named
-@Singleton
public class DockerConfiguration {
private final String dockerHost;
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/GitConfiguration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/GitConfiguration.java
index 6dcce8e6e4..85bdcab673 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/GitConfiguration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/GitConfiguration.java
@@ -29,8 +29,6 @@
import static com.walmartlabs.concord.agent.cfg.Utils.getStringOrDefault;
-@Named
-@Singleton
public class GitConfiguration {
private final String token;
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ImportConfiguration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ImportConfiguration.java
index 7cde1f8ca7..f1f5756ba0 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ImportConfiguration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ImportConfiguration.java
@@ -23,14 +23,10 @@
import com.typesafe.config.Config;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-@Named
-@Singleton
public class ImportConfiguration {
private final Set disabledProcessors;
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/PreForkConfiguration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/PreForkConfiguration.java
index 68758b065c..de38930cee 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/PreForkConfiguration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/PreForkConfiguration.java
@@ -23,12 +23,8 @@
import com.typesafe.config.Config;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.util.concurrent.TimeUnit;
-@Named
-@Singleton
public class PreForkConfiguration {
private final boolean enabled;
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RepositoryCacheConfiguration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RepositoryCacheConfiguration.java
index 81ef969da0..c1454e31a1 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RepositoryCacheConfiguration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RepositoryCacheConfiguration.java
@@ -23,15 +23,11 @@
import com.typesafe.config.Config;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.nio.file.Path;
import java.time.Duration;
import static com.walmartlabs.concord.agent.cfg.Utils.getOrCreatePath;
-@Named
-@Singleton
public class RepositoryCacheConfiguration {
private final Path cacheDir;
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV1Configuration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV1Configuration.java
index 90246a638f..dc57172c37 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV1Configuration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV1Configuration.java
@@ -23,11 +23,7 @@
import com.typesafe.config.Config;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-@Named
-@Singleton
public class RunnerV1Configuration extends AbstractRunnerConfiguration {
@Inject
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV2Configuration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV2Configuration.java
index 1f12aee1a6..8128b7dd67 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV2Configuration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/RunnerV2Configuration.java
@@ -23,11 +23,7 @@
import com.typesafe.config.Config;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-@Named
-@Singleton
public class RunnerV2Configuration extends AbstractRunnerConfiguration {
@Inject
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ServerConfiguration.java b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ServerConfiguration.java
index eabe02609c..5e6bfb4523 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ServerConfiguration.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/cfg/ServerConfiguration.java
@@ -25,14 +25,10 @@
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.util.concurrent.TimeUnit;
import static com.walmartlabs.concord.agent.cfg.Utils.getStringOrDefault;
-@Named
-@Singleton
public class ServerConfiguration {
private static final Logger log = LoggerFactory.getLogger(ServerConfiguration.class);
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/DefaultDependencies.java b/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/DefaultDependencies.java
index 70ad0e3387..4c5d88f5e2 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/DefaultDependencies.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/DefaultDependencies.java
@@ -23,8 +23,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -37,8 +35,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-@Named
-@Singleton
public class DefaultDependencies {
private static final Logger log = LoggerFactory.getLogger(DefaultDependencies.class);
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/ProcessPool.java b/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/ProcessPool.java
index a389be7e8d..54dd575a65 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/ProcessPool.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/executors/runner/ProcessPool.java
@@ -29,16 +29,12 @@
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-@Named
-@Singleton
public class ProcessPool {
private static final Logger log = LoggerFactory.getLogger(ProcessPool.class);
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/remote/ApiClientFactory.java b/agent/src/main/java/com/walmartlabs/concord/agent/remote/ApiClientFactory.java
index 92e2fde99b..0b2403c212 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/remote/ApiClientFactory.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/remote/ApiClientFactory.java
@@ -30,8 +30,6 @@
import com.walmartlabs.concord.common.IOUtils;
import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
@@ -39,8 +37,6 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;
-@Named
-@Singleton
public class ApiClientFactory {
private static final String SESSION_COOKIE_NAME = "JSESSIONID";
diff --git a/agent/src/main/java/com/walmartlabs/concord/agent/remote/QueueClientProvider.java b/agent/src/main/java/com/walmartlabs/concord/agent/remote/QueueClientProvider.java
index 789a565b8a..63f6945bd2 100644
--- a/agent/src/main/java/com/walmartlabs/concord/agent/remote/QueueClientProvider.java
+++ b/agent/src/main/java/com/walmartlabs/concord/agent/remote/QueueClientProvider.java
@@ -26,13 +26,9 @@
import com.walmartlabs.concord.server.queueclient.QueueClientConfiguration;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Provider;
-import javax.inject.Singleton;
import java.net.URISyntaxException;
-@Named
-@Singleton
public class QueueClientProvider implements Provider {
private final AgentConfiguration agentCfg;
diff --git a/it/testing-server/pom.xml b/it/testing-server/pom.xml
index 23b03d94db..d4cba3fc79 100644
--- a/it/testing-server/pom.xml
+++ b/it/testing-server/pom.xml
@@ -35,6 +35,10 @@
+
+ com.walmartlabs.concord
+ concord-agent
+
org.testcontainers
postgresql
@@ -43,5 +47,16 @@
com.typesafe
config
+
+
+ com.walmartlabs.concord
+ concord-client2
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
diff --git a/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordAgent.java b/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordAgent.java
new file mode 100644
index 0000000000..531378e33b
--- /dev/null
+++ b/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordAgent.java
@@ -0,0 +1,97 @@
+package com.walmartlabs.concord.it.testingserver;
+
+/*-
+ * *****
+ * Concord
+ * -----
+ * Copyright (C) 2017 - 2023 Walmart Inc.
+ * -----
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =====
+ */
+
+import com.google.inject.Guice;
+import com.google.inject.Module;
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigParseOptions;
+import com.typesafe.config.ConfigResolveOptions;
+import com.walmartlabs.concord.agent.Agent;
+import com.walmartlabs.concord.agent.AgentModule;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * A helper class for running concord-agent.
+ * The agent runs in the same JVM as TestingConcordAgent.
+ */
+public class TestingConcordAgent implements AutoCloseable {
+
+ private final Map extraConfiguration;
+ private final List> extraModules;
+ private final int apiPort;
+ private final String agentApiKey;
+
+ private Agent agent;
+
+ public TestingConcordAgent(TestingConcordServer testingConcordServer) {
+ this(testingConcordServer, Map.of(), List.of());
+ }
+
+ public TestingConcordAgent(TestingConcordServer testingConcordServer, Map extraConfiguration, List> extraModules) {
+ this.apiPort = testingConcordServer.getApiPort();
+ this.agentApiKey = testingConcordServer.getAgentApiKey();
+ this.extraConfiguration = extraConfiguration;
+ this.extraModules = extraModules;
+ }
+
+ public synchronized void start() {
+ var config = prepareConfig();
+ var system = new AgentModule(config);
+ var allModules = Stream.concat(extraModules.stream().map(f -> f.apply(config)), Stream.of(system)).toList();
+ var injector = Guice.createInjector(allModules);
+ agent = injector.getInstance(Agent.class);
+ agent.start();
+ }
+
+ public synchronized void stop() {
+ if (agent != null) {
+ agent.stop();
+ agent = null;
+ }
+ }
+
+ @Override
+ public void close() {
+ this.stop();
+ }
+
+ private Config prepareConfig() {
+ var extraConfig = ConfigFactory.parseMap(this.extraConfiguration);
+
+ var testConfig = ConfigFactory.parseMap(Map.of(
+ "maintenanceModeListenerPort", 0,
+ "server.apiBaseUrl", "http://localhost:" + apiPort,
+ "server.websocketUrl", "ws://localhost:" + apiPort + "/websocket",
+ "server.apiKey", agentApiKey
+ ));
+
+ var defaultConfig = ConfigFactory.load("concord-agent.conf", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults().setAllowUnresolved(true))
+ .getConfig("concord-agent");
+
+ return extraConfig.withFallback(testConfig.withFallback(defaultConfig)).resolve();
+ }
+}
diff --git a/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordServer.java b/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordServer.java
index bcca549988..4f1260828e 100644
--- a/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordServer.java
+++ b/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordServer.java
@@ -20,6 +20,7 @@
* =====
*/
+import com.google.common.collect.ImmutableMap;
import com.google.inject.Module;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
@@ -36,28 +37,39 @@
import java.util.function.Function;
import java.util.stream.Stream;
+import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
+/**
+ * A helper class for running concord-server. It runs PostgreSQL in Docker
+ * and the server runs in the same JVM as TestingConcordServer.
+ */
public class TestingConcordServer implements AutoCloseable {
+ private final PostgreSQLContainer> db;
private final Map extraConfiguration;
private final List> extraModules;
+ private final int apiPort;
+ private final String adminApiKey;
+ private final String agentApiKey;
- private PostgreSQLContainer> db;
private ConcordServer server;
- public TestingConcordServer(Map extraConfiguration, List> extraModules) {
- this.extraConfiguration = requireNonNull(extraConfiguration);
- this.extraModules = requireNonNull(extraModules);
+ public TestingConcordServer(PostgreSQLContainer> db) {
+ this(db, 8001, Map.of(), List.of());
}
- public TestingConcordServer() {
- this(Map.of(), List.of());
+ public TestingConcordServer(PostgreSQLContainer> db, int apiPort, Map extraConfiguration, List> extraModules) {
+ this.db = requireNonNull(db);
+ this.extraConfiguration = requireNonNull(extraConfiguration);
+ this.apiPort = apiPort;
+ this.extraModules = requireNonNull(extraModules);
+ this.adminApiKey = randomString(8);
+ this.agentApiKey = randomString(16);
}
public synchronized TestingConcordServer start() throws Exception {
- db = new PostgreSQLContainer<>("postgres:15-alpine");
- db.start();
+ checkArgument(db.isRunning(), "The database container is not running");
var config = prepareConfig(db);
var system = new ConcordServerModule(config);
@@ -68,21 +80,24 @@ public synchronized TestingConcordServer start() throws Exception {
return this;
}
- @Override
- public synchronized void close() throws Exception {
- this.stop();
- }
-
- public void stop() throws Exception {
+ public synchronized void stop() throws Exception {
if (server != null) {
server.stop();
server = null;
}
+ }
- if (db != null) {
- db.stop();
- db = null;
- }
+ @Override
+ public void close() throws Exception {
+ this.stop();
+ }
+
+ public int getApiPort() {
+ return apiPort;
+ }
+
+ public String getApiBaseUrl() {
+ return "http://localhost:" + apiPort;
}
public ConcordServer getServer() {
@@ -93,20 +108,30 @@ public PostgreSQLContainer> getDb() {
return db;
}
+ public String getAdminApiKey() {
+ return adminApiKey;
+ }
+
+ public String getAgentApiKey() {
+ return agentApiKey;
+ }
+
private Config prepareConfig(PostgreSQLContainer> db) {
var extraConfig = ConfigFactory.parseMap(this.extraConfiguration);
- var testConfig = ConfigFactory.parseMap(Map.of(
- "db.url", db.getJdbcUrl(),
- "db.appUsername", db.getUsername(),
- "db.appPassword", db.getPassword(),
- "db.inventoryUsername", db.getUsername(),
- "db.inventoryPassword", db.getPassword(),
- "db.changeLogParameters.defaultAdminToken", "foobar",
- "secretStore.serverPassword", randomString(),
- "secretStore.secretStoreSalt", randomString(),
- "secretStore.projectSecretSalt", randomString()
- ));
+ var testConfig = ConfigFactory.parseMap(ImmutableMap.builder()
+ .put("server.port", String.valueOf(apiPort))
+ .put("db.url", db.getJdbcUrl())
+ .put("db.appUsername", db.getUsername())
+ .put("db.appPassword", db.getPassword())
+ .put("db.inventoryUsername", db.getUsername())
+ .put("db.inventoryPassword", db.getPassword())
+ .put("db.changeLogParameters.defaultAdminToken", adminApiKey)
+ .put("db.changeLogParameters.defaultAgentToken", agentApiKey)
+ .put("secretStore.serverPassword", randomString(64))
+ .put("secretStore.secretStoreSalt", randomString(64))
+ .put("secretStore.projectSecretSalt", randomString(64))
+ .build());
var defaultConfig = ConfigFactory.load("concord-server.conf", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults().setAllowUnresolved(true))
.getConfig("concord-server");
@@ -114,8 +139,8 @@ private Config prepareConfig(PostgreSQLContainer> db) {
return extraConfig.withFallback(testConfig.withFallback(defaultConfig)).resolve();
}
- private static String randomString() {
- byte[] ab = new byte[64];
+ private static String randomString(int minLength) {
+ byte[] ab = new byte[minLength];
new SecureRandom().nextBytes(ab);
return Base64.getEncoder().encodeToString(ab);
}
@@ -124,20 +149,29 @@ private static String randomString() {
* Just an example.
*/
public static void main(String[] args) throws Exception {
- try (TestingConcordServer server = new TestingConcordServer(Map.of("process.watchdogPeriod", "10 seconds"), List.of())) {
+ try (var db = new PostgreSQLContainer<>("postgres:15-alpine");
+ var server = new TestingConcordServer(db, 8001, Map.of("process.watchdogPeriod", "10 seconds"), List.of())) {
+ db.start();
server.start();
-
- System.out.println("""
- ==============================================================
+ System.out.printf("""
+ ==============================================================
+
+ UI: http://localhost:8001/
+ DB:
+ JDBC URL: %s
+ username: %s
+ password: %s
- UI: http://localhost:8001/
- DB:
- JDBC URL: %s
- username: %s
- password: %s
- """.formatted(server.getDb().getJdbcUrl(), server.getDb().getUsername(), server.getDb().getPassword()));
+ admin API key: %s
+ agent API key: %s
+ %n""", db.getJdbcUrl(),
+ db.getUsername(),
+ db.getPassword(),
+ server.getAdminApiKey(),
+ server.getAgentApiKey());
Thread.currentThread().join();
}
}
}
+
diff --git a/it/testing-server/src/test/java/com/walmartlabs/concord/it/testingserver/TestingConcordIT.java b/it/testing-server/src/test/java/com/walmartlabs/concord/it/testingserver/TestingConcordIT.java
new file mode 100644
index 0000000000..0e93548756
--- /dev/null
+++ b/it/testing-server/src/test/java/com/walmartlabs/concord/it/testingserver/TestingConcordIT.java
@@ -0,0 +1,109 @@
+package com.walmartlabs.concord.it.testingserver;
+
+/*-
+ * *****
+ * Concord
+ * -----
+ * Copyright (C) 2017 - 2023 Walmart Inc.
+ * -----
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =====
+ */
+
+import com.walmartlabs.concord.client2.ApiClientConfiguration;
+import com.walmartlabs.concord.client2.DefaultApiClientFactory;
+import com.walmartlabs.concord.client2.ProcessApi;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+import static com.walmartlabs.concord.client2.ProcessEntry.StatusEnum.FINISHED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * A test that tests TestingConcordServer and TestingConcordAgent can run together in the same JVM.
+ */
+public class TestingConcordIT {
+
+ private PostgreSQLContainer> db;
+ private TestingConcordServer concordServer;
+ private TestingConcordAgent concordAgent;
+
+ @Before
+ public void setUp() throws Exception {
+ db = new PostgreSQLContainer<>("postgres:15-alpine");
+ db.start();
+
+ int apiPort = getFreePort();
+ concordServer = new TestingConcordServer(db, apiPort, Map.of(), List.of());
+ concordServer.start();
+
+ concordAgent = new TestingConcordAgent(concordServer);
+ concordAgent.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (concordAgent != null) {
+ concordAgent.close();
+ concordAgent = null;
+ }
+
+ if (concordServer != null) {
+ concordServer.close();
+ concordServer = null;
+ }
+
+ if (db != null) {
+ db.close();
+ db = null;
+ }
+ }
+
+ @Test(timeout = 120_000)
+ public void testRunningSimpleProcess() throws Exception {
+ var client = new DefaultApiClientFactory(concordServer.getApiBaseUrl())
+ .create(ApiClientConfiguration.builder()
+ .apiKey(concordServer.getAdminApiKey())
+ .build());
+
+ var processApi = new ProcessApi(client);
+ var response = processApi.startProcess(Map.of("concord.yml", """
+ configuration:
+ runtime: "concord-v2"
+ flows:
+ default:
+ - log: "Hello!"
+ """.getBytes()));
+ assertNotNull(response.getInstanceId());
+
+ var process = processApi.waitForCompletion(response.getInstanceId(), Duration.ofSeconds(60).toMillis());
+ assertEquals(FINISHED, process.getStatus());
+ }
+
+ private static int getFreePort() {
+ try (var socket = new ServerSocket(0)) {
+ return socket.getLocalPort();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/ApiServerModule.java b/server/impl/src/main/java/com/walmartlabs/concord/server/ApiServerModule.java
index d0f13ab6ae..fb07707b21 100644
--- a/server/impl/src/main/java/com/walmartlabs/concord/server/ApiServerModule.java
+++ b/server/impl/src/main/java/com/walmartlabs/concord/server/ApiServerModule.java
@@ -20,8 +20,10 @@
* =====
*/
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Binder;
import com.google.inject.Module;
+import com.google.inject.name.Names;
import com.walmartlabs.concord.server.boot.*;
import com.walmartlabs.concord.server.boot.filters.*;
import com.walmartlabs.concord.server.boot.servlets.FormServletHolder;
@@ -31,6 +33,8 @@
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.eclipse.jetty.servlet.FilterHolder;
+import org.sonatype.siesta.Component;
+import org.sonatype.siesta.jackson2.ObjectMapperResolver;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpServlet;
@@ -44,7 +48,14 @@ public class ApiServerModule implements Module {
@Override
public void configure(Binder binder) {
+ // Jackson
+
+ binder.bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class).in(SINGLETON);
+ binder.bind(ObjectMapper.class).annotatedWith(Names.named("siesta")).toProvider(ObjectMapperProvider.class).in(SINGLETON);
+ newSetBinder(binder, Component.class).addBinding().to(ObjectMapperResolver.class);
+
// Jetty
+
binder.bind(HttpServer.class).in(SINGLETON);
// Filter
diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/ObjectMapperInitializer.java b/server/impl/src/main/java/com/walmartlabs/concord/server/ObjectMapperProvider.java
similarity index 58%
rename from server/impl/src/main/java/com/walmartlabs/concord/server/ObjectMapperInitializer.java
rename to server/impl/src/main/java/com/walmartlabs/concord/server/ObjectMapperProvider.java
index 4184391c0e..eaa5068ac2 100644
--- a/server/impl/src/main/java/com/walmartlabs/concord/server/ObjectMapperInitializer.java
+++ b/server/impl/src/main/java/com/walmartlabs/concord/server/ObjectMapperProvider.java
@@ -4,7 +4,7 @@
* *****
* Concord
* -----
- * Copyright (C) 2017 - 2018 Walmart Inc.
+ * Copyright (C) 2017 - 2023 Walmart Inc.
* -----
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,24 +20,24 @@
* =====
*/
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import org.eclipse.sisu.EagerSingleton;
-import javax.inject.Inject;
-import javax.inject.Named;
+import javax.inject.Provider;
-@Named
-@EagerSingleton
-public class ObjectMapperInitializer {
+public class ObjectMapperProvider implements Provider {
- @Inject
- public ObjectMapperInitializer(@Named("siesta") javax.inject.Provider mapperProvider) {
- ObjectMapper om = mapperProvider.get();
- om.registerModule(new GuavaModule());
- om.registerModule(new Jdk8Module());
- om.registerModule(new JavaTimeModule());
+ @Override
+ public ObjectMapper get() {
+ return new ObjectMapper()
+ .registerModule(new GuavaModule())
+ .registerModule(new Jdk8Module())
+ .registerModule(new JavaTimeModule())
+ .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+ .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
}
diff --git a/server/impl/src/test/java/com/walmartlabs/concord/server/TestObjectMapper.java b/server/impl/src/test/java/com/walmartlabs/concord/server/TestObjectMapper.java
index c42e4a53b7..83306020f8 100644
--- a/server/impl/src/test/java/com/walmartlabs/concord/server/TestObjectMapper.java
+++ b/server/impl/src/test/java/com/walmartlabs/concord/server/TestObjectMapper.java
@@ -28,9 +28,7 @@ public final class TestObjectMapper {
public static final ObjectMapper INSTANCE = createObjectMapper();
private static ObjectMapper createObjectMapper() {
- ObjectMapper om = new ObjectMapperProvider().get();
- new ObjectMapperInitializer(() -> om);
- return om;
+ return new ObjectMapperProvider().get();
}
private TestObjectMapper() {