diff --git a/.github/actions/run-ee-server/action.yml b/.github/actions/run-ee-server/action.yml index d4f492b439..63e06162a5 100644 --- a/.github/actions/run-ee-server/action.yml +++ b/.github/actions/run-ee-server/action.yml @@ -28,10 +28,14 @@ inputs: required: false description: 'docker-host, separate-docker-container, "remote-connection" via DOCKER_HOST' default: 'docker-host' - docker-run-extra-flags: + env-vars: required: false description: Used to disable server features - default: '' + default: 'STRONG_CONSISTENCY=1 SECURITY=1 MUTUAL_TLS=1' +outputs: + container-name: + description: 'Server container name' + value: ${{ steps.get-container-name.outputs.container-name }} runs: using: "composite" @@ -49,58 +53,23 @@ runs: - run: echo IMAGE_FULL_NAME=${{ inputs.registry-name }}/${{ inputs.image-name }}:${{ inputs.server-tag }} >> $GITHUB_ENV shell: bash - - run: echo NEW_IMAGE_FULL_NAME=${{ env.IMAGE_FULL_NAME }}-python-client-testing >> $GITHUB_ENV - shell: bash - - # QE images are not multi-platform and only support a single platform. - - name: Allow building images from different platforms other than the native one - uses: docker/setup-qemu-action@v3 - - # macOS Github runners and Windows self-hosted runners don't have buildx installed by default - - if: ${{ runner.os == 'Windows' || runner.os == 'macOS' }} - uses: docker/setup-buildx-action@v3 - - run: echo CA_CERT_FILE_NAME="ca.cer" >> $GITHUB_ENV shell: bash - run: echo TLS_PORT="4333" >> $GITHUB_ENV shell: bash - - name: Build Aerospike server Docker image for testing - # We enable TLS standard authentication to verify that the OpenSSL library bundled with the wheel works - # You can manually verify this by enabling debug logging in the client and checking that the server certificate was verified - uses: docker/build-push-action@v6 - with: - # Don't want to use default Git context or else it will clone the whole Python client repo again - context: .github/workflows/docker-build-context - build-args: | - SERVER_IMAGE=${{ env.IMAGE_FULL_NAME }} - TLS_PORT=${{ env.TLS_PORT }} - tags: ${{ env.NEW_IMAGE_FULL_NAME }} - # setup-buildx-action configures Docker to use the docker-container build driver - # This driver doesn't publish an image locally by default - # so we have to manually enable it - load: true - - - run: echo SERVER_CONTAINER_NAME="aerospike" >> $GITHUB_ENV - shell: bash - - # Confirmed via logs that input string will not be quoted in bash - - run: docker run -d ${{ inputs.docker-run-extra-flags }} --name ${{ env.SERVER_CONTAINER_NAME }} -p 3000:3000 -p ${{ env.TLS_PORT }}:${{ env.TLS_PORT }} ${{ env.NEW_IMAGE_FULL_NAME }} - shell: bash - - name: 'macOS: install timeout command' if: ${{ runner.os == 'macOS' }} run: brew install coreutils shell: bash - - name: Wait for container to be healthy - run: | - timeout 30s bash -c 'until [[ "$(docker inspect -f {{.State.Health.Status}} ${{ env.SERVER_CONTAINER_NAME }})" == "healthy" ]]; do sleep 0.1; done' + - run: ${{ inputs.env-vars }} bash ./run-ee-server.bash + working-directory: .github/workflows/docker-setup shell: bash - # For debugging - - run: docker logs ${{ env.SERVER_CONTAINER_NAME }} + - id: get-container-name + run: echo container-name=aerospike >> $GITHUB_OUTPUT shell: bash # Configure tests @@ -129,9 +98,9 @@ runs: crudini --existing=param --set config.conf enterprise-edition password ${{ env.SUPERUSER_NAME_AND_PASSWORD }} crudini --set config.conf tls enable true # Cannot use abs path because config.conf is copied into Docker container during cibuildwheel tests - crudini --set config.conf tls cafile ../.github/workflows/docker-build-context/${{ env.CA_CERT_FILE_NAME }} - crudini --set config.conf tls keyfile ../.github/workflows/docker-build-context/client.pem - crudini --set config.conf tls certfile ../.github/workflows/docker-build-context/client.cer + crudini --set config.conf tls cafile ../.github/workflows/docker-setup/${{ env.CA_CERT_FILE_NAME }} + crudini --set config.conf tls keyfile ../.github/workflows/docker-setup/client.pem + crudini --set config.conf tls certfile ../.github/workflows/docker-setup/client.cer working-directory: test shell: bash @@ -152,7 +121,7 @@ runs: - name: Set IP address to Docker container for the server if: ${{ inputs.where-is-client-connecting-from == 'separate-docker-container' }} - run: echo SERVER_IP=$(docker container inspect -f '{{ .NetworkSettings.IPAddress }}' ${{ env.SERVER_CONTAINER_NAME }}) >> $GITHUB_ENV + run: echo SERVER_IP=$(docker container inspect -f '{{ .NetworkSettings.IPAddress }}' ${{ steps.get-container-name.outputs.container-name }}) >> $GITHUB_ENV shell: bash - name: Invalid input @@ -161,7 +130,9 @@ runs: shell: bash - name: Get cluster name - run: echo CLUSTER_NAME=$(docker exec ${{ env.SERVER_CONTAINER_NAME }} asinfo -v "get-config:context=service" -l | grep -i cluster-name | cut -d = -f 2) >> $GITHUB_ENV + # Here we just assume that security is enabled. Whenever the dev tests are run with run-ee-server.yml, security is enabled anyways. + # TODO: but this needs to be fixed eventually for other clients that disable security for their tests. + run: echo CLUSTER_NAME=$(docker run --rm --network host aerospike/aerospike-tools asinfo -U admin -P admin -v "get-config:context=service" -l | grep -i cluster-name | cut -d = -f 2) >> $GITHUB_ENV shell: bash - name: Set EE server's IP address diff --git a/.github/actions/wait-for-ce-server-to-start/action.yml b/.github/actions/wait-for-ce-server-to-start/action.yml index 5f0057ab83..d18abefc40 100644 --- a/.github/actions/wait-for-ce-server-to-start/action.yml +++ b/.github/actions/wait-for-ce-server-to-start/action.yml @@ -12,15 +12,11 @@ runs: - run: echo WAIT_SCRIPT_FILE_NAME=wait-for-as-server-to-start.bash >> $GITHUB_ENV shell: bash - - name: Copy wait script in server Docker container - run: docker cp $WAIT_SCRIPT_FILE_NAME ${{ inputs.container-name }}:/ - working-directory: .github/workflows/docker-build-context - shell: bash - # There is no healthcheck by default in the server CE Docker image. # We can just reuse our wait script. because the CE server should be ready after it finishes. - name: Wait for EE server to start # Composite actions doesn't support step-level timeout-minutes, so we use timeout command. # Call bash shell explicitly since timeout uses "sh" shell by default, for some reason - run: docker exec ${{ inputs.container-name }} timeout 30s bash /$WAIT_SCRIPT_FILE_NAME + run: timeout 30s bash $WAIT_SCRIPT_FILE_NAME shell: bash + working-directory: .github/workflows/docker-setup diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index adee326bb9..e78e28efae 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -335,8 +335,6 @@ jobs: - run: | # Checks that server started up properly docker logs aerospike - # Prints the actual logs from asd - docker exec aerospike cat /var/log/aerospike/aerospike.log if: ${{ always() && env.RUN_INTEGRATION_TESTS_IN_CIBW == 'true' }} shell: bash diff --git a/.github/workflows/docker-build-context/Dockerfile b/.github/workflows/docker-build-context/Dockerfile deleted file mode 100644 index 0677f0515f..0000000000 --- a/.github/workflows/docker-build-context/Dockerfile +++ /dev/null @@ -1,114 +0,0 @@ -ARG SERVER_IMAGE=aerospike/aerospike-server-enterprise - -FROM $SERVER_IMAGE AS shared - -# Define our variables once, here. -# Shared between build stages to generate the server certificate and to build final server image - -# Use our own conf file for the final server image. -# The entrypoint script will use these templates to generate conf files -ARG AEROSPIKE_CONF_FILE_NAME=aerospike-dev.conf.jinja -# Our entrypoint script needs to pass this custom aerospike conf to the server binary -ENV AEROSPIKE_CONF_PATH=/etc/aerospike/$AEROSPIKE_CONF_FILE_NAME -COPY $AEROSPIKE_CONF_FILE_NAME $AEROSPIKE_CONF_PATH - -ARG SERVER_KEY_FILE_NAME=server.pem -ARG SERVER_CERT_FILE_NAME=server.cer - -FROM shared AS enable-security - -WORKDIR /opt/aerospike/smd - -# security.smd was generated manually by -# 1. Starting a new Aerospike EE server using Docker -# 2. Creating the superuser user -# 3. Copying /opt/aerospike/smd/security.smd from the container and committing it to this repo -# This file should always work -# TODO: generate this automatically, somehow. -COPY security.smd . - -# Do this so we don't have to pass in user credentials every time we use Aerospike tools in the entrypoint script -COPY astools.conf.jinja /etc/aerospike/ - -FROM enable-security AS enable-strong-consistency - -COPY roster.smd . - -# Use a separate build stage to generate certs since we don't want openssl bundled in the final image - -FROM shared AS generate-server-cert-for-tls - -RUN apt update -RUN apt install -y openssl - -# Need to pass cluster name to openssl command -ARG CLUSTER_NAME_FILE_NAME=cluster_name -RUN grep -Eo "cluster-name [a-z]+" $AEROSPIKE_CONF_PATH | awk '{print $2}' > $CLUSTER_NAME_FILE_NAME - -# Generate server private key and CSR - -ARG SERVER_CSR_FILE_NAME=server.csr -RUN openssl req -newkey rsa:4096 -keyout $SERVER_KEY_FILE_NAME -nodes -new -out $SERVER_CSR_FILE_NAME -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=$(cat $CLUSTER_NAME_FILE_NAME)" - -# Send CSR to CA. CA will output a server certificate -# We use an external CA because we want the client to use that same CA to verify the server certificate when connecting -# via TLS. -# By default we use the "dummy" CA cert and key that are committed in the repo -ARG CA_KEY_FILE_NAME=ca.pem -ARG CA_CERT_FILE_NAME=ca.cer -COPY $CA_KEY_FILE_NAME . -COPY $CA_CERT_FILE_NAME . -RUN openssl x509 -req -in $SERVER_CSR_FILE_NAME -CA $CA_CERT_FILE_NAME -CAkey $CA_KEY_FILE_NAME -out $SERVER_CERT_FILE_NAME - -FROM enable-strong-consistency AS enable-tls - -ARG SSL_WORKING_DIR=/etc/ssl -WORKDIR $SSL_WORKING_DIR -ARG SERVER_KEY_INSTALL_PATH=$SSL_WORKING_DIR/private/$SERVER_KEY_FILE_NAME -ARG SERVER_CERT_INSTALL_PATH=$SSL_WORKING_DIR/certs/$SERVER_CERT_FILE_NAME -ARG CA_CERT_INSTALL_PATH=$SSL_WORKING_DIR/certs/$CA_CERT_FILE_NAME - -COPY --from=generate-server-cert-for-tls $SERVER_KEY_FILE_NAME $SERVER_KEY_INSTALL_PATH -COPY --from=generate-server-cert-for-tls $SERVER_CERT_FILE_NAME $SERVER_CERT_INSTALL_PATH -COPY --from=generate-server-cert-for-tls $CA_CERT_FILE_NAME $CA_CERT_INSTALL_PATH - -# User can set their own TLS port if they want -ARG TLS_PORT=4333 -EXPOSE $TLS_PORT - -# Could not figure out how to stop Windows self-hosted runners -# from changing line endings for wait-for-as-server-to-start.bash. -# .gitattributes does not work, neither does changing git-config settings. -# This should work for now. -FROM ubuntu:24.04 AS dos2unix - -# Run our own set up steps after server finishes starting up -WORKDIR /my-scripts -COPY entrypoint-finalize-setup.bash wait-for-as-server-to-start.bash . -# Both Github Actions and the entrypoint script can reuse this wait script -RUN apt update -RUN apt install -y dos2unix -RUN dos2unix *.bash - -FROM enable-tls AS finish-setup - -# We need to install jinja2 in our server image -RUN apt update -# Verified that installing these should not interfere with the server -# There are a few packages being upgraded with this command, but it should be ok -RUN apt install -y python3 python3-pip python3-venv -WORKDIR / -# We use a venv to handle newer Python versions (3.12+) -# Those versions require a flag to "break system packages" when installing pip packages directly to the system -RUN python3 -m venv .venv -ENV PATH="/.venv/bin:$PATH" -RUN python3 -m pip install jinja2 -COPY toggle-features-in-conf-files.py . - -ENV HEALTHCHECK_FILE_PATH=/finalized -HEALTHCHECK --interval=1s --timeout=1s CMD test -f $HEALTHCHECK_FILE_PATH - -COPY --from=dos2unix /my-scripts/* . - -# Entrypoint is in root dir -ENTRYPOINT ["/usr/bin/as-tini-static", "-r", "SIGUSR1", "-t", "SIGTERM", "--", "/entrypoint-finalize-setup.bash"] diff --git a/.github/workflows/docker-build-context/README.md b/.github/workflows/docker-build-context/README.md deleted file mode 100644 index 02fb2a79dc..0000000000 --- a/.github/workflows/docker-build-context/README.md +++ /dev/null @@ -1,50 +0,0 @@ -## About - -This Docker image deploys an Aerospike server with these features enabled by default: -- Strong consistency -- Security -- TLS mutual authentication - -To disable any of the above features, start up the Docker container with any combination of these environment variables set: -```sh -# The value of the environment variable doesn't actually matter -NO_SC=1 -NO_SECURITY=1 -NO_TLS=1 -``` - -## Note - -The CA certificate in this folder is a fake certificate used both by the client and server for connecting via TLS. - -The private key was generated along with this CA certificate, and is only used to sign the server certificate used in testing. - -## How to build - -Build the image: -```sh -docker build -f Dockerfile . --tag aerospike/aerospike-server-enterprise:latest-sc -``` - -Then run the image: -```sh -docker run -d -p 3000:3000 -p 4333:4333 --name aerospike aerospike/aerospike-server-enterprise:latest-sc -``` - -The container should take about 5 seconds to become "healthy": - -```sh -% docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -376dbaf0f0a8 aerospike/aerospike-server-enterprise:latest-sc "/usr/bin/as-tini-st…" 1 second ago Up 1 second (health: starting) 0.0.0.0:3000->3000/tcp, 0.0.0.0:4333->4333/tcp, 3001-3002/tcp aerospike -``` - -This shows the results of the entrypoint script which sets up strong consistency: -```sh -docker logs aerospike -``` - -The server logs are no longer printed to stdout; they are printed to this file in the container instead: -```sh -docker exec aerospike head -n 200 /var/log/aerospike/aerospike.log -``` diff --git a/.github/workflows/docker-build-context/aerospike-dev.conf.jinja b/.github/workflows/docker-build-context/aerospike-dev.conf.jinja deleted file mode 100644 index 23a308b70b..0000000000 --- a/.github/workflows/docker-build-context/aerospike-dev.conf.jinja +++ /dev/null @@ -1,92 +0,0 @@ -# Aerospike database configuration file -# This template sets up a single-node, single namespace developer environment. -# -# Alternatively, you can pass in your own configuration file. -# You can see more examples at -# https://github.com/aerospike/aerospike-server/tree/master/as/etc - -# This stanza must come first. -service { - {% if sc is true %} - node-id a1 - {% endif %} - feature-key-file /etc/aerospike/features.conf - cluster-name docker -} - -logging { - # Save log files so we can see entrypoint output alone via stdout - file /var/log/aerospike/aerospike.log { - context any info - } -} - -network { - {% if tls is true %} - tls docker { - key-file /etc/ssl/private/server.pem - cert-file /etc/ssl/certs/server.cer - ca-file /etc/ssl/certs/ca.cer - } - {% endif %} - service { - {% if tls is true %} - tls-port 4333 - tls-authenticate-client any - tls-name docker - {% endif %} - address any - port 3000 - - # Uncomment the following to set the 'access-address' parameter to the - # IP address of the Docker host. This will the allow the server to correctly - # publish the address which applications and other nodes in the cluster to - # use when addressing this node. - # access-address - } - - heartbeat { - # mesh is used for environments that do not support multicast - mode mesh - address local - port 3002 - interval 150 - timeout 10 - } - - fabric { - # Intra-cluster communication port (migrates, replication, etc) - # default to same address in 'service' - address local - port 3001 - } - -} - -namespace test { - {% if sc is true %} - strong-consistency-allow-expunge true - strong-consistency true - {% endif %} - replication-factor 1 - default-ttl 2592000 - nsup-period 120 - - storage-engine device { - # For 'storage-engine memory' with 'device' or 'file' backing, we - # recommend having multiple devices (eight is recommended). One is used - # here for backward compatibility. - file /opt/aerospike/data/test.dat - filesize 4G - read-page-cache true - } -} - -{% if security is true %} -security { - enable-quotas true - log { - report-violation true - } -} -{% endif %} diff --git a/.github/workflows/docker-build-context/astools.conf.jinja b/.github/workflows/docker-build-context/astools.conf.jinja deleted file mode 100644 index 97eb382808..0000000000 --- a/.github/workflows/docker-build-context/astools.conf.jinja +++ /dev/null @@ -1,7 +0,0 @@ -[cluster] -# If security is enabled: -# - We need special permissions to run security tests, so we can't use the default admin user. -# - And if SC is also enabled, we need the superuser to finish configuring SC in our entrypoint script -{% for key in ["user", "password"] %} -{{ key }} = "{% if security is true %}superuser{% endif %}" -{% endfor %} diff --git a/.github/workflows/docker-build-context/entrypoint-finalize-setup.bash b/.github/workflows/docker-build-context/entrypoint-finalize-setup.bash deleted file mode 100755 index 350b799aec..0000000000 --- a/.github/workflows/docker-build-context/entrypoint-finalize-setup.bash +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -# Entrypoint script used by our custom EE server image - -# Bash required for using job control - -set -x -set -m -set -e - -python3 toggle-features-in-conf-files.py - -# Disable features if needed -cd /opt/aerospike/smd -if [[ -n "$NO_SECURITY" ]]; then - rm security.smd -fi -if [[ -n "$NO_SC" ]]; then - rm roster.smd -fi - -# We have to remove the .jinja part -asd --fgdaemon --config-file ${AEROSPIKE_CONF_PATH%.*} & - -# We don't need to timeout here. -# If the wait script runs forever, users running the container manually will know that -# the container is "unhealthy" by checking the status -# And our Github Actions code will wait for the container to be healthy or timeout after 30 seconds. -bash /wait-for-as-server-to-start.bash - -if [[ -z "$NO_SC" ]]; then - # Finish setting up strong consistency - asadm --enable --execute "manage revive ns test" - asadm --enable --execute "manage recluster" -fi - -# Allows HEALTHCHECK to report this container as healthy, now -touch $HEALTHCHECK_FILE_PATH - -fg diff --git a/.github/workflows/docker-build-context/roster.smd b/.github/workflows/docker-build-context/roster.smd deleted file mode 100644 index 66daed5f6b..0000000000 --- a/.github/workflows/docker-build-context/roster.smd +++ /dev/null @@ -1,12 +0,0 @@ -[ - [ - 97107025374203, - 1 - ], - { - "key": "test", - "value": "a1", - "generation": 1, - "timestamp": 465602976982 - } -] diff --git a/.github/workflows/docker-build-context/security.smd b/.github/workflows/docker-build-context/security.smd deleted file mode 100644 index 9c530d5143..0000000000 --- a/.github/workflows/docker-build-context/security.smd +++ /dev/null @@ -1,48 +0,0 @@ -[ - [ - 162276881999406, - 14 - ], - { - "key": "admin|P", - "value": "$2a$10$7EqJtq98hPqEX7fNZaFWoO1mVO/4MLpGzsqojz6E9Gef6iXDjXdDa", - "generation": 1, - "timestamp": 0 - }, - { - "key": "admin|R|user-admin", - "value": "", - "generation": 1, - "timestamp": 0 - }, - { - "key": "superuser|P", - "value": "$2a$10$7EqJtq98hPqEX7fNZaFWoOZX0o4mZCBUwvzt/iecIcG4JaDOC41zK", - "generation": 3, - "timestamp": 458774922440 - }, - { - "key": "superuser|R|read-write-udf", - "value": "", - "generation": 3, - "timestamp": 458774922441 - }, - { - "key": "superuser|R|sys-admin", - "value": "", - "generation": 3, - "timestamp": 458774922442 - }, - { - "key": "superuser|R|user-admin", - "value": "", - "generation": 3, - "timestamp": 458774922442 - }, - { - "key": "superuser|R|data-admin", - "value": null, - "generation": 2, - "timestamp": 458774718056 - } -] diff --git a/.github/workflows/docker-build-context/toggle-features-in-conf-files.py b/.github/workflows/docker-build-context/toggle-features-in-conf-files.py deleted file mode 100644 index f4972fff9c..0000000000 --- a/.github/workflows/docker-build-context/toggle-features-in-conf-files.py +++ /dev/null @@ -1,37 +0,0 @@ -from jinja2 import Environment, FileSystemLoader -import os - -AEROSPIKE_CONF_PATH = os.getenv("AEROSPIKE_CONF_PATH") -AEROSPIKE_CONF_FOLDER = os.path.dirname(AEROSPIKE_CONF_PATH) -env = Environment(loader = FileSystemLoader(AEROSPIKE_CONF_FOLDER), trim_blocks=True, lstrip_blocks=True) -# By default, all features enabled -# Disable feature if env var is present -env_vars = [ - "NO_SECURITY", - "NO_TLS", - "NO_SC" -] -kwargs = {} -for env_var in env_vars: - value = os.getenv(env_var) - # Determine which features to enable in Jinja template - # Our jinja template checks if a feature is True in order to set a feature - # e.g env var "NO_SC" -> Jinja variable "sc" - jinja_var = env_var.replace("NO_", "").lower() - # Enable a feature if env var is not set - # e.g If env var NO_SC is not set, set Jinja variable "sc" to True - # otherwise, if NO_SC is set, set "sc" to False - kwargs[jinja_var] = value is None - -# For debugging: print which features are enabled at runtime (entrypoint script will run this) -print(kwargs) - -templates = [ - "astools.conf.jinja", - "aerospike-dev.conf.jinja" -] -for tmpl_name in templates: - template = env.get_template(tmpl_name) - output = template.render(**kwargs) - with open(f"{AEROSPIKE_CONF_FOLDER}/{tmpl_name}".removesuffix(".jinja"), "w") as f: - f.write(output) diff --git a/.github/workflows/docker-setup/README.md b/.github/workflows/docker-setup/README.md new file mode 100644 index 0000000000..f9f898d749 --- /dev/null +++ b/.github/workflows/docker-setup/README.md @@ -0,0 +1,39 @@ +## About + +The `run-ee-server.bash` script deploys an Aerospike server with the ability to enable these server features: +- Strong consistency +- Security +- TLS mutual authentication + +To enable any of the above features, run this script with any combination of these environment variables set to: +```sh +STRONG_CONSISTENCY=1 +SECURITY=1 +MUTUAL_TLS=1 +``` + +## Note + +- The CA certificate in this folder is a fake certificate used both by the client and server for connecting via TLS. The +private key was generated along with this CA certificate, and was only used to sign the server certificate used in testing. +- The server certificate and private key is also provided here, so the setup script doesn't have to regenerate it every +time it gets run. +- The client certificate and private key is also embedded here so the dev tests can reuse them when mutual authentication is enabled. +- We support TLS in order to check that the OpenSSL library bundled with our wheels are working properly. You can +manually verify this by enabling debug logging in the client and checking that the server certificate was verified + +## Example of how to run + +With only strong consistency enabled: +```sh +STRONG_CONSISTENCY=1 ./run-ee-server.bash +``` + +The script will return once the server is ready for testing. + +## How to teardown + +Run this script: +```sh +./teardown-ee-server.bash +``` diff --git a/.github/workflows/docker-setup/aerospike-dev.yaml b/.github/workflows/docker-setup/aerospike-dev.yaml new file mode 100644 index 0000000000..98b6696f61 --- /dev/null +++ b/.github/workflows/docker-setup/aerospike-dev.yaml @@ -0,0 +1,55 @@ +# *** Aerospike Metadata Generated by Asconfig *** +# aerospike-server-version: 8.1.0 +# asconfig-version: 0.19.0 +# https: //github.com/aerospike/aerospike-server/tree/master/as/etc +# *** End Aerospike Metadata *** + +logging: + - name: console + # Debug level logs can be helpful for debugging failing tests + any: debug +namespaces: + - default-ttl: 2592000 + name: test + nsup-period: 120 + replication-factor: 1 + # TODO: consider moving to memory storage to reduce time taken to run tests + storage-engine: + files: + - /opt/aerospike/data/test.dat + filesize: 4294967296 + read-page-cache: true + type: device + strong-consistency: "true" + strong-consistency-allow-expunge: "true" +network: + fabric: + addresses: + - local + port: 3001 + heartbeat: + addresses: + - local + interval: 150 + mode: mesh + port: 3002 + timeout: 10 + service: + addresses: + - any + port: 3000 + tls-authenticate-client: any + tls-name: docker + tls-port: "4333" + tls: + - ca-file: /etc/ssl/certs/ca.cer + cert-file: /etc/ssl/certs/server.cer + key-file: /etc/ssl/private/server.pem + name: docker +service: + cluster-name: docker + feature-key-file: /etc/aerospike/features.conf +security: + enable-quotas: "true" + log: + report-violation: "true" diff --git a/.github/workflows/docker-build-context/ca.cer b/.github/workflows/docker-setup/ca.cer similarity index 100% rename from .github/workflows/docker-build-context/ca.cer rename to .github/workflows/docker-setup/ca.cer diff --git a/.github/workflows/docker-build-context/ca.pem b/.github/workflows/docker-setup/ca.pem similarity index 100% rename from .github/workflows/docker-build-context/ca.pem rename to .github/workflows/docker-setup/ca.pem diff --git a/.github/workflows/docker-build-context/client.cer b/.github/workflows/docker-setup/client.cer similarity index 100% rename from .github/workflows/docker-build-context/client.cer rename to .github/workflows/docker-setup/client.cer diff --git a/.github/workflows/docker-build-context/client.pem b/.github/workflows/docker-setup/client.pem similarity index 100% rename from .github/workflows/docker-build-context/client.pem rename to .github/workflows/docker-setup/client.pem diff --git a/.github/workflows/docker-setup/run-ee-server.bash b/.github/workflows/docker-setup/run-ee-server.bash new file mode 100755 index 0000000000..b9f5b6bb10 --- /dev/null +++ b/.github/workflows/docker-setup/run-ee-server.bash @@ -0,0 +1,113 @@ +#!/bin/bash + +set -e +set -x + +# Prevent Windows shell from mangling paths +export MSYS_NO_PATHCONV=1 + +# Input defaults + +BASE_IMAGE=${BASE_IMAGE:-"aerospike/aerospike-server-enterprise"} +# Server features +MUTUAL_TLS=${MUTUAL_TLS:-"0"} +SECURITY=${SECURITY:-"0"} +STRONG_CONSISTENCY=${STRONG_CONSISTENCY:-"0"} + +# End inputs + +VOLUME_NAME=aerospike-conf-vol +docker volume create $VOLUME_NAME + +volume_dest_folder="/workdir" +container_name_for_populating_volume="container_for_populating_volume" + +docker run --name $container_name_for_populating_volume --rm -v $VOLUME_NAME:$volume_dest_folder -d alpine tail -f /dev/null +docker cp ./ $container_name_for_populating_volume:$volume_dest_folder +docker stop $container_name_for_populating_volume + +aerospike_yaml_file_name="aerospike-dev.yaml" + +call_from_yq_container() { + # alpine container's process is run as root user + # So the files copied into the named volume will also be owned by root + # Since these files only have write permission for the owner (root), + # We also need to run yq container as root in order to write to the yaml file in this volume. + docker run --rm --user root -v $VOLUME_NAME:$volume_dest_folder mikefarah/yq "$1" -i ${volume_dest_folder}/${aerospike_yaml_file_name} +} + +# del() operations are idempotent +if [[ "$MUTUAL_TLS" == "1" ]]; then + call_from_yq_container ".network.service.tls-authenticate-client = \"any\"" + call_from_yq_container ".network.service.tls-name = \"docker\"" + call_from_yq_container ".network.service.tls-port = \"4333\"" + call_from_yq_container ".network.tls[0].ca-file = \"$volume_dest_folder/ca.cer\"" + call_from_yq_container ".network.tls[0].cert-file = \"$volume_dest_folder/server.cer\"" + call_from_yq_container ".network.tls[0].key-file = \"$volume_dest_folder/server.pem\"" + call_from_yq_container ".network.tls[0].name = \"docker\"" +else + call_from_yq_container "del(.network.service.tls-authenticate-client)" + call_from_yq_container "del(.network.service.tls-name)" + call_from_yq_container "del(.network.service.tls-port)" + call_from_yq_container "del(.network.tls)" +fi + +if [[ "$SECURITY" == "1" ]]; then + call_from_yq_container ".security.enable-quotas = \"true\"" + call_from_yq_container ".security.log.report-violation = \"true\"" +else + call_from_yq_container "del(.security)" +fi + +if [[ "$STRONG_CONSISTENCY" == "1" ]]; then + call_from_yq_container ".namespaces[0].strong-consistency = \"true\"" + call_from_yq_container ".namespaces[0].strong-consistency-allow-expunge = \"true\"" +else + call_from_yq_container ".namespaces[0].strong-consistency = \"false\"" + call_from_yq_container ".namespaces[0].strong-consistency-allow-expunge = \"false\"" +fi + +# We want to save our aerospike.conf in this directory. +call_from_tools_container="docker run --rm -v $VOLUME_NAME:$volume_dest_folder --network host aerospike/aerospike-tools" + +aerospike_conf_name=aerospike.conf +$call_from_tools_container asconfig convert -f ${volume_dest_folder}/${aerospike_yaml_file_name} -o ${volume_dest_folder}/$aerospike_conf_name + +# Generate server private key and CSR +# openssl req -newkey rsa:4096 -keyout server.pem -nodes -new -out server.csr -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=docker" +# Generate server cert +# openssl x509 -req -in server.csr -CA ca.cer -CAkey ca.pem -out server.cer + +# Some Docker containers may have a lower max fd limit than the server default +# I'm not sure how to get the max fd limit of a container without a shell +docker run --ulimit nofile=15000 -d --rm --name aerospike -p 4333:4333 -p 3000:3000 \ + -v $VOLUME_NAME:$volume_dest_folder \ + $BASE_IMAGE --config-file $volume_dest_folder/$aerospike_conf_name + +if [[ "$SECURITY" == "1" ]]; then + export SECURITY_FLAGS="-U admin -P admin" +fi + +# docker logs -f aerospike + +# TODO: make sure server container is alive or waiting is pointless +./wait-for-as-server-to-start.bash + +# Set up security +superuser_name_and_password=superuser +if [[ "$SECURITY" == "1" ]]; then + $call_from_tools_container asadm $SECURITY_FLAGS --enable --execute "manage acl \ + create user $superuser_name_and_password password $superuser_name_and_password \ + roles read-write-udf, sys-admin, user-admin, data-admin" +fi + +# Strong consistency +# Set up roster +if [[ "$STRONG_CONSISTENCY" == "1" ]]; then + if [[ "$SECURITY" == "1" ]]; then + # Admin user doesn't have enough permissions to set up the roster and recluster + SECURITY_FLAGS="-U $superuser_name_and_password -P $superuser_name_and_password" + fi + $call_from_tools_container asadm $SECURITY_FLAGS --enable --execute "manage roster stage observed ns test" + $call_from_tools_container asadm $SECURITY_FLAGS --enable --execute "manage recluster" +fi diff --git a/.github/workflows/docker-setup/server.cer b/.github/workflows/docker-setup/server.cer new file mode 100644 index 0000000000..24c956eff2 --- /dev/null +++ b/.github/workflows/docker-setup/server.cer @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEwzCCA6ugAwIBAgIUMshYh0w4mf1+jD8rmL4madGudc8wDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI +Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55 +U2VjdGlvbk5hbWUxEjAQBgNVBAMMCW15ZHVtbXljYTAeFw0yNTExMDQxNzE0MTZa +Fw0yNTEyMDQxNzE0MTZaMHgxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5h +bWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkG +A1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMQ8wDQYDVQQDDAZkb2NrZXIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC5quOeCVbD+k/jhHAoSl7vc2Dg5tpu +1lOSihoKJuBYCuOJBjzQeoG31ATnhTG76L4hCm+coH5MCaQxAhm8Bl7izEIyJMLW +byaIAIr5O03UWz/LIJSo5404Bu0qlI5jcpJDMpPvVkjnFBjg4Y/WESvUG9off3si +OImmdQ0EIK0smz4+Y8N3frKkclLr8IjG3zlnuZ+gr0bQ06lCCNoExsOcfOghHrAl +XX5Ep+BTY3QW52M9VEeNT4OEvHd4fTvhy1jZjlno/dcrYGuaZLjXKdlJjSpDZCiw +92xYZzSp76d84IGte1LE4g3n1cU4h/Stfe7UPs692+Wz7P1CGHwPiJ1sz8iZU/HB +5gUMFSz4Vk/pTJNY2XHNGcKIH/Rnd1EsLqgJ4Fugtd2/PQZPXcXbMNsZ8nUhgtQn +DlOqrkDGouQYDlYUKiy3WLIwkFBTmuDasu/qUuIoGL9fWiLNz6VScirxI3/o0ZBc +Oz73EDMqzT1S6PCY6lFLcOt6VGPUVQp5F1LIsbi34YC8xBVEfUHP++bVqEkOL+1L +zWXYfhT81TSZVqQo6gaLNrE+Urj3L4n9i9QbPawnWbJ4ZEMgs+z6Rn6Hgrbg/2Wi +bgqfz9FsojGlG3gk6oy6xR72KrOcIPa2BDnzwAh2LflS9qllbYksdynNzOyzW2vp +TMG8YM36QrCPZwIDAQABo0IwQDAdBgNVHQ4EFgQUBy3cVqMvvd38sPPZQ1w5Ct2e +FFMwHwYDVR0jBBgwFoAUXBu61isVNrhyc/WRDMErWYuKGOQwDQYJKoZIhvcNAQEL +BQADggEBAB15cqCeb+kv7sNTGdlUhQ2q2IhLXmMfcDf9FwylbOqTnKwF/hP7qeMN +RFuyjBggb1HGeSJHAFnxUu2YKqh8FPqgh6Jv52rx7yLK7IRCH8O8637TGEE6V02H +FF1CUdYh8/zdaim1ppXIQIQ5I2cjfdaEalhc8g8iqkkFHGLTBmkXR4cungBQwRFl +mEBvz7fHWN0bAT0ndJS5Blr8/j2WJNiUbIOljhlPqHln7umQC0+cIFcapfNflxOx +OvveDDkKiOOIaLF//oERC8SxpfCQPaI6B3fPCyXbjhQmQK/BOlbC7xD7LIWE8DiS +SWFQaUThUaReC5mcgmZQ6hafYIfO4nI= +-----END CERTIFICATE----- diff --git a/.github/workflows/docker-setup/server.pem b/.github/workflows/docker-setup/server.pem new file mode 100644 index 0000000000..0175740af5 --- /dev/null +++ b/.github/workflows/docker-setup/server.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +4f7/1IN8kpQ8H9uar8/YmPYyP+jqXOk= +-----END PRIVATE KEY----- diff --git a/.github/workflows/docker-setup/teardown-ee-server.bash b/.github/workflows/docker-setup/teardown-ee-server.bash new file mode 100755 index 0000000000..0baccb8f9c --- /dev/null +++ b/.github/workflows/docker-setup/teardown-ee-server.bash @@ -0,0 +1,2 @@ +docker stop aerospike +docker volume rm aerospike-conf-vol diff --git a/.github/workflows/docker-build-context/wait-for-as-server-to-start.bash b/.github/workflows/docker-setup/wait-for-as-server-to-start.bash old mode 100644 new mode 100755 similarity index 84% rename from .github/workflows/docker-build-context/wait-for-as-server-to-start.bash rename to .github/workflows/docker-setup/wait-for-as-server-to-start.bash index 9d69768927..c309fb7227 --- a/.github/workflows/docker-build-context/wait-for-as-server-to-start.bash +++ b/.github/workflows/docker-setup/wait-for-as-server-to-start.bash @@ -5,6 +5,8 @@ set -o pipefail # We use bash because we need the not (!) operator +CALL_FROM_TOOLS_CONTAINER="docker run --rm --network host aerospike/aerospike-tools" + while true; do # Intermediate step is to send docker exec command's output to stdout in case it fails # Sometimes, errors only appear in stdout and not stderr, like if asinfo throws an error because of no credentials @@ -13,7 +15,7 @@ while true; do # grep doesn't have a way to print all lines passed as input. # ack does have an option but it doesn't come installed by default echo "Checking if we can reach the server via the service port..." - if asinfo -v status | tee >(cat) | grep -qE "^ok"; then + if $CALL_FROM_TOOLS_CONTAINER asinfo $SECURITY_FLAGS -v status | tee >(cat) | grep -qE "^ok"; then # Server is ready when asinfo returns ok echo "Can reach server now." break @@ -29,7 +31,7 @@ while true; do # The Dockerfile uses a roster from a previously running Aerospike server in a Docker container # When we reuse this roster, the server assumes all of its partitions are dead because it's running on a new # storage device. That is why we ignore-migrations here - if asinfo -v "cluster-stable:ignore-migrations=true" 2>&1 | (! grep -qE "^ERROR"); then + if $CALL_FROM_TOOLS_CONTAINER asinfo $SECURITY_FLAGS -v "cluster-stable:ignore-migrations=true" 2>&1 | (! grep -qE "^ERROR"); then echo "Server is in a stable state." break fi diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d8b352db06..86880fb4d3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -480,7 +480,8 @@ jobs: with: container-name: aerospike - - uses: ./.github/actions/run-ee-server + - id: run-ee-server + uses: ./.github/actions/run-ee-server if: ${{ matrix.test-case == 'user_agent_with_ee' }} with: registry-name: ${{ env.REGISTRY_NAME }} @@ -488,7 +489,7 @@ jobs: registry-username: ${{ env.REGISTRY_NAME == 'docker.io' && secrets.DOCKER_HUB_BOT_USERNAME || secrets.QE_DOCKER_REGISTRY_USERNAME }} registry-password: ${{ env.REGISTRY_NAME == 'docker.io' && secrets.DOCKER_HUB_BOT_PW || secrets.QE_DOCKER_REGISTRY_PASSWORD }} # The user agent can also send the client's username if app-id is not set by the client - docker-run-extra-flags: '-e NO_SC=1 -e NO_TLS=1' + env-vars: 'SECURITY=1' - name: Install test dependencies if: ${{ matrix.test-case == 'lowest_supported_server_version' }} @@ -508,6 +509,7 @@ jobs: # Even for server versions < 8.1 that don't support user agent, this client version should still work on older servers name: Run client in background run: | + set -x cat <<-EOF >> run-client-in-bg.py import aerospike import time @@ -532,14 +534,18 @@ jobs: - if: ${{ startsWith(matrix.test-case, 'user_agent') }} name: Confirm that the user agent shows the correct client language and version (only for server 8.1) run: | - server_version=$(docker exec ${{ env.CONTAINER_NAME }} asinfo -v build) + if [[ "${{ endsWith(matrix.test-case, 'with_ee') }}" == "true" ]]; then + CREDENTIALS="-U superuser -P superuser" + fi + + server_version=$(docker run --network host aerospike/aerospike-tools asinfo $CREDENTIALS -v "build") major_num=$(echo $server_version | cut -d '.' -f 1) minor_num=$(echo $server_version | cut -d '.' -f 2) if [[ $major_num -lt 8 || ( $major_num -eq 8 && $minor_num -lt 1 ) ]]; then exit 0 fi - info_cmd_response=$(docker exec ${{ env.CONTAINER_NAME }} asinfo -v "user-agents" | head -n 1) + info_cmd_response=$(docker run --network host aerospike/aerospike-tools asinfo $CREDENTIALS -v "user-agents" | head -n 1) echo "Info command response: $info_cmd_response" # Response format: user-agent=:... user_agent_base64_encoded=$(echo $info_cmd_response | perl -n -E 'say $1 if m/user-agent= ([a-zA-Z0-9+\/=]+) :/x') @@ -560,4 +566,4 @@ jobs: test "$client_version" = "$expected_client_version" shell: bash env: - CONTAINER_NAME: aerospike + CONTAINER_NAME: ${{ matrix.test-case == 'user_agent_with_ee' && steps.run-ee-server.outputs.container-name || 'aerospike' }}