From 8f0c5e322df07336ecce98e4257b046097146564 Mon Sep 17 00:00:00 2001 From: Michal Bajer Date: Wed, 3 Aug 2022 11:11:44 +0000 Subject: [PATCH] feat(iroha2-ledger): add Iroha V2 test ledger image and setup class - Add a new test image for Iroha V2 (iroha2-all-in-one). It start a test ledger in single container, and also contains a proxy script for running iroha_client_cli. - Add the new image to the CI. - Add a new class for starting and interacting with Iroha V2 test ledger from typescript test - Iroha2TestLedger. - Add test for test setup class to ensure basic functions are working correctly. Relates to #2138 Signed-off-by: Michal Bajer --- .../workflows/iroha2-all-in-one-publish.yaml | 56 +++ .../src/main/typescript/common/containers.ts | 19 + .../typescript/iroha/iroha2-test-ledger.ts | 388 ++++++++++++++++++ .../src/main/typescript/public-api.ts | 7 + .../iroha2-test-ledger.test.ts | 190 +++++++++ tools/docker/iroha2-all-in-one/Dockerfile | 44 ++ tools/docker/iroha2-all-in-one/README.md | 52 +++ .../iroha2-all-in-one/docker-compose.yml | 20 + tools/docker/iroha2-all-in-one/healthcheck.sh | 21 + .../iroha2-all-in-one/iroha_client_cli.sh | 10 + .../iroha2-all-in-one/script-start-docker.sh | 2 + .../src/configs/client_cli/config.json | 20 + .../src/configs/peer/config.json | 46 +++ .../src/configs/peer/genesis.json | 97 +++++ .../iroha2-all-in-one/src/docker-compose.yml | 72 ++++ .../docker/iroha2-all-in-one/supervisord.conf | 23 ++ 16 files changed, 1067 insertions(+) create mode 100644 .github/workflows/iroha2-all-in-one-publish.yaml create mode 100644 packages/cactus-test-tooling/src/main/typescript/iroha/iroha2-test-ledger.ts create mode 100644 packages/cactus-test-tooling/src/test/typescript/integration/iroha/iroha2-test-ledger/iroha2-test-ledger.test.ts create mode 100644 tools/docker/iroha2-all-in-one/Dockerfile create mode 100644 tools/docker/iroha2-all-in-one/README.md create mode 100644 tools/docker/iroha2-all-in-one/docker-compose.yml create mode 100644 tools/docker/iroha2-all-in-one/healthcheck.sh create mode 100644 tools/docker/iroha2-all-in-one/iroha_client_cli.sh create mode 100755 tools/docker/iroha2-all-in-one/script-start-docker.sh create mode 100644 tools/docker/iroha2-all-in-one/src/configs/client_cli/config.json create mode 100644 tools/docker/iroha2-all-in-one/src/configs/peer/config.json create mode 100644 tools/docker/iroha2-all-in-one/src/configs/peer/genesis.json create mode 100644 tools/docker/iroha2-all-in-one/src/docker-compose.yml create mode 100644 tools/docker/iroha2-all-in-one/supervisord.conf diff --git a/.github/workflows/iroha2-all-in-one-publish.yaml b/.github/workflows/iroha2-all-in-one-publish.yaml new file mode 100644 index 00000000000..815813e1da3 --- /dev/null +++ b/.github/workflows/iroha2-all-in-one-publish.yaml @@ -0,0 +1,56 @@ +name: iroha2-all-in-one-publish + +on: + push: + # Publish `main` as Docker `latest` image. + branches: + - main + + # Publish `v1.2.3` tags as releases. + tags: + - v* + +env: + IMAGE_NAME: cactus-iroha2-all-in-one + +jobs: + # Push image to GitHub Packages. + # See also https://docs.docker.com/docker-hub/builds/ + build-tag-push-container: + runs-on: ubuntu-20.04 + env: + DOCKER_BUILDKIT: 1 + DOCKERFILE_PATH: ./tools/docker/iroha2-all-in-one/Dockerfile + DOCKER_BUILD_DIR: ./tools/docker/iroha2-all-in-one/ + permissions: + packages: write + contents: read + + steps: + - uses: actions/checkout@v2.4.0 + + - name: Build image + run: docker build $DOCKER_BUILD_DIR --file $DOCKERFILE_PATH --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" + + - name: Log in to registry + # This is where you will update the PAT to GITHUB_TOKEN + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Push image + run: | + SHORTHASH=$(git rev-parse --short "$GITHUB_SHA") + TODAYS_DATE="$(date +%F)" + DOCKER_TAG="$TODAYS_DATE-$SHORTHASH" + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME + # Change all uppercase to lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + # Strip "v" prefix from tag name + [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') + # Do not use the `latest` tag at all, tag with date + git short hash if there is no git tag + [ "$VERSION" == "main" ] && VERSION=$DOCKER_TAG + echo IMAGE_ID=$IMAGE_ID + echo VERSION=$VERSION + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION diff --git a/packages/cactus-test-tooling/src/main/typescript/common/containers.ts b/packages/cactus-test-tooling/src/main/typescript/common/containers.ts index b1f2f1965cd..dd642b30f94 100644 --- a/packages/cactus-test-tooling/src/main/typescript/common/containers.ts +++ b/packages/cactus-test-tooling/src/main/typescript/common/containers.ts @@ -653,6 +653,25 @@ export class Containers { } }); } + + /** + * Get all environment variables defined in container provided in argument. + * + * @param container Running dockerode container instance + * @returns Map between environment variable name and it's value. + */ + public static async getEnvVars( + container: Container, + ): Promise> { + Checks.truthy(container); + + const inspectInfo = await container.inspect(); + return new Map( + inspectInfo.Config.Env.map( + (entry) => entry.split("=") as [string, string], + ), + ); + } } export interface IStreamLogsRequest { diff --git a/packages/cactus-test-tooling/src/main/typescript/iroha/iroha2-test-ledger.ts b/packages/cactus-test-tooling/src/main/typescript/iroha/iroha2-test-ledger.ts new file mode 100644 index 00000000000..b974737e477 --- /dev/null +++ b/packages/cactus-test-tooling/src/main/typescript/iroha/iroha2-test-ledger.ts @@ -0,0 +1,388 @@ +/** + * Helper utils for setting up and starting Iroha V2 ledger for testing. + */ + +import { EventEmitter } from "events"; +import Docker, { + Container, + ContainerCreateOptions, + ContainerInfo, +} from "dockerode"; +import { + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { ITestLedger } from "../i-test-ledger"; +import { Containers } from "../common/containers"; + +/** + * Input options to Iroha2TestLedger constructor. + */ +export interface IIroha2TestLedgerOptions { + readonly containerImageName?: string; + readonly containerImageVersion?: string; + readonly logLevel?: LogLevelDesc; + readonly emitContainerLogs?: boolean; + readonly envVars?: string[]; + // For test development, attach to ledger that is already running, don't spin up new one + readonly useRunningLedger?: boolean; +} + +/** + * Default values used by Iroha2TestLedger constructor. + */ +export const IROHA2_TEST_LEDGER_DEFAULT_OPTIONS = Object.freeze({ + containerImageName: "ghcr.io/hyperledger/cactus-iroha2-all-in-one", + containerImageVersion: "2022-10-18-06770b6c", + logLevel: "info" as LogLevelDesc, + emitContainerLogs: true, + envVars: [], + useRunningLedger: false, +}); + +/** + * Iroha V2 configuration used by `iroha_client_cli` tool. + * Contains all the necessary data needed to connect to the Iroha ledger. + */ +export type Iroha2ClientConfig = { + TORII_API_URL: string; + TORII_TELEMETRY_URL: string; + ACCOUNT_ID: { + name: string; + domain_id: { + name: string; + }; + }; + BASIC_AUTH: { + web_login: string; + password: string; + }; + PUBLIC_KEY: string; + PRIVATE_KEY: { + digest_function: string; + payload: string; + }; + LOGGER_CONFIGURATION: Record; +}; + +/** + * Class for running a test Iroha V2 ledger in a container. + */ +export class Iroha2TestLedger implements ITestLedger { + public readonly containerImageName: string; + public readonly containerImageVersion: string; + public readonly logLevel: LogLevelDesc; + public readonly emitContainerLogs: boolean; + public readonly envVars: string[]; + public readonly useRunningLedger: boolean; + public container: Container | undefined; + + private readonly log: Logger; + + constructor(options?: IIroha2TestLedgerOptions) { + // Parse input options + this.containerImageName = + options?.containerImageName ?? + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.containerImageName; + + this.containerImageVersion = + options?.containerImageVersion ?? + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.containerImageVersion; + + this.logLevel = + options?.logLevel ?? IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.logLevel; + + this.emitContainerLogs = + options?.emitContainerLogs ?? + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.emitContainerLogs; + + this.envVars = + options?.envVars ?? IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.envVars; + + this.useRunningLedger = + options?.useRunningLedger ?? + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.useRunningLedger; + + // Instantiate logger + this.log = LoggerProvider.getOrCreate({ + level: this.logLevel, + label: "iroha2-test-ledger", + }); + } + + /** + * Full container name with a version tag + */ + public get fullContainerImageName(): string { + return [this.containerImageName, this.containerImageVersion].join(":"); + } + + /** + * Start a test Iroha V2 ledger. + * + * @param omitPull Don't pull docker image from upstream if true. + * @returns Promise + */ + public async start(omitPull = false): Promise { + if (this.useRunningLedger) { + this.log.info( + "Search for already running Iroha V2 Test Ledger because 'useRunningLedger' flag is enabled.", + ); + this.log.info( + "Search criteria - image name: ", + this.fullContainerImageName, + ", state: running", + ); + const containerInfo = await Containers.getByPredicate( + (ci) => + ci.Image === this.fullContainerImageName && ci.State === "running", + ); + const docker = new Docker(); + this.container = docker.getContainer(containerInfo.Id); + return this.container; + } + + if (this.container) { + this.log.warn("Container was already running - restarting it..."); + await this.container.stop(); + await this.container.remove(); + this.container = undefined; + } + + if (!omitPull) { + await Containers.pullImage( + this.fullContainerImageName, + {}, + this.logLevel, + ); + } + + const createOptions: ContainerCreateOptions = { + ExposedPorts: { + "8080/tcp": {}, // Peer0 API + "8180/tcp": {}, // Peer0 Telemetry + }, + + Env: this.envVars, + + HostConfig: { + PublishAllPorts: true, + Privileged: true, + }, + }; + + return new Promise((resolve, reject) => { + const docker = new Docker(); + const eventEmitter: EventEmitter = docker.run( + this.fullContainerImageName, + [], + [], + createOptions, + {}, + (err: unknown) => { + if (err) { + reject(err); + } + }, + ); + + eventEmitter.once("start", async (container: Container) => { + this.container = container; + + if (this.emitContainerLogs) { + const fnTag = `[${this.fullContainerImageName}]`; + await Containers.streamLogs({ + container: this.container, + tag: fnTag, + log: this.log, + }); + } + + try { + await Containers.waitForHealthCheck(container.id); + resolve(container); + } catch (ex) { + this.log.error(ex); + reject(ex); + } + }); + }); + } + + /** + * Get container status. + * + * @returns status string + */ + public async getContainerStatus(): Promise { + if (!this.container) { + throw new Error( + "Iroha2TestLedger#getContainerStatus(): Container not started yet!", + ); + } + + const { Status } = await Containers.getById(this.container.id); + return Status; + } + + /** + * Stop a test Iroha V2 ledger. + * + * @returns Stop operation results. + */ + public async stop(): Promise { + if (this.useRunningLedger) { + this.log.info("Ignore stop request because useRunningLedger is enabled."); + return; + } else if (this.container) { + return Containers.stop(this.container); + } else { + throw new Error( + `Iroha2TestLedger#stop() Container was never created, nothing to stop.`, + ); + } + } + + /** + * Destroy a test Iroha V2 ledger. + * + * @returns Destroy operation results. + */ + public async destroy(): Promise { + if (this.useRunningLedger) { + this.log.info( + "Ignore destroy request because useRunningLedger is enabled.", + ); + return; + } else if (this.container) { + return this.container.remove(); + } else { + throw new Error( + `Iroha2TestLedger#destroy() Container was never created, nothing to destroy.`, + ); + } + } + + /** + * Get this container info (output from dockerode listContainers method). + * + * @returns ContainerInfo + */ + protected async getContainerInfo(): Promise { + if (!this.container) { + throw new Error( + "Iroha2TestLedger#getContainerInfo(): Container not started yet!", + ); + } + + const containerInfos = await new Docker().listContainers({}); + const containerId = this.container.id; + const thisContainerInfo = containerInfos.find( + (ci) => ci.Id === containerId, + ); + + if (thisContainerInfo) { + return thisContainerInfo; + } else { + throw new Error( + "Iroha2TestLedger#getContainerInfo() could not find container info.", + ); + } + } + + /** + * Change the port in URL from original to the one that was exported by docker + * (i.e. the one that is available in `localhost` running this container, not inside the container). + * + * @param url some URL ending with a port (e.g. `http://127.0.0.1:8080`) + * @param containerInfo dockerode container info. + * @returns patched URL string. + */ + protected async patchDockerPortInURL( + url: string, + containerInfo: ContainerInfo, + ): Promise { + this.log.debug("URL before adjustment:", url); + + const origPort = url.substring(url.lastIndexOf(":") + 1); + const localhostPort = await Containers.getPublicPort( + parseInt(origPort, 10), + containerInfo, + ); + + const newUrl = url.replace(origPort, localhostPort.toString()); + this.log.debug("URL after adjustment:", newUrl); + + return newUrl; + } + + /** + * Read client config file from the container. Adjust the ports in URL so that the endpoints + * can be used from localhost. + * + * @returns parsed `Iroha2ClientConfig` + */ + public async getClientConfig(): Promise { + if (!this.container) { + throw new Error( + "Iroha2TestLedger#getClientConfig(): Container not started yet!", + ); + } + + // Get App root + const envVars = await Containers.getEnvVars(this.container); + const appRootDir = envVars.get("APP_ROOT") ?? "/app"; + + // Read config file + const configPath = `${appRootDir}/configs/client_cli/config.json`; + this.log.debug("Get client config from path:", configPath); + const configFile = await Containers.pullFile( + this.container, + configPath, + "ascii", + ); + this.log.debug("Raw config file:", configFile); + + // Parse file + const configObj = JSON.parse(configFile) as Iroha2ClientConfig; + + // Patch ports + const containerInfo = await this.getContainerInfo(); + configObj.TORII_API_URL = await this.patchDockerPortInURL( + configObj.TORII_API_URL, + containerInfo, + ); + configObj.TORII_TELEMETRY_URL = await this.patchDockerPortInURL( + configObj.TORII_TELEMETRY_URL, + containerInfo, + ); + + return configObj; + } + + /** + * Execute `iroha_client_cli` on the ledger. + * + * @param cmd Array of `iroha_client_cli` arguments. + * @param timeout Command timeout. + * @returns Output of the command. + * @note The output will contain some additional output (like fetching the cli image), not only the command response. + */ + public async runIrohaClientCli( + cmd: string[], + timeout = 60 * 1000, + ): Promise { + if (!this.container) { + throw new Error( + "Iroha2TestLedger#runIrohaClientCli(): Container not started yet!", + ); + } + + // Pass the command to shell helper script in the container + cmd.unshift("iroha_client_cli"); + + this.log.debug("Run shell command:", cmd); + return Containers.exec(this.container, cmd, timeout, this.logLevel); + } +} diff --git a/packages/cactus-test-tooling/src/main/typescript/public-api.ts b/packages/cactus-test-tooling/src/main/typescript/public-api.ts index 7d9a41a67b0..1da5fdd4fa0 100755 --- a/packages/cactus-test-tooling/src/main/typescript/public-api.ts +++ b/packages/cactus-test-tooling/src/main/typescript/public-api.ts @@ -65,6 +65,13 @@ export { IROHA_TEST_LEDGER_OPTIONS_JOI_SCHEMA, } from "./iroha/iroha-test-ledger"; +export { + Iroha2TestLedger, + IIroha2TestLedgerOptions, + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS, + Iroha2ClientConfig, +} from "./iroha/iroha2-test-ledger"; + export { PostgresTestContainer, IPostgresTestContainerConstructorOptions, diff --git a/packages/cactus-test-tooling/src/test/typescript/integration/iroha/iroha2-test-ledger/iroha2-test-ledger.test.ts b/packages/cactus-test-tooling/src/test/typescript/integration/iroha/iroha2-test-ledger/iroha2-test-ledger.test.ts new file mode 100644 index 00000000000..87eae536eea --- /dev/null +++ b/packages/cactus-test-tooling/src/test/typescript/integration/iroha/iroha2-test-ledger/iroha2-test-ledger.test.ts @@ -0,0 +1,190 @@ +/** + * Tests of Iroha V2 helper typescript setup class. + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +// Ledger settings +const containerImageName = "ghcr.io/hyperledger/cactus-iroha2-all-in-one"; +const containerImageVersion = "2022-10-18-06770b6c"; +const useRunningLedger = false; + +// Log settings +const testLogLevel: LogLevelDesc = "info"; + +import { + Iroha2TestLedger, + pruneDockerAllIfGithubAction, + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS, +} from "../../../../../main/typescript/index"; + +import { + LogLevelDesc, + LoggerProvider, + Logger, +} from "@hyperledger/cactus-common"; + +import "jest-extended"; + +// Logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "iroha2-test-ledger.test", + level: testLogLevel, +}); + +/** + * Main test suite + */ +describe("Iroha V2 Test Ledger checks", () => { + let ledger: Iroha2TestLedger; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + + log.info("Start Iroha2TestLedger..."); + ledger = new Iroha2TestLedger({ + containerImageName, + containerImageVersion, + useRunningLedger, + emitContainerLogs: false, + logLevel: testLogLevel, + }); + log.debug("IrohaV2 image:", ledger.fullContainerImageName); + expect(ledger).toBeTruthy(); + + await ledger.start(); + }); + + afterAll(async () => { + log.info("FINISHING THE TESTS"); + + if (ledger) { + log.info("Stop the fabric ledger..."); + await ledger.stop(); + await ledger.destroy(); + } + + log.info("Prune Docker..."); + await pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + }); + + ////////////////////////////////// + // Tests + ////////////////////////////////// + + /** + * Check if started container is still healthy. + */ + test("Started container is healthy", async () => { + const status = await ledger.getContainerStatus(); + expect(status).toEndWith("(healthy)"); + }); + + /** + * Check handling of default constructor options. + */ + test("Starting without options sets default values correctly", async () => { + const defaultLedger = new Iroha2TestLedger(); + + expect(defaultLedger.containerImageName).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.containerImageName, + ); + expect(defaultLedger.containerImageVersion).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.containerImageVersion, + ); + expect(defaultLedger.logLevel).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.logLevel, + ); + expect(defaultLedger.emitContainerLogs).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.emitContainerLogs, + ); + expect(defaultLedger.envVars).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.envVars, + ); + expect(defaultLedger.useRunningLedger).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.useRunningLedger, + ); + }); + + /** + * Check handling of default boolean constructor options. + */ + test("Constructor handles default boolean values correctly", async () => { + // true flags + const trueOptsLedger = new Iroha2TestLedger({ + emitContainerLogs: true, + useRunningLedger: true, + }); + expect(trueOptsLedger.emitContainerLogs).toEqual(true); + expect(trueOptsLedger.useRunningLedger).toEqual(true); + + // false flags + const falseOptsLedger = new Iroha2TestLedger({ + emitContainerLogs: false, + useRunningLedger: false, + }); + expect(falseOptsLedger.emitContainerLogs).toEqual(false); + expect(falseOptsLedger.useRunningLedger).toEqual(false); + + // undefined flags + const undefinedOptsLedger = new Iroha2TestLedger({ + emitContainerLogs: undefined, + useRunningLedger: undefined, + }); + expect(undefinedOptsLedger.emitContainerLogs).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.emitContainerLogs, + ); + expect(undefinedOptsLedger.useRunningLedger).toEqual( + IROHA2_TEST_LEDGER_DEFAULT_OPTIONS.useRunningLedger, + ); + }); + + /** + * Check response of `getClientConfig()` + */ + test("getClientConfig returns correct data", async () => { + const config = await ledger.getClientConfig(); + log.info("Received client config:", JSON.stringify(config)); + + expect(config).toBeTruthy(); + + expect(config.TORII_API_URL).toBeTruthy(); + expect(config.TORII_TELEMETRY_URL).toBeTruthy(); + + expect(config.ACCOUNT_ID).toBeTruthy(); + expect(config.ACCOUNT_ID.name).toBeTruthy(); + expect(config.ACCOUNT_ID.domain_id).toBeTruthy(); + expect(config.ACCOUNT_ID.domain_id.name).toBeTruthy(); + + expect(config.BASIC_AUTH).toBeTruthy(); + expect(config.BASIC_AUTH.web_login).toBeTruthy(); + expect(config.BASIC_AUTH.password).toBeTruthy(); + + expect(config.PUBLIC_KEY).toBeTruthy(); + + expect(config.PRIVATE_KEY).toBeTruthy(); + expect(config.PRIVATE_KEY.digest_function).toBeTruthy(); + expect(config.PRIVATE_KEY.payload).toBeTruthy(); + + expect(config.LOGGER_CONFIGURATION).toBeTruthy(); + }); + + /** + * Check response of `runIrohaClientCli()` + * + * @todo this is flaky for some reason - fix + */ + test.skip("runIrohaClientCli returns asset data", async () => { + const assets = await ledger.runIrohaClientCli(["asset", "list", "all"]); + log.info("Received assets response:", assets); + expect(assets).toBeTruthy(); + expect(assets).toContain("definition_id"); + }); +}); diff --git a/tools/docker/iroha2-all-in-one/Dockerfile b/tools/docker/iroha2-all-in-one/Dockerfile new file mode 100644 index 00000000000..cefa2c9b5b4 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/Dockerfile @@ -0,0 +1,44 @@ +FROM docker:20.10.17-dind + +ENV APP_ROOT="/app" + +# Install docker-compose +RUN apk update \ + && apk add --no-cache \ + # docker-compose dependencies + py-pip \ + python3-dev \ + libffi-dev \ + openssl-dev \ + # Other dependencies + supervisor \ + jq \ + && pip install wheel \ + && pip install docker-compose + +# Copy iroha_client_cli proxy script +COPY ./iroha_client_cli.sh /bin/iroha_client_cli +RUN chmod +x /bin/iroha_client_cli + +# Setup healtcheck +COPY ./healthcheck.sh /bin/healthcheck +RUN chmod +x /bin/healthcheck +HEALTHCHECK --interval=5s --timeout=5s --start-period=30s --retries=60 CMD /bin/healthcheck + +WORKDIR ${APP_ROOT} + +# Copy Iroha 2 test network sources +COPY ./src . + +# Peer0 API +EXPOSE 8080 +# Peer0 telemetry +EXPOSE 8180 + +ENV IROHA_VERSION="dev-nightly-75da907f66d5270f407a50e06bc76cec41d3d409" +ENV IROHA_CLI_VERSION="dev-nightly-75da907f66d5270f407a50e06bc76cec41d3d409" + +# Setup supervisor entrypoint +COPY supervisord.conf /etc/supervisord.conf +ENTRYPOINT ["/usr/bin/supervisord"] +CMD ["--configuration", "/etc/supervisord.conf", "--nodaemon"] diff --git a/tools/docker/iroha2-all-in-one/README.md b/tools/docker/iroha2-all-in-one/README.md new file mode 100644 index 00000000000..5e7ad1218f2 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/README.md @@ -0,0 +1,52 @@ +# iroha2-all-in-one + +An all in one Iroha V2 docker image as described in [Iroha 2 Documentation](https://hyperledger.github.io/iroha-2-docs/guide/build-and-install.html). +- This docker image is for `testing` and `development` only. +- **Do NOT use in production!** + +## Usage + +### Docker Compose +``` bash +./script-start-docker.sh +``` + +or manually: + +``` bash +docker-compose build && docker-compose up -d +``` + +### Docker +``` bash +# Build +DOCKER_BUILDKIT=1 docker build ./tools/docker/iroha2-all-in-one/ -t cactus_iroha2_all_in_one + +# Run +docker run --rm --name iroha2_aio_testnet --detach --privileged -p 8080:8080 -p 8180:8180 cactus_iroha2_all_in_one +``` + +## iroha_client_cli +- Image contains a proxy script for executing `iroha_client_cli` commands. +- Learn more from [Iroha 2 Bash tutorial](https://hyperledger.github.io/iroha-2-docs/guide/bash.html) +- Make sure the container is `healthy` before using the CLI. + +### Example + +``` bash +# List all assets +docker exec -ti iroha2_aio_testnet iroha_client_cli asset list all + +# Register new domain +docker exec -ti iroha2_aio_testnet iroha_client_cli domain register --id="looking_glass" + +# List all domains +docker exec -ti iroha2_aio_testnet iroha_client_cli domain list all +``` + +## Test Setup +- Use typescript [Iroha2TestLedger helper class](../../../packages/cactus-test-tooling/src/main/typescript/iroha/iroha2-test-ledger.ts) to start this ledger and use it from inside of automatic test. + +## Possible improvements +- Use specific iroha docker image tag when they are available. +- Freeze images like it's done in fabric-all-in-one, to speed up the startup (although it's pretty fast already). diff --git a/tools/docker/iroha2-all-in-one/docker-compose.yml b/tools/docker/iroha2-all-in-one/docker-compose.yml new file mode 100644 index 00000000000..fda7f0e6e7e --- /dev/null +++ b/tools/docker/iroha2-all-in-one/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.5" + +services: + iroha2-aio-testnet: + container_name: ${CACTUS_IROHA2_LEDGER_CONTAINER_NAME:-iroha2_aio_testnet} + image: ${CACTUS_IROHA2_LEDGER_IMAGE_NAME:-cactus_iroha2_all_in_one:1.0.0} + privileged: true + build: + context: ./ + dockerfile: Dockerfile + ports: + - "8080:8080" # Api + - "8180:8180" # Telemetry + networks: + - iroha2-network + +networks: + iroha2-network: + name: iroha2_aio_network + driver: bridge diff --git a/tools/docker/iroha2-all-in-one/healthcheck.sh b/tools/docker/iroha2-all-in-one/healthcheck.sh new file mode 100644 index 00000000000..f2fec563206 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/healthcheck.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +# Fail on first wrong command +set -e + +# First peer access point +API_URL="http://0.0.0.0:8080" +TELEMETRY_URL="http://0.0.0.0:8180" + +# Check health +wget -O- "${API_URL}/health" | grep -Fi 'Healthy' +echo "Status: Healthy" + +# Get blocks +blocks=$(wget -O- "${TELEMETRY_URL}/status" | jq -r '.blocks') +if [ blocks -lt 1]; then + echo "No genesis block yet..." + exit 1 +fi + +echo "Healtcheck OK." diff --git a/tools/docker/iroha2-all-in-one/iroha_client_cli.sh b/tools/docker/iroha2-all-in-one/iroha_client_cli.sh new file mode 100644 index 00000000000..888f497f8e3 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/iroha_client_cli.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh +# Simple helper that will proxy iroha_client_cli into CLI container. + +docker run \ + --rm \ + -v"${APP_ROOT}/configs/client_cli/config.json":"/config.json" \ + --network="host" \ + -ti \ + "hyperledger/iroha2:client-cli-${IROHA_CLI_VERSION}" \ + ./iroha_client_cli "$@" diff --git a/tools/docker/iroha2-all-in-one/script-start-docker.sh b/tools/docker/iroha2-all-in-one/script-start-docker.sh new file mode 100755 index 00000000000..654f4da29bd --- /dev/null +++ b/tools/docker/iroha2-all-in-one/script-start-docker.sh @@ -0,0 +1,2 @@ +echo "[process] start docker environment for Sawtooth testnet" +docker-compose build && docker-compose up -d diff --git a/tools/docker/iroha2-all-in-one/src/configs/client_cli/config.json b/tools/docker/iroha2-all-in-one/src/configs/client_cli/config.json new file mode 100644 index 00000000000..f4a289f0789 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/src/configs/client_cli/config.json @@ -0,0 +1,20 @@ +{ + "TORII_API_URL": "http://127.0.0.1:8080", + "TORII_TELEMETRY_URL": "http://127.0.0.1:8180", + "ACCOUNT_ID": { + "name": "alice", + "domain_id": { + "name": "wonderland" + } + }, + "BASIC_AUTH": { + "web_login": "mad_hatter", + "password": "ilovetea" + }, + "PUBLIC_KEY": "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0", + "PRIVATE_KEY": { + "digest_function": "ed25519", + "payload": "9ac47abf59b356e0bd7dcbbbb4dec080e302156a48ca907e47cb6aea1d32719e7233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + }, + "LOGGER_CONFIGURATION": {} +} diff --git a/tools/docker/iroha2-all-in-one/src/configs/peer/config.json b/tools/docker/iroha2-all-in-one/src/configs/peer/config.json new file mode 100644 index 00000000000..8f5ee24946e --- /dev/null +++ b/tools/docker/iroha2-all-in-one/src/configs/peer/config.json @@ -0,0 +1,46 @@ +{ + "TORII": { + "P2P_ADDR": "127.0.0.1:1337", + "API_URL": "127.0.0.1:8080" + }, + "SUMERAGI": { + "TRUSTED_PEERS": [ + { + "address": "127.0.0.1:1337", + "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + }, + { + "address": "127.0.0.1:1338", + "public_key": "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" + }, + { + "address": "127.0.0.1:1339", + "public_key": "ed0120faca9e8aa83225cb4d16d67f27dd4f93fc30ffa11adc1f5c88fd5495ecc91020" + }, + { + "address": "127.0.0.1:1340", + "public_key": "ed01208e351a70b6a603ed285d666b8d689b680865913ba03ce29fb7d13a166c4e7f1f" + } + ] + }, + "KURA": { + "INIT_MODE": "strict", + "BLOCK_STORE_PATH": "./blocks" + }, + "BLOCK_SYNC": { + "GOSSIP_PERIOD_MS": 10000, + "BATCH_SIZE": 4 + }, + "PUBLIC_KEY": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b", + "PRIVATE_KEY": { + "digest_function": "ed25519", + "payload": "282ed9f3cf92811c3818dbc4ae594ed59dc1a2f78e4241e31924e101d6b1fb831c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + }, + "GENESIS": { + "ACCOUNT_PUBLIC_KEY": "ed01204cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf", + "ACCOUNT_PRIVATE_KEY": { + "digest_function": "ed25519", + "payload": "d748e18ce60cb30dea3e73c9019b7af45a8d465e3d71bcc9a5ef99a008205e534cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf" + } + } +} diff --git a/tools/docker/iroha2-all-in-one/src/configs/peer/genesis.json b/tools/docker/iroha2-all-in-one/src/configs/peer/genesis.json new file mode 100644 index 00000000000..8a78005a5e7 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/src/configs/peer/genesis.json @@ -0,0 +1,97 @@ +{ + "transactions": [ + { + "isi": [ + { + "Register": { + "object": { + "Raw": { + "Identifiable": { + "NewDomain": { + "id": { + "name": "wonderland" + }, + "logo": null, + "metadata": {} + } + } + } + } + } + }, + { + "Register": { + "object": { + "Raw": { + "Identifiable": { + "NewAccount": { + "id": { + "name": "alice", + "domain_id": { + "name": "wonderland" + } + }, + "signatories": [ + "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + ], + "metadata": {} + } + } + } + } + } + }, + { + "Register": { + "object": { + "Raw": { + "Identifiable": { + "NewAssetDefinition": { + "id": { + "name": "rose", + "domain_id": { + "name": "wonderland" + } + }, + "value_type": "Quantity", + "mintable": "Infinitely", + "metadata": {} + } + } + } + } + } + }, + { + "Mint": { + "object": { + "Raw": { + "U32": 13 + } + }, + "destination_id": { + "Raw": { + "Id": { + "AssetId": { + "definition_id": { + "name": "rose", + "domain_id": { + "name": "wonderland" + } + }, + "account_id": { + "name": "alice", + "domain_id": { + "name": "wonderland" + } + } + } + } + } + } + } + } + ] + } + ] +} diff --git a/tools/docker/iroha2-all-in-one/src/docker-compose.yml b/tools/docker/iroha2-all-in-one/src/docker-compose.yml new file mode 100644 index 00000000000..0b37e4f4be1 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/src/docker-compose.yml @@ -0,0 +1,72 @@ +# Based on upstream https://github.com/hyperledger/iroha/blob/iroha2/docker-compose.yml + +version: "3.7" +services: + iroha0: + image: hyperledger/iroha2:${IROHA_VERSION} + environment: + TORII_P2P_ADDR: iroha0:1337 + TORII_API_URL: iroha0:8080 + TORII_TELEMETRY_URL: iroha0:8180 + IROHA_PUBLIC_KEY: "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + IROHA_PRIVATE_KEY: '{"digest_function": "ed25519", "payload": "282ed9f3cf92811c3818dbc4ae594ed59dc1a2f78e4241e31924e101d6b1fb831c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b"}' + SUMERAGI_TRUSTED_PEERS: '[{"address":"iroha0:1337", "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b"}, {"address":"iroha1:1338", "public_key": "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1"}, {"address": "iroha2:1339", "public_key": "ed0120faca9e8aa83225cb4d16d67f27dd4f93fc30ffa11adc1f5c88fd5495ecc91020"}, {"address": "iroha3:1340", "public_key": "ed01208e351a70b6a603ed285d666b8d689b680865913ba03ce29fb7d13a166c4e7f1f"}]' + ports: + - "1337:1337" + - "8080:8080" + - "8180:8180" + volumes: + - './configs/peer:/config' + init: true + command: ./iroha --submit-genesis + + iroha1: + image: hyperledger/iroha2:${IROHA_VERSION} + environment: + TORII_P2P_ADDR: iroha1:1338 + TORII_API_URL: iroha1:8081 + TORII_TELEMETRY_URL: iroha1:8181 + IROHA_PUBLIC_KEY: "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1" + IROHA_PRIVATE_KEY: '{"digest_function": "ed25519", "payload": "3bac34cda9e3763fa069c1198312d1ec73b53023b8180c822ac355435edc4a24cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1"}' + SUMERAGI_TRUSTED_PEERS: '[{"address":"iroha0:1337", "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b"}, {"address":"iroha1:1338", "public_key": "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1"}, {"address": "iroha2:1339", "public_key": "ed0120faca9e8aa83225cb4d16d67f27dd4f93fc30ffa11adc1f5c88fd5495ecc91020"}, {"address": "iroha3:1340", "public_key": "ed01208e351a70b6a603ed285d666b8d689b680865913ba03ce29fb7d13a166c4e7f1f"}]' + ports: + - "1338:1338" + - "8081:8081" + - "8181:8181" + volumes: + - './configs/peer:/config' + init: true + + iroha2: + image: hyperledger/iroha2:${IROHA_VERSION} + environment: + TORII_P2P_ADDR: iroha2:1339 + TORII_API_URL: iroha2:8082 + TORII_TELEMETRY_URL: iroha2:8182 + IROHA_PUBLIC_KEY: "ed0120faca9e8aa83225cb4d16d67f27dd4f93fc30ffa11adc1f5c88fd5495ecc91020" + IROHA_PRIVATE_KEY: '{"digest_function": "ed25519", "payload": "1261a436d36779223d7d6cf20e8b644510e488e6a50bafd77a7485264d27197dfaca9e8aa83225cb4d16d67f27dd4f93fc30ffa11adc1f5c88fd5495ecc91020"}' + SUMERAGI_TRUSTED_PEERS: '[{"address":"iroha0:1337", "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b"}, {"address":"iroha1:1338", "public_key": "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1"}, {"address": "iroha2:1339", "public_key": "ed0120faca9e8aa83225cb4d16d67f27dd4f93fc30ffa11adc1f5c88fd5495ecc91020"}, {"address": "iroha3:1340", "public_key": "ed01208e351a70b6a603ed285d666b8d689b680865913ba03ce29fb7d13a166c4e7f1f"}]' + ports: + - "1339:1339" + - "8082:8082" + - "8182:8182" + volumes: + - './configs/peer:/config' + init: true + + iroha3: + image: hyperledger/iroha2:${IROHA_VERSION} + environment: + TORII_P2P_ADDR: iroha3:1340 + TORII_API_URL: iroha3:8083 + TORII_TELEMETRY_URL: iroha3:8183 + IROHA_PUBLIC_KEY: "ed01208e351a70b6a603ed285d666b8d689b680865913ba03ce29fb7d13a166c4e7f1f" + IROHA_PRIVATE_KEY: '{"digest_function": "ed25519", "payload": "a70dab95c7482eb9f159111b65947e482108cfe67df877bd8d3b9441a781c7c98e351a70b6a603ed285d666b8d689b680865913ba03ce29fb7d13a166c4e7f1f"}' + SUMERAGI_TRUSTED_PEERS: '[{"address":"iroha0:1337", "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b"}, {"address":"iroha1:1338", "public_key": "ed0120cc25624d62896d3a0bfd8940f928dc2abf27cc57cefeb442aa96d9081aae58a1"}, {"address": "iroha2:1339", "public_key": "ed0120faca9e8aa83225cb4d16d67f27dd4f93fc30ffa11adc1f5c88fd5495ecc91020"}, {"address": "iroha3:1340", "public_key": "ed01208e351a70b6a603ed285d666b8d689b680865913ba03ce29fb7d13a166c4e7f1f"}]' + ports: + - "1340:1340" + - "8083:8083" + - "8183:8183" + volumes: + - './configs/peer:/config' + init: true diff --git a/tools/docker/iroha2-all-in-one/supervisord.conf b/tools/docker/iroha2-all-in-one/supervisord.conf new file mode 100644 index 00000000000..b00705be017 --- /dev/null +++ b/tools/docker/iroha2-all-in-one/supervisord.conf @@ -0,0 +1,23 @@ +[supervisord] +logfile = /var/log/supervisord.log +logfile_maxbytes = 50MB +logfile_backups=10 +loglevel = info + +[program:dockerd] +command=dockerd-entrypoint.sh +autostart=true +autorestart=true +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 + +[program:iroha2-test-ledger] +command=docker-compose -f %(ENV_APP_ROOT)s/docker-compose.yml up +autostart=true +autorestart=false +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0