Skip to content

Commit

Permalink
Skipped functionality tests on older releases
Browse files Browse the repository at this point in the history
  • Loading branch information
DomPeliniAerospike committed Dec 23, 2024
1 parent 3e1a6cd commit 0d5b6ed
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 32 deletions.
98 changes: 72 additions & 26 deletions .github/actions/run-ee-server/action.yml
Original file line number Diff line number Diff line change
@@ -1,62 +1,108 @@
name: 'Run EE Server'
description: 'Run EE server'
description: 'Run EE server. Returns once server is ready. Only tested on Linux and macOS'
# NOTE: do not share this server container with others
# since it's using the default admin / admin credentials
inputs:
# All inputs in composite actions are strings
use-server-rc:
required: true
default: false
description: Deploy server release candidate?
default: 'false'
server-tag:
required: true
description: Specify Docker tag

default: 'latest'
# Github Composite Actions can't access secrets
# so we need to pass them in as inputs
docker-hub-username:
description: Required for using release candidates
required: false
docker-hub-password:
description: Required for using release candidates
required: false

runs:
using: "composite"
steps:
- name: Create config folder to store configs in
run: mkdir configs
- name: Install crudini to manipulate config.conf
# This will only work on the Github hosted runners.
run: pipx install crudini --pip-args "-c ${{ github.workspace }}/.github/workflows/requirements.txt"
working-directory: .github/workflows
shell: bash

- name: Use release server
if: ${{ inputs.use-server-rc == 'false' }}
run: echo "SERVER_IMAGE=aerospike/aerospike-server-enterprise" >> $GITHUB_ENV
- name: Create config.conf
run: cp config.conf.template config.conf
working-directory: .github/workflows
shell: bash

- name: Use release candidate server
if: ${{ inputs.use-server-rc == 'true' }}
run: echo "SERVER_IMAGE=aerospike/aerospike-server-enterprise-rc" >> $GITHUB_ENV
- name: Use enterprise edition instead of community edition in config.conf
run: |
crudini --existing=param --set config.conf enterprise-edition hosts ''
crudini --existing=param --set config.conf enterprise-edition hosts 127.0.0.1:3000
working-directory: .github/workflows
shell: bash

- run: echo SUPERUSER_NAME_AND_PASSWORD="superuser" >> $GITHUB_ENV
shell: bash

- name: Set credentials in config file
run: |
crudini --existing=param --set config.conf enterprise-edition user ${{ env.SUPERUSER_NAME_AND_PASSWORD }}
crudini --existing=param --set config.conf enterprise-edition password ${{ env.SUPERUSER_NAME_AND_PASSWORD }}
working-directory: .github/workflows
shell: bash

- name: Log into Docker Hub to get server RC
if: ${{ inputs.use-server-rc == 'true' }}
run: docker login --username ${{ inputs.docker-hub-username }} --password ${{ inputs.docker-hub-password }}
shell: bash

- name: Get default aerospike.conf from Docker server EE container
run: |
docker run -d --name aerospike -p 3000-3002:3000-3002 $SERVER_IMAGE:${{ inputs.server-tag }}
sleep 5
docker cp aerospike:/etc/aerospike/aerospike.conf ./configs/aerospike.conf
docker container stop aerospike
docker container rm aerospike
- run: echo IMAGE_NAME=aerospike/aerospike-server-enterprise${{ inputs.use-server-rc == 'true' && '-rc' || '' }}:${{ inputs.server-tag }} >> $GITHUB_ENV
shell: bash

- run: echo NEW_IMAGE_NAME=${{ env.IMAGE_NAME }}-security-and-sc >> $GITHUB_ENV
shell: bash

# 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

- name: Build and push
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_NAME }}
tags: ${{ env.NEW_IMAGE_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

- run: docker run -d --name ${{ env.SERVER_CONTAINER_NAME }} -e DEFAULT_TTL=2592000 -p 3000:3000 ${{ env.NEW_IMAGE_NAME }}
shell: bash

- uses: ./.github/actions/wait-for-as-server-to-start
with:
container-name: ${{ env.SERVER_CONTAINER_NAME }}
is-strong-consistency-enabled: true

- run: echo ASADM_AUTH_FLAGS="--user=${{ env.SUPERUSER_NAME_AND_PASSWORD }} --password=${{ env.SUPERUSER_NAME_AND_PASSWORD }}" >> $GITHUB_ENV
shell: bash

- name: Enable security features using aerospike.conf
# Security stanza
run: echo -e "security {\n\tenable-quotas true\n}\n" >> ./aerospike.conf
working-directory: ./configs
# All the partitions are assumed to be dead when reusing a roster file
- run: docker exec ${{ env.SERVER_CONTAINER_NAME }} asadm $ASADM_AUTH_FLAGS --enable --execute "manage revive ns test"
shell: bash

- name: Run enterprise edition server
run: docker run -tid -v $(pwd)/configs:/opt/aerospike/etc -p 3000:3000 --name aerospike $SERVER_IMAGE:${{ inputs.server-tag }} asd --config-file /opt/aerospike/etc/aerospike.conf
# Apply changes
- run: docker exec ${{ env.SERVER_CONTAINER_NAME }} asadm $ASADM_AUTH_FLAGS --enable --execute "manage recluster"
shell: bash

- name: Create user in database for tests
# Use default admin user to create another user for testing
run: docker exec aerospike asadm --user admin --password admin --enable -e "manage acl create user superuser password superuser roles read-write-udf sys-admin user-admin data-admin"
# For debugging
- run: docker logs aerospike
shell: bash
28 changes: 28 additions & 0 deletions .github/actions/wait-for-as-server-to-start/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: 'Wait for Aerospike server to start'
description: Only tested on Linux and macOS
inputs:
container-name:
required: true
is-security-enabled:
required: false
default: 'false'
is-strong-consistency-enabled:
required: false
default: 'false'

runs:
using: "composite"
steps:
- name: 'macOS: install timeout command'
if: ${{ runner.os == 'macOS' }}
run: brew install coreutils
shell: bash

# Composite actions doesn't support step-level timeout-minutes
# Use timeout command and store polling logic in file to make it easier to read
# Call bash shell explicitly since timeout uses "sh" shell by default, for some reason
# Also, we don't want to fail if we timeout in case the server *did* finish starting up but the script couldn't detect it due to a bug
# Effectively, this composite action is like calling "sleep" that is optimized to exit early when it detects an ok from the server
- name: Wait for EE server to start
run: timeout 30 bash ./.github/workflows/wait-for-as-server-to-start.bash ${{ inputs.container-name }} ${{ inputs.is-security-enabled }} ${{ inputs.is-strong-consistency-enabled }} || true
shell: bash
50 changes: 50 additions & 0 deletions .github/workflows/docker-build-context/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
ARG server_image=aerospike/aerospike-server-enterprise:8.0.0.0-rc1
ARG ROSTER_FILE_NAME=roster.smd
# Temp file for passing node id from one build stage to another
# Docker doesn't support command substitution for setting values for ARG variables, so we have to do this
ARG NODE_ID_FILE_NAME=node_id

FROM $server_image as configure-server

WORKDIR /opt/aerospike/smd

# Enable authentication

ARG AEROSPIKE_CONF_TEMPLATE_PATH=/etc/aerospike/aerospike.template.conf

# Not using asconfig to edit config because we are working with a template file, which may not have valid values yet
RUN echo -e "security {\n\tenable-quotas true\n}\n" >> $AEROSPIKE_CONF_TEMPLATE_PATH
# 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 .

# Enable strong consistency
RUN sed -i "s/\(namespace.*{\)/\1\n\tstrong-consistency true/" $AEROSPIKE_CONF_TEMPLATE_PATH
RUN sed -i "s/\(namespace.*{\)/\1\n\tstrong-consistency-allow-expunge true/" $AEROSPIKE_CONF_TEMPLATE_PATH
RUN sed -i "s/\(namespace.*{\)/\1\n\tdefault-ttl 2592000/" $AEROSPIKE_CONF_TEMPLATE_PATH
ARG ROSTER_FILE_NAME
COPY $ROSTER_FILE_NAME .

# Fetch node id from roster.smd

# There's no tag for the latest major version to prevent breaking changes in jq
# This is the next best thing
FROM ghcr.io/jqlang/jq:1.7 as get-jq
# jq docker image doesn't have a shell
# We need a shell to fetch and pass the node id to the next build stage
FROM busybox as get-node-id
COPY --from=get-jq /jq /bin/
ARG ROSTER_FILE_NAME
COPY $ROSTER_FILE_NAME .
ARG NODE_ID_FILE_NAME
RUN jq --raw-output '.[1].value' $ROSTER_FILE_NAME > $NODE_ID_FILE_NAME

FROM configure-server as set-node-id
ARG NODE_ID_FILE_NAME
COPY --from=get-node-id $NODE_ID_FILE_NAME .
RUN sed -i "s/\(^service {\)/\1\n\tnode-id $(cat $NODE_ID_FILE_NAME)/" $AEROSPIKE_CONF_TEMPLATE_PATH
RUN rm $NODE_ID_FILE_NAME
12 changes: 12 additions & 0 deletions .github/workflows/docker-build-context/roster.smd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
[
97107025374203,
1
],
{
"key": "test",
"value": "a1",
"generation": 1,
"timestamp": 465602976982
}
]
48 changes: 48 additions & 0 deletions .github/workflows/docker-build-context/security.smd
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[
[
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
}
]
55 changes: 55 additions & 0 deletions .github/workflows/wait-for-as-server-to-start.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash

set -x
# Makes sure that if the "docker exec" command fails, it is not ignored
set -o pipefail

container_name=$1
is_security_enabled=$2
is_strong_consistency_enabled=$3

if [[ $is_security_enabled == true ]]; then
# We need to pass credentials to asinfo if server requires it
# TODO: passing in credentials via command line flags since I can't figure out how to use --instance with global astools.conf
user_credentials="--user=admin --password=admin"
fi

while true; do
# An unset variable will have a default empty value
# Intermediate step is to print docker exec command's output in case it fails
# Sometimes, errors only appear in stdout and not stderr, like if asinfo throws an error because of no credentials
# (This is a bug in asinfo since all error messages should be sent to stderr)
# But piping and passing stdin to grep will hide the first command's stdout.
# 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
# shellcheck disable=SC2086 # The flags in user credentials should be separate anyways. Not one string
echo "Checking if we can reach the server via the service port..."
if docker exec "$container_name" asinfo $user_credentials -v status | tee >(cat) | grep -qE "^ok"; then
# Server is ready when asinfo returns ok
echo "Can reach server now."
# docker container inspect "$container_name"
break
fi

echo "Server didn't return ok via the service port. Polling again..."
done

# Although the server may be reachable via the service port, the cluster may not be fully initialized yet.
# If we try to connect too soon (e.g right after "status" returns ok), the client may throw error code -1
while true; do
echo "Waiting for server to stabilize (i.e return a cluster key)..."
# We assume that when an ERROR is returned, the cluster is not stable yet (i.e not fully initialized)
cluster_stable_info_cmd="cluster-stable"
if [[ $is_strong_consistency_enabled == true ]]; then
# 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.
cluster_stable_info_cmd="$cluster_stable_info_cmd:ignore-migrations=true"
fi
if docker exec "$container_name" asinfo $user_credentials -v $cluster_stable_info_cmd 2>&1 | (! grep -qE "^ERROR"); then
echo "Server is in a stable state."
break
fi

echo "Server did not return a cluster key. Polling again..."
done
2 changes: 1 addition & 1 deletion lib/aerospike.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ exports.Config = require('./config')
exports.Double = require('./double')

/**
* Multi-record transaction (MRT) class. Each command in the MRT must use the same namespace.
* Multi-record transaction (MRT) class. All commands in the same MRT must use the same namespace.
*
* note: By default, open transactions are destroyed when the final client in a process is closed.
* If you need your transaction to persist after the last client has been closed, provide `false` for the
Expand Down
6 changes: 3 additions & 3 deletions ts-test/tests/mrt_backward_compatible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ const recgen: any = helper.recgen
const status: typeof statusModule = Aerospike.status

describe('MRT backward compatible tests', function () {
helper.skipUnlessVersionAndCommunity('< 8.0.0', this)
helper.skip(this, 'MRT\'s require version 8.0.0 or greater')

// helper.skipUnlessVersionAndCommunity('< 8.0.0', this)

const client: Cli = helper.client

Expand Down Expand Up @@ -87,8 +89,6 @@ describe('MRT backward compatible tests', function () {
};

await client.put(key2, record2, meta, policy)
await client.put(key3, record2, meta, policy)
await client.put(key4, record2, meta, policy)

let get_result: AerospikeRecord = await client.get(key2, policy)
expect(get_result.bins).to.eql(record2)
Expand Down
1 change: 1 addition & 0 deletions ts-test/tests/mrt_functionality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const status: typeof statusModule = Aerospike.status
describe('MRT functionality tests', function () {
helper.skipUnlessVersionAndEnterprise('>= 8.0.0', this)


const client: Cli = helper.client

const key1: K = keygen.string(helper.namespace, helper.set, { prefix: 'test/mrt/1' })()
Expand Down
12 changes: 10 additions & 2 deletions ts-test/tests/test_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,19 @@ Aerospike.setDefaultLogging(config.log ?? {})


export function skipUnlessVersionAndEnterprise (this: any, versionRange: any, ctx: Suite) {
skipUnless(ctx, () => {return (this.cluster.isVersionInRange(versionRange) && (this.cluster.isEnterprise())) }, `cluster version does not meet requirements: "${versionRange} and/or requires enterprise"`)
skipUnless(ctx, () => {
console.log(this.cluster.isVersionInRange(versionRange))
console.log((!this.cluster.isEnterprise()))
return (this.cluster.isVersionInRange(versionRange) && (this.cluster.isEnterprise())) }, `cluster version does not meet requirements: "${versionRange} and/or requires enterprise"`)
}

export function skipUnlessVersionAndCommunity (this: any, versionRange: any, ctx: Suite) {
skipUnless(ctx, () => {return (this.cluster.isVersionInRange(versionRange) && (!this.cluster.isEnterprise())) }, `cluster version does not meet requirements: "${versionRange} and/or requires enterprise"`)
skipUnless(ctx, () => {
console.log(this.cluster.isVersionInRange(versionRange))
console.log((!this.cluster.isEnterprise()))
return (this.cluster.isVersionInRange(versionRange) && (!this.cluster.isEnterprise()))

}, `cluster version does not meet requirements: "${versionRange} and/or requires enterprise"`)
}

export function skipUnlessSupportsTtl(this: any, ctx: Suite) {
Expand Down

0 comments on commit 0d5b6ed

Please sign in to comment.