Skip to content

Commit

Permalink
Merge pull request #645 from aerospike/CLIENT-3181
Browse files Browse the repository at this point in the history
CLIENT-3181: Add Multi-Record Transactions
  • Loading branch information
DomPeliniAerospike authored Dec 24, 2024
2 parents d966c7c + 8ae16a8 commit c8c6512
Show file tree
Hide file tree
Showing 63 changed files with 3,631 additions and 699 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
7 changes: 7 additions & 0 deletions .github/workflows/config.conf.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[community-edition]
hosts: 127.0.0.1:3000

[enterprise-edition]
hosts :
user :
password :
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
}
]
4 changes: 4 additions & 0 deletions .github/workflows/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
parver==0.5
crudini==0.9.4
delocate==0.10.4
mypy==1.10.0
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ jobs:
npm install typescript --save-dev;
npx tsc;
cd ..;
npm run test dist/
npm run test
test-lowest-supported-server:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -278,7 +278,7 @@ jobs:
npm install typescript --save-dev;
npx tsc;
cd ..;
npm run test dist/ -- --t 20000
npm run test -- --t 20000
test-ee:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -335,7 +335,7 @@ jobs:
npm install typescript --save-dev;
npx tsc;
cd ..;
npm run test dist/admin.js -- --h localhost --U admin --P admin --t 40000
npm run test -- --h localhost --U superuser --P superuser --t 40000
test-valgrind:
runs-on: ubuntu-latest
Expand Down
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ To run all the test cases:

npm test

To run a specific tests, use:

npm test --testfile=filename.js

Note: make sure your server has TTL enabled for the `test` namespace ([Namespace Retention Configuration](https://docs.aerospike.com/server/operations/configure/namespace/retention)) to allow all tests to run correctly.

To run the tests and also report on test coverage:
Expand Down
2 changes: 1 addition & 1 deletion aerospike-client-c
Submodule aerospike-client-c updated 94 files
+3 −0 Makefile
+2 −2 README.md
+1 −0 examples/async_examples/Makefile
+3 −3 examples/async_examples/async_batch_get/src/main/example.c
+2 −2 examples/async_examples/async_delay_queue/src/main/example.c
+4 −0 examples/async_examples/async_transaction/Makefile
+329 −0 examples/async_examples/async_transaction/src/main/example.c
+1 −0 examples/basic_examples/Makefile
+2 −2 examples/basic_examples/append/src/main/example.c
+2 −2 examples/basic_examples/incr/src/main/example.c
+4 −0 examples/basic_examples/transaction/Makefile
+144 −0 examples/basic_examples/transaction/src/main/example.c
+3 −3 examples/batch_examples/get/src/main/example.c
+ project/aerospike_logo.png
+12 −12 project/doxyfile
+116 −0 project/doxygen-awesome-sidebar-only.css
+2,681 −0 project/doxygen-awesome.css
+84 −0 project/header.html
+0 −0 project/layout.xml
+2 −0 project/test.mk
+0 −14 src/apidocs/footer.html
+0 −47 src/apidocs/header.html
+0 −716 src/apidocs/html/aerospike.css
+0 −563 src/apidocs/html/style.css
+0 −1,204 src/apidocs/old.css
+6 −6 src/include/aerospike/aerospike.h
+70 −30 src/include/aerospike/aerospike_batch.h
+93 −6 src/include/aerospike/aerospike_key.h
+6 −6 src/include/aerospike/aerospike_stats.h
+227 −0 src/include/aerospike/aerospike_txn.h
+3 −3 src/include/aerospike/as_admin.h
+21 −8 src/include/aerospike/as_async.h
+1 −1 src/include/aerospike/as_batch.h
+23 −12 src/include/aerospike/as_cluster.h
+124 −21 src/include/aerospike/as_command.h
+5 −5 src/include/aerospike/as_config.h
+2 −2 src/include/aerospike/as_error.h
+21 −16 src/include/aerospike/as_event.h
+27 −2 src/include/aerospike/as_event_internal.h
+3 −3 src/include/aerospike/as_exp.h
+1 −1 src/include/aerospike/as_latency.h
+11 −11 src/include/aerospike/as_node.h
+1 −1 src/include/aerospike/as_operations.h
+2 −1 src/include/aerospike/as_peers.h
+184 −45 src/include/aerospike/as_policy.h
+3 −3 src/include/aerospike/as_proto.h
+7 −1 src/include/aerospike/as_query.h
+2 −2 src/include/aerospike/as_record.h
+2 −2 src/include/aerospike/as_socket.h
+62 −21 src/include/aerospike/as_status.h
+308 −0 src/include/aerospike/as_txn.h
+95 −0 src/include/aerospike/as_txn_monitor.h
+1 −1 src/include/aerospike/version.h
+1,455 −287 src/main/aerospike/aerospike_batch.c
+1,093 −249 src/main/aerospike/aerospike_key.c
+13 −5 src/main/aerospike/aerospike_query.c
+7 −4 src/main/aerospike/aerospike_scan.c
+740 −0 src/main/aerospike/aerospike_txn.c
+9 −9 src/main/aerospike/as_cluster.c
+200 −31 src/main/aerospike/as_command.c
+12 −4 src/main/aerospike/as_error.c
+176 −16 src/main/aerospike/as_event.c
+1 −1 src/main/aerospike/as_event_uv.c
+27 −19 src/main/aerospike/as_info.c
+2 −2 src/main/aerospike/as_metrics_writer.c
+3 −3 src/main/aerospike/as_node.c
+94 −40 src/main/aerospike/as_peers.c
+8 −4 src/main/aerospike/as_policy.c
+412 −0 src/main/aerospike/as_txn.c
+349 −0 src/main/aerospike/as_txn_monitor.c
+1 −1 src/main/aerospike/version.c
+2 −2 src/test/aerospike_query/query_background.c
+31 −14 src/test/aerospike_test.c
+924 −0 src/test/transaction.c
+1,157 −0 src/test/transaction_async.c
+2 −2 src/test/util/map_rec.c
+2 −2 src/test/util/test_aerospike.c
+1 −1 vs/aerospike-client-c-libevent.nuspec
+1 −1 vs/aerospike-client-c-libuv.nuspec
+1 −1 vs/aerospike-client-c.nuspec
+2 −0 vs/aerospike-test/aerospike-test.vcxproj
+6 −0 vs/aerospike-test/aerospike-test.vcxproj.filters
+54 −0 vs/aerospike.sln
+6 −0 vs/aerospike/aerospike.vcxproj
+18 −0 vs/aerospike/aerospike.vcxproj.filters
+248 −0 vs/examples/async-transaction/async-transaction.vcxproj
+33 −0 vs/examples/async-transaction/async-transaction.vcxproj.filters
+4 −0 vs/examples/async-transaction/packages.config
+4 −0 vs/examples/transaction/packages.config
+248 −0 vs/examples/transaction/transaction.vcxproj
+33 −0 vs/examples/transaction/transaction.vcxproj.filters
+8 −0 xcode/aerospike-test.xcodeproj/project.pbxproj
+24 −0 xcode/aerospike.xcodeproj/project.pbxproj
+24 −0 xcode/examples.xcodeproj/project.pbxproj
Loading

0 comments on commit c8c6512

Please sign in to comment.