From bde42ee1cca1e03b87bd76d9fb79e4fe3bd3e3c5 Mon Sep 17 00:00:00 2001 From: Ivan Bodrov Date: Tue, 21 Nov 2023 10:15:13 -0500 Subject: [PATCH] testing-concord-server: add agent wrapper, simple test --- it/testing-server/pom.xml | 15 +++ .../it/testingserver/TestingConcordAgent.java | 92 +++++++++++++++++++ .../testingserver/TestingConcordServer.java | 90 +++++++++++------- .../it/testingserver/TestingConcordIT.java | 85 +++++++++++++++++ 4 files changed, 248 insertions(+), 34 deletions(-) create mode 100644 it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordAgent.java create mode 100644 it/testing-server/src/test/java/com/walmartlabs/concord/it/testingserver/TestingConcordIT.java 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..d2cd4531e3 --- /dev/null +++ b/it/testing-server/src/main/java/com/walmartlabs/concord/it/testingserver/TestingConcordAgent.java @@ -0,0 +1,92 @@ +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 String agentApiKey; + + private Agent agent; + + public TestingConcordAgent(String agentApiKey) { + this(agentApiKey, Map.of(), List.of()); + } + + public TestingConcordAgent(String agentApiKey, Map extraConfiguration, List> extraModules) { + this.agentApiKey = agentApiKey; + 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( + "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..ec3e8522d3 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 @@ -36,28 +36,37 @@ 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 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, Map.of(), List.of()); } - public TestingConcordServer() { - this(Map.of(), List.of()); + public TestingConcordServer(PostgreSQLContainer db, Map extraConfiguration, List> extraModules) { + this.db = requireNonNull(db); + this.extraConfiguration = requireNonNull(extraConfiguration); + 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 +77,16 @@ 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 ConcordServer getServer() { @@ -93,6 +97,14 @@ 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); @@ -102,10 +114,11 @@ private Config prepareConfig(PostgreSQLContainer db) { "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() + "db.changeLogParameters.defaultAdminToken", adminApiKey, + "db.changeLogParameters.defaultAgentToken", agentApiKey, + "secretStore.serverPassword", randomString(64), + "secretStore.secretStoreSalt", randomString(64), + "secretStore.projectSecretSalt", randomString(64) )); var defaultConfig = ConfigFactory.load("concord-server.conf", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults().setAllowUnresolved(true)) @@ -114,8 +127,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 +137,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, 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 - """.formatted(server.getDb().getJdbcUrl(), server.getDb().getUsername(), server.getDb().getPassword())); + UI: http://localhost:8001/ + DB: + JDBC URL: %s + username: %s + password: %s + + 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..2fe1d7f1e3 --- /dev/null +++ b/it/testing-server/src/test/java/com/walmartlabs/concord/it/testingserver/TestingConcordIT.java @@ -0,0 +1,85 @@ +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 java.time.Duration; +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 TestingConcordServer concordServer; + private TestingConcordAgent concordAgent; + + @Before + public void setUp() throws Exception { + concordServer = new TestingConcordServer(); + concordServer.start(); + + concordAgent = new TestingConcordAgent(concordServer.getAgentApiKey()); + concordAgent.start(); + } + + @After + public void tearDown() throws Exception { + if (concordAgent != null) { + concordAgent.close(); + concordAgent = null; + } + + if (concordServer != null) { + concordServer.close(); + concordServer = null; + } + } + + @Test(timeout = 120_000) + public void testRunningSimpleProcess() throws Exception { + var client = new DefaultApiClientFactory("http://localhost:8001") + .create(ApiClientConfiguration.builder() + .apiKey(concordServer.getAdminApiKey()) + .build()); + + var processApi = new ProcessApi(client); + var response = processApi.startProcess(Map.of("concord.yml", """ + flows: + default: + - log: "Hello!" + """.getBytes())); + assertNotNull(response.getInstanceId()); + + var process = processApi.waitForCompletion(response.getInstanceId(), Duration.ofSeconds(60).toMillis()); + assertEquals(FINISHED, process.getStatus()); + } +}