Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a standalone, containerized agent #91

Merged
merged 3 commits into from
Jan 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,10 @@ jobs:
asset_path: ${{ steps.release.outputs.artifacts_archive_path }}
asset_name: ${{ steps.release.outputs.artifacts_archive_path }}
asset_content_type: application/zip
- if: github.event.release
name: Build & Push Docker images
id: docker
timeout-minutes: 10
run: |
echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
mvn -B -Pdocker -DskipTests -DdockerGoal=build -DdockerImageTag=${{ github.event.release.tag_name }} package
148 changes: 148 additions & 0 deletions agent/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>agent</artifactId>
<packaging>jar</packaging>

<name>EZE - Embedded Zeebe Engine Agent</name>

<parent>
<groupId>org.camunda.community</groupId>
<artifactId>eze-root</artifactId>
<relativePath>../pom.xml</relativePath>
<version>0.7.1-SNAPSHOT</version>
</parent>

<dependencies>
<dependency>
<groupId>org.camunda.community</groupId>
<artifactId>eze</artifactId>
<version>${project.version}</version>
</dependency>

<!-- required as otherwise the engine cannot be instantiated due to a missing class -->
<dependency>
<groupId>io.camunda</groupId>
<artifactId>zeebe-exporter-api</artifactId>
</dependency>

<!-- cli -->
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
</dependency>

<dependency>
<groupId>org.agrona</groupId>
<artifactId>agrona</artifactId>
</dependency>

<!-- logging; adopt the implementation included in eze -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.camunda</groupId>
<artifactId>zeebe-client-java</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<properties>
<agent.finalName>eze</agent.finalName>
<agent.mainClass>org.camunda.community.eze.agent.Agent</agent.mainClass>
</properties>

<build>
<finalName>${agent.finalName}</finalName>
<plugins>
<!-- process picocli annotations -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>info.picocli</groupId>
<artifactId>picocli-codegen</artifactId>
<version>${picocli.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>

<profiles>
<!-- generates a Docker image as part of the build process; this configuration expects
authentication to have been set up beforehand -->
<profile>
<id>docker</id>

<properties>
<dockerImageTag>${project.version}</dockerImageTag>

<!-- set to build to build AND push; leave to dockerBuild to only build locally -->
<dockerGoal>dockerBuild</dockerGoal>
</properties>

<build>
<plugins>
<!-- Build a docker image -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>

<configuration>
<from>
<!-- smallest Java 17 base image; Temurin does not produce Java 17 JRE only builds -->
<image>
azul/zulu-openjdk-alpine:17-jre-headless@sha256:ca58039e2aa75651ab6e6558816202e85187675d8ee64f6dc7fc8d44cb6ffef3
</image>
</from>
<to>
<image>ghcr.io/camunda-community-hub/${project.build.finalName}</image>
<tags>${dockerImageTag}</tags>
</to>
<container>
<mainClass>${agent.mainClass}</mainClass>
<ports>
<port>26500</port>
</ports>
</container>
</configuration>

<executions>
<execution>
<phase>package</phase>
<goals>
<goal>${dockerGoal}</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
73 changes: 73 additions & 0 deletions agent/src/main/java/org/camunda/community/eze/agent/Agent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Zeebe Community License 1.1. You may not use this file
* except in compliance with the Zeebe Community License 1.1.
*/
package org.camunda.community.eze.agent;

import java.util.Collections;
import java.util.concurrent.Callable;
import org.agrona.concurrent.ShutdownSignalBarrier;
import org.camunda.community.eze.EngineFactory;
import org.camunda.community.eze.ZeebeEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;

/**
* A thin wrapper around a {@link ZeebeEngine} instance. For potential exit codes, see {@link
* ExitCode}.
*/
@Command(name = "agent", mixinStandardHelpOptions = true, description = "Standalone EZE agent")
public final class Agent implements Callable<Integer> {

private final Logger logger;

public Agent() {
logger = LoggerFactory.getLogger(getClass());
}

@Override
public Integer call() {
final var shutdownBarrier = new ShutdownSignalBarrier();
final ZeebeEngine engine;

logger.info("Starting EZE agent...");
try {
engine = EngineFactory.INSTANCE.create(Collections.emptyList());
} catch (final Exception e) {
logger.error("Failed to create an instance of the embedded engine", e);
return ExitCode.CREATE_ERROR.getCode();
}

try {
engine.start();
} catch (final Exception e) {
logger.error("Failed to start EZE agent", e);
return ExitCode.START_ERROR.getCode();
}

logger.info("EZE agent started at {}", engine.getGatewayAddress());
ExitCode exitCode = ExitCode.OK;
shutdownBarrier.await();

logger.info("Shutting down EZE agent...");
try {
engine.stop();
} catch (final Exception e) {
logger.warn("Failed to gracefully shutdown the embedded engine", e);
exitCode = ExitCode.SHUTDOWN_ERROR;
}

logger.info("Shutdown EZE agent with status: {}", exitCode);
return exitCode.getCode();
}

public static void main(final String[] args) {
final int exitCode = new CommandLine(new Agent()).execute(args);
System.exit(exitCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Zeebe Community License 1.1. You may not use this file
* except in compliance with the Zeebe Community License 1.1.
*/
package org.camunda.community.eze.agent;

/**
* A list of possible exit status codes returned by the Agent. These can be consumed by scripts or
* container orchestrators to react appropriately based on the error.
*/
enum ExitCode {
/** Indicates the application started and shutdown gracefully without errors */
OK(0),
/** Indicates the agent failed to create an instance of EZE */
CREATE_ERROR(128),
/** Indicates the agent failed to start the EZE instance */
START_ERROR(129),
/** Indicates the agent started the engine, but failed to gracefully shut it down */
SHUTDOWN_ERROR(131);

private final int code;

ExitCode(final int code) {
this.code = code;
}

int getCode() {
return code;
}
}
16 changes: 16 additions & 0 deletions agent/src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">

<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>

<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>

</Configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.camunda.community.eze.agent;

import static org.assertj.core.api.Assertions.assertThat;

import io.camunda.zeebe.client.ZeebeClient;
import io.camunda.zeebe.client.api.response.Topology;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.camunda.community.eze.ZeebeEngineImpl;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import sun.misc.Signal;

final class AgentTest {
private ExecutorService agentThreadContext;

@AfterEach
void afterEach() {
if (agentThreadContext != null) {
agentThreadContext.shutdownNow();
}
}

@Test
void shouldStartEngine() {
// given
final var agent = new Agent();
agentThreadContext = Executors.newSingleThreadExecutor();

// when
final var exitCodeFuture = agentThreadContext.submit(agent);
final Topology topology;
try (final var client =
ZeebeClient.newClientBuilder()
.usePlaintext()
.gatewayAddress("localhost:" + ZeebeEngineImpl.PORT)
.build()) {
topology = client.newTopologyRequest().send().join();
}
Signal.raise(new Signal("INT"));

// then
assertThat(topology.getClusterSize()).isEqualTo(1);
assertThat(exitCodeFuture)
.succeedsWithin(Duration.ofSeconds(10))
.isEqualTo(ExitCode.OK.getCode());
}
}
28 changes: 28 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</parent>

<modules>
<module>agent</module>
<module>eze</module>
<module>junit-extension</module>
</modules>
Expand Down Expand Up @@ -54,6 +55,7 @@
<plugin.version.jacoco>0.8.7</plugin.version.jacoco>
<plugin.version.fmt>2.13</plugin.version.fmt>
<plugin.version.license>4.1</plugin.version.license>
<plugin.version.jib>3.2.0</plugin.version.jib>
<assertj.version>3.22.0</assertj.version>
<awaitility.version>4.1.1</awaitility.version>
<log4j.version>2.17.1</log4j.version>
Expand All @@ -63,6 +65,8 @@
<protobuf-java.version>3.19.3</protobuf-java.version>
<jackson.version>2.13.1</jackson.version>
<scala-library.version>2.13.8</scala-library.version>
<picocli.version>4.6.2</picocli.version>
<agrona.version>1.14.0</agrona.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -214,10 +218,34 @@
<artifactId>scala-library</artifactId>
<version>${scala-library.version}</version>
</dependency>

<!-- Agent dependencies -->
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>${picocli.version}</version>
</dependency>

<dependency>
<groupId>org.agrona</groupId>
<artifactId>agrona</artifactId>
<version>${agrona.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<pluginManagement>
<plugins>
<!-- used to package Docker images, primarily for the Agent -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>${plugin.version.jib}</version>
</plugin>
</plugins>
</pluginManagement>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down