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);
+ }
+ }
+}