Compare commits
35 Commits
analyze-fl
...
support-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccabbe7a63 | ||
|
|
aac169c5b3 | ||
|
|
d0cb85e642 | ||
|
|
34a7fed59a | ||
|
|
8124226ba9 | ||
|
|
eea21dd73e | ||
|
|
a6483fc6a7 | ||
|
|
45def388c7 | ||
|
|
da3db9f3bc | ||
|
|
34b2e360a9 | ||
|
|
9bc0d9425e | ||
|
|
62d1b68c2f | ||
|
|
ec46cca7a3 | ||
|
|
326a04c6a3 | ||
|
|
b21f9e71ed | ||
|
|
c2e0b310f5 | ||
|
|
b492c7d34f | ||
|
|
303608982a | ||
|
|
c0f979fdcb | ||
|
|
5242427686 | ||
|
|
d3168ded5a | ||
|
|
0a28fee34b | ||
|
|
dc9a2c45c4 | ||
|
|
1133bb8ecb | ||
|
|
e9c5cc3b82 | ||
|
|
3f4ac0dd58 | ||
|
|
a0ecea7d1c | ||
|
|
567e1fa1cb | ||
|
|
ef37c44149 | ||
|
|
ab34b060c0 | ||
|
|
67c1874e81 | ||
|
|
9d457eafa8 | ||
|
|
986ea37ead | ||
|
|
afe7d47a5c | ||
|
|
8884a0ea78 |
649
.github/workflows/analyze_diff.yaml
vendored
649
.github/workflows/analyze_diff.yaml
vendored
@@ -1,649 +0,0 @@
|
||||
name: Analyze diff flakiness
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# pull_request:
|
||||
# paths-ignore:
|
||||
# - "docs/**"
|
||||
# - "**/*.md"
|
||||
# - ".clang-format"
|
||||
# - "CODEOWNERS"
|
||||
# - "licenses/*"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Initialize deps
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
code_analysis:
|
||||
name: "Code analysis"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
# This is also needed if we want do to comparison against other branches
|
||||
# See https://github.community/t/checkout-code-fails-when-it-runs-lerna-run-test-since-master/17920
|
||||
- name: Fetch all history for all tags and branches
|
||||
run: git fetch
|
||||
|
||||
- name: Initialize deps
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Set base branch
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/${{ github.base_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set base branch # if we manually dispatch or push to master
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/master" >> $GITHUB_ENV
|
||||
|
||||
- name: Python code analysis
|
||||
run: |
|
||||
CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only --diff-filter=d)
|
||||
for file in ${CHANGED_FILES}; do
|
||||
echo ${file}
|
||||
if [[ ${file} == *.py ]]; then
|
||||
python3 -m black --check --diff ${file}
|
||||
python3 -m isort --profile black --check-only --diff ${file}
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON -DASAN=ON -DUBSAN=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests. It is restricted to 2 threads intentionally, because higher concurrency makes the timing related tests unstable.
|
||||
cd build
|
||||
LSAN_OPTIONS=suppressions=$PWD/../tools/lsan.supp UBSAN_OPTIONS=halt_on_error=1 ctest -R memgraph__unit --output-on-failure -j2
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Restrict clang-tidy results only to the modified parts
|
||||
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build -regex ".+\.cpp" | tee ./build/clang_tidy_output.txt
|
||||
|
||||
# Fail if any warning is reported
|
||||
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Initialize deps
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- name: Run drivers tests
|
||||
run: |
|
||||
./tests/drivers/run.sh
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
tests/integration/run.sh
|
||||
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Initialize deps
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
cd gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose up -d
|
||||
cd ../pulsar
|
||||
docker-compose up -d
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
source ve3/bin/activate_e2e
|
||||
cd e2e
|
||||
./run.sh
|
||||
|
||||
- name: Ensure Kafka and Pulsar are down
|
||||
if: always()
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose down
|
||||
cd ../pulsar
|
||||
docker-compose down
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (SSL)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
./continuous_integration --use-ssl
|
||||
|
||||
- name: Run durability test
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create enterprise DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data"
|
||||
path: |
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
#
|
||||
# experimental_build_ha:
|
||||
# name: "High availability build"
|
||||
# runs-on: [self-hosted, Linux, X64, Diff]
|
||||
# strategy:
|
||||
# max-parallel: 1
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
# env:
|
||||
# THREADS: 24
|
||||
# MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
# MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
#
|
||||
# steps:
|
||||
# - name: Set up repository
|
||||
# uses: actions/checkout@v3
|
||||
# with:
|
||||
# # Number of commits to fetch. `0` indicates all history for all
|
||||
# # branches and tags. (default: 1)
|
||||
# fetch-depth: 0
|
||||
#
|
||||
# - name: Initialize deps
|
||||
# run: |
|
||||
# # Activate toolchain.
|
||||
# source /opt/toolchain-v4/activate
|
||||
#
|
||||
# # Initialize dependencies.
|
||||
# ./init
|
||||
#
|
||||
# - name: Build release binaries
|
||||
# run: |
|
||||
# source /opt/toolchain-v4/activate
|
||||
# cd build
|
||||
# cmake -DCMAKE_BUILD_TYPE=Release -DMG_EXPERIMENTAL_HIGH_AVAILABILITY=ON ..
|
||||
# make -j$THREADS
|
||||
# - name: Run unit tests
|
||||
# run: |
|
||||
# source /opt/toolchain-v4/activate
|
||||
# cd build
|
||||
# ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
# - name: Run e2e tests
|
||||
# run: |
|
||||
# cd tests
|
||||
# ./setup.sh /opt/toolchain-v4/activate
|
||||
# source ve3/bin/activate_e2e
|
||||
# cd e2e
|
||||
# ./run.sh "Coordinator"
|
||||
# ./run.sh "Client initiated failover"
|
||||
# ./run.sh "Uninitialized cluster"
|
||||
# - name: Save test data
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: always()
|
||||
# with:
|
||||
# name: "Test data"
|
||||
# path: |
|
||||
# # multiple paths could be defined
|
||||
# build/logs
|
||||
#
|
||||
# experimental_build_mt:
|
||||
# name: "MultiTenancy replication build"
|
||||
# runs-on: [self-hosted, Linux, X64, Diff]
|
||||
# strategy:
|
||||
# max-parallel: 1
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
# env:
|
||||
# THREADS: 24
|
||||
# MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
# MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
#
|
||||
# steps:
|
||||
# - name: Set up repository
|
||||
# uses: actions/checkout@v3
|
||||
# with:
|
||||
# # Number of commits to fetch. `0` indicates all history for all
|
||||
# # branches and tags. (default: 1)
|
||||
# fetch-depth: 0
|
||||
#
|
||||
#
|
||||
# - name: Initialize deps
|
||||
# run: |
|
||||
# # Activate toolchain.
|
||||
# source /opt/toolchain-v4/activate
|
||||
#
|
||||
# # Initialize dependencies.
|
||||
# ./init
|
||||
#
|
||||
# - name: Build release binaries
|
||||
# run: |
|
||||
# # Activate toolchain.
|
||||
# source /opt/toolchain-v4/activate
|
||||
#
|
||||
# # Build MT replication experimental binaries.
|
||||
# cd build
|
||||
# cmake -DCMAKE_BUILD_TYPE=Release -D MG_EXPERIMENTAL_REPLICATION_MULTITENANCY=ON ..
|
||||
# make -j$THREADS
|
||||
#
|
||||
# - name: Run unit tests
|
||||
# run: |
|
||||
# # Activate toolchain.
|
||||
# source /opt/toolchain-v4/activate
|
||||
#
|
||||
# # Run unit tests.
|
||||
# cd build
|
||||
# ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
#
|
||||
# - name: Run e2e tests
|
||||
# run: |
|
||||
# cd tests
|
||||
# ./setup.sh /opt/toolchain-v4/activate
|
||||
# source ve3/bin/activate_e2e
|
||||
# cd e2e
|
||||
#
|
||||
# # Just the replication based e2e tests
|
||||
# ./run.sh "Replicate multitenancy"
|
||||
# ./run.sh "Show"
|
||||
# ./run.sh "Show while creating invalid state"
|
||||
# ./run.sh "Delete edge replication"
|
||||
# ./run.sh "Read-write benchmark"
|
||||
# ./run.sh "Index replication"
|
||||
# ./run.sh "Constraints"
|
||||
#
|
||||
# - name: Save test data
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: always()
|
||||
# with:
|
||||
# name: "Test data"
|
||||
# path: |
|
||||
# # multiple paths could be defined
|
||||
# build/logs
|
||||
|
||||
release_jepsen_test:
|
||||
name: "Release Jepsen Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
#continue-on-error: true
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Initialize deps
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
# Build only memgraph release binarie.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
|
||||
make -j$THREADS memgraph
|
||||
|
||||
- name: Refresh Jepsen Cluster
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh cluster-refresh
|
||||
|
||||
- name: Run Jepsen tests
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
path: tests/jepsen/Jepsen.tar.gz
|
||||
|
||||
release_benchmarks:
|
||||
name: "Release benchmarks"
|
||||
runs-on: [self-hosted, Linux, X64, Diff, Gen7]
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Initialize deps
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Build only memgraph release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run macro benchmarks
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Get branch name (merge)
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get branch name (pull request)
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload macro benchmark results
|
||||
run: |
|
||||
cd tools/bench-graph-client
|
||||
virtualenv -p python3 ve3
|
||||
source ve3/bin/activate
|
||||
pip install -r requirements.txt
|
||||
./main.py --benchmark-name "macro_benchmark" \
|
||||
--benchmark-results "../../tests/macro_benchmark/.harness_summary" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
# TODO (andi) No need for path flags and for --disk-storage and --in-memory-analytical
|
||||
- name: Run mgbench
|
||||
run: |
|
||||
cd tests/mgbench
|
||||
./benchmark.py vendor-native --num-workers-for-benchmark 12 --export-results benchmark_result.json pokec/medium/*/*
|
||||
|
||||
- name: Upload mgbench results
|
||||
run: |
|
||||
cd tools/bench-graph-client
|
||||
virtualenv -p python3 ve3
|
||||
source ve3/bin/activate
|
||||
pip install -r requirements.txt
|
||||
./main.py --benchmark-name "mgbench" \
|
||||
--benchmark-results "../../tests/mgbench/benchmark_result.json" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
72
.github/workflows/analyze_e2e.yaml
vendored
72
.github/workflows/analyze_e2e.yaml
vendored
@@ -1,72 +0,0 @@
|
||||
name: Analyze e2e flakiness
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
- ".clang-format"
|
||||
- "CODEOWNERS"
|
||||
- "licenses/*"
|
||||
|
||||
jobs:
|
||||
release_e2e_test:
|
||||
name: "Release End-to-end Test"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
strategy:
|
||||
max-parallel: 5
|
||||
fail-fast: false
|
||||
matrix:
|
||||
try_id: [1,2,3,4,5,6,7,8,9,10]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose up -d
|
||||
cd ../pulsar
|
||||
docker-compose up -d
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
source ve3/bin/activate_e2e
|
||||
cd e2e
|
||||
./run.sh
|
||||
|
||||
- name: Ensure Kafka and Pulsar are down
|
||||
if: always()
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose down
|
||||
cd ../pulsar
|
||||
docker-compose down
|
||||
2
.github/workflows/daily_benchmark.yaml
vendored
2
.github/workflows/daily_benchmark.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
75
.github/workflows/diff.yaml
vendored
75
.github/workflows/diff.yaml
vendored
@@ -4,18 +4,18 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
# pull_request:
|
||||
# paths-ignore:
|
||||
# - "docs/**"
|
||||
# - "**/*.md"
|
||||
# - ".clang-format"
|
||||
# - "CODEOWNERS"
|
||||
# - "licenses/*"
|
||||
#
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
- ".clang-format"
|
||||
- "CODEOWNERS"
|
||||
- "licenses/*"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -137,9 +137,9 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage(Code analysis)"
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
- name: Run clang-tidy
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -208,9 +208,9 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage(Debug build)"
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
@@ -223,7 +223,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
@@ -268,7 +268,6 @@ jobs:
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
if: false
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose up -d
|
||||
@@ -276,7 +275,6 @@ jobs:
|
||||
docker-compose up -d
|
||||
|
||||
- name: Run e2e tests
|
||||
if: false
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
@@ -285,7 +283,7 @@ jobs:
|
||||
./run.sh
|
||||
|
||||
- name: Ensure Kafka and Pulsar are down
|
||||
if: false
|
||||
if: always()
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose down
|
||||
@@ -325,16 +323,16 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data(Release build)"
|
||||
name: "Test data"
|
||||
path: |
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
@@ -349,7 +347,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -368,7 +366,6 @@ jobs:
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
- name: Run e2e tests
|
||||
if: false
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
@@ -378,10 +375,10 @@ jobs:
|
||||
./run.sh "Client initiated failover"
|
||||
./run.sh "Uninitialized cluster"
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data(High availability build)"
|
||||
name: "Test data"
|
||||
path: |
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
@@ -396,7 +393,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -426,7 +423,6 @@ jobs:
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Run e2e tests
|
||||
if: false
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
@@ -443,10 +439,10 @@ jobs:
|
||||
./run.sh "Constraints"
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data(MultiTenancy replication build)"
|
||||
name: "Test data"
|
||||
path: |
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
@@ -462,7 +458,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -479,18 +475,13 @@ jobs:
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
|
||||
make -j$THREADS memgraph
|
||||
|
||||
- name: Refresh Jepsen Cluster
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh cluster-refresh
|
||||
|
||||
- name: Run Jepsen tests
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
@@ -506,7 +497,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
2
.github/workflows/full_clang_tidy.yaml
vendored
2
.github/workflows/full_clang_tidy.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
54
.github/workflows/package_memgraph.yaml
vendored
54
.github/workflows/package_memgraph.yaml
vendored
@@ -42,14 +42,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package amzn-2 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: amzn-2
|
||||
path: build/output/amzn-2/memgraph*.rpm
|
||||
@@ -60,14 +60,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package centos-7 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: centos-7
|
||||
path: build/output/centos-7/memgraph*.rpm
|
||||
@@ -78,14 +78,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package centos-9 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: centos-9
|
||||
path: build/output/centos-9/memgraph*.rpm
|
||||
@@ -96,14 +96,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package debian-10 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-10
|
||||
path: build/output/debian-10/memgraph*.deb
|
||||
@@ -114,14 +114,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-11
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
@@ -132,14 +132,14 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package debian-11-arm ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-11-aarch64
|
||||
path: build/output/debian-11-arm/memgraph*.deb
|
||||
@@ -150,14 +150,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-platform
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-11-platform
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
./run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker
|
||||
path: build/output/docker/memgraph*.tar.gz
|
||||
@@ -188,14 +188,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package fedora-36 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: fedora-36
|
||||
path: build/output/fedora-36/memgraph*.rpm
|
||||
@@ -206,14 +206,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-18.04 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-18.04
|
||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
||||
@@ -224,14 +224,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-20.04 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-20.04
|
||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
||||
@@ -242,14 +242,14 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-22.04 ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-22.04
|
||||
path: build/output/ubuntu-22.04/memgraph*.deb
|
||||
@@ -260,14 +260,14 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-22.04-arm ${{ github.event.inputs.build_type }}
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-22.04-aarch64
|
||||
path: build/output/ubuntu-22.04-arm/memgraph*.deb
|
||||
@@ -279,7 +279,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
# name: # if name input parameter is not provided, all artifacts are downloaded
|
||||
# and put in directories named after each one.
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
215
.github/workflows/release_debian10.yaml
vendored
215
.github/workflows/release_debian10.yaml
vendored
@@ -14,21 +14,19 @@ on:
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }}
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -42,6 +40,10 @@ jobs:
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Set default build_type to Release
|
||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
|
||||
@@ -63,11 +65,10 @@ jobs:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -109,19 +110,22 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage(Coverage build)"
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -153,6 +157,10 @@ jobs:
|
||||
run: |
|
||||
./tests/drivers/run.sh
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
tests/integration/run.sh
|
||||
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
@@ -163,49 +171,23 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage(Debug build)"
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
debug_integration_test:
|
||||
name: "Debug integration tests"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
tests/integration/run.sh
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
timeout-minutes: 60
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, BigMemory]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -219,6 +201,10 @@ jobs:
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Set default build_type to Release
|
||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
@@ -240,60 +226,11 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
cd gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
release_benchmark_tests:
|
||||
name: "Release Benchmark Tests"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
@@ -320,31 +257,29 @@ jobs:
|
||||
--num-database-workers 9 --num-clients-workers 30 \
|
||||
--no-strict
|
||||
|
||||
release_e2e_test:
|
||||
if: false
|
||||
name: "Release End-to-end Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
timeout-minutes: 60
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
cd gql_behave
|
||||
./continuous_integration
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Build release binaries
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries
|
||||
# Run unit tests.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
run: |
|
||||
@@ -369,32 +304,6 @@ jobs:
|
||||
cd ../pulsar
|
||||
docker-compose down
|
||||
|
||||
release_durability_stress_tests:
|
||||
name: "Release durability and stress tests"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
@@ -405,6 +314,11 @@ jobs:
|
||||
cd tests/stress
|
||||
./continuous_integration --use-ssl
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
- name: Run durability test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
@@ -420,11 +334,15 @@ jobs:
|
||||
release_jepsen_test:
|
||||
name: "Release Jepsen Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -437,23 +355,22 @@ jobs:
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Set default build_type to Release
|
||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
||||
|
||||
# Build only memgraph release binary.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS memgraph
|
||||
|
||||
- name: Refresh Jepsen Cluster
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh cluster-refresh
|
||||
|
||||
- name: Run Jepsen tests
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
|
||||
2
.github/workflows/release_docker.yaml
vendored
2
.github/workflows/release_docker.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
DOCKER_REPOSITORY_NAME: memgraph
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
200
.github/workflows/release_ubuntu2004.yaml
vendored
200
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -14,21 +14,19 @@ on:
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }}
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -42,6 +40,10 @@ jobs:
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Set default build_type to Release
|
||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
|
||||
@@ -59,11 +61,14 @@ jobs:
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -105,19 +110,22 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage(Coverage build)"
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -149,6 +157,10 @@ jobs:
|
||||
run: |
|
||||
./tests/drivers/run.sh
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
tests/integration/run.sh
|
||||
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
@@ -159,49 +171,23 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage(Debug build)"
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
debug_integration_test:
|
||||
name: "Debug integration tests"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
tests/integration/run.sh
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -215,6 +201,10 @@ jobs:
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Set default build_type to Release
|
||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
@@ -236,60 +226,11 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
cd gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
release_benchmark_tests:
|
||||
name: "Release Benchmark Tests"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
@@ -316,31 +257,29 @@ jobs:
|
||||
--num-database-workers 9 --num-clients-workers 30 \
|
||||
--no-strict
|
||||
|
||||
release_e2e_test:
|
||||
if: false
|
||||
name: "Release End-to-end Test"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
cd gql_behave
|
||||
./continuous_integration
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Build release binaries
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries
|
||||
# Run unit tests.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
run: |
|
||||
@@ -365,32 +304,6 @@ jobs:
|
||||
cd ../pulsar
|
||||
docker-compose down
|
||||
|
||||
release_durability_stress_tests:
|
||||
name: "Release durability and stress tests"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
@@ -401,6 +314,11 @@ jobs:
|
||||
cd tests/stress
|
||||
./continuous_integration --use-ssl
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
- name: Run durability test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
|
||||
62
.github/workflows/stress_test_large.yaml
vendored
62
.github/workflows/stress_test_large.yaml
vendored
@@ -1,62 +0,0 @@
|
||||
name: Stress test large
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_type:
|
||||
type: choice
|
||||
description: "Memgraph Build type. Default value is Release."
|
||||
default: 'Release'
|
||||
options:
|
||||
- Release
|
||||
- RelWithDebInfo
|
||||
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }}
|
||||
|
||||
jobs:
|
||||
stress_test_large:
|
||||
name: "Stress test large"
|
||||
timeout-minutes: 720
|
||||
strategy:
|
||||
matrix:
|
||||
os: [Debian10, Ubuntu20.04]
|
||||
extra: [BigMemory, Gen8]
|
||||
exclude:
|
||||
- os: Debian10
|
||||
extra: Gen8
|
||||
- os: Ubuntu20.04
|
||||
extra: BigMemory
|
||||
runs-on: [self-hosted, Linux, X64, "${{ matrix.os }}", "${{ matrix.extra }}"]
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
2
.github/workflows/upload_to_s3.yaml
vendored
2
.github/workflows/upload_to_s3.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: dawidd6/action-download-artifact@v4
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: package_all.yaml
|
||||
workflow_conclusion: success
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# RocksDB ADR
|
||||
|
||||
**Author**
|
||||
Marko Budiselic (github.com/gitbuda)
|
||||
|
||||
**Status**
|
||||
ACCEPTED
|
||||
|
||||
**Date**
|
||||
January 23, 2024
|
||||
|
||||
**Problem**
|
||||
|
||||
Interacting with data (reads and writes) on disk in a concurrent, safe, and
|
||||
fast way is a challenging task. Implementing all low-level primitives to
|
||||
interact with various disk hardware efficiently consumes significant
|
||||
engineering people. Whenever Memgraph has to store data on disk (or any
|
||||
other colder than RAM storage system), the problem is how to do that in the
|
||||
least amount of development time while satisfying all functional requirements
|
||||
(often performance).
|
||||
|
||||
**Criteria**
|
||||
|
||||
- working efficiently in a highly concurrent environment
|
||||
- easy integration with Memgraph's C++ codebase
|
||||
- providing low-level key-value API
|
||||
- heavily tested in production environments
|
||||
- providing abstractions for the storage hardware (even for cloud-based
|
||||
storages like S3)
|
||||
|
||||
**Decision**
|
||||
|
||||
There are a few robust key-value stores, but finding one that is
|
||||
production-ready and compatible with Memgraph's C++ codebase is challenging.
|
||||
**We select [RocksDB](https://github.com/facebook/rocksdb)** because it
|
||||
delivers robust API to manage data on disk; it's battle-tested in many
|
||||
production environments (many databases systems are embedding RocksDB), and
|
||||
it's the most compatible one.
|
||||
@@ -294,10 +294,3 @@ set_path_external_library(jemalloc STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/jemalloc/include/)
|
||||
|
||||
import_header_library(rangev3 ${CMAKE_CURRENT_SOURCE_DIR}/rangev3/include)
|
||||
|
||||
# Setup NuRaft
|
||||
import_external_library(nuraft STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nuraft/lib/libnuraft.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nuraft/include/)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(nuraft INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
@@ -126,7 +126,6 @@ declare -A primary_urls=(
|
||||
["absl"]="https://$local_cache_host/git/abseil-cpp.git"
|
||||
["jemalloc"]="https://$local_cache_host/git/jemalloc.git"
|
||||
["range-v3"]="https://$local_cache_host/git/ericniebler/range-v3.git"
|
||||
["nuraft"]="https://$local_cache_host/git/eBay/NuRaft.git"
|
||||
)
|
||||
|
||||
# The goal of secondary urls is to have links to the "source of truth" of
|
||||
@@ -156,7 +155,6 @@ declare -A secondary_urls=(
|
||||
["absl"]="https://github.com/abseil/abseil-cpp.git"
|
||||
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
|
||||
["range-v3"]="https://github.com/ericniebler/range-v3.git"
|
||||
["nuraft"]="https://github.com/eBay/NuRaft.git"
|
||||
)
|
||||
|
||||
# antlr
|
||||
@@ -279,10 +277,3 @@ popd
|
||||
#range-v3 release-0.12.0
|
||||
range_v3_ref="release-0.12.0"
|
||||
repo_clone_try_double "${primary_urls[range-v3]}" "${secondary_urls[range-v3]}" "rangev3" "$range_v3_ref"
|
||||
|
||||
# NuRaft
|
||||
nuraft_tag="v2.1.0"
|
||||
repo_clone_try_double "${primary_urls[nuraft]}" "${secondary_urls[nuraft]}" "nuraft" "$nuraft_tag" true
|
||||
pushd nuraft
|
||||
./prepare.sh
|
||||
popd
|
||||
|
||||
@@ -22,10 +22,8 @@ add_subdirectory(dbms)
|
||||
add_subdirectory(flags)
|
||||
add_subdirectory(distributed)
|
||||
add_subdirectory(replication)
|
||||
add_subdirectory(replication_handler)
|
||||
add_subdirectory(coordination)
|
||||
add_subdirectory(replication_coordination_glue)
|
||||
add_subdirectory(system)
|
||||
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
|
||||
@@ -45,10 +43,10 @@ set(mg_single_node_v2_sources
|
||||
add_executable(memgraph ${mg_single_node_v2_sources})
|
||||
target_include_directories(memgraph PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(memgraph stdc++fs Threads::Threads
|
||||
mg-telemetry mg-communication mg-communication-metrics mg-memory mg-utils mg-license mg-settings mg-glue mg-flags mg::system mg::replication_handler)
|
||||
mg-telemetry mg-communication mg-communication-metrics mg-memory mg-utils mg-license mg-settings mg-glue mg-flags)
|
||||
|
||||
# NOTE: `include/mg_procedure.syms` describes a pattern match for symbols which
|
||||
# should be dynamically exported, so that `dlopen` can correctly link th
|
||||
# should be dynamically exported, so that `dlopen` can correctly link the
|
||||
# symbols in custom procedure module libraries.
|
||||
target_link_libraries(memgraph "-Wl,--dynamic-list=${CMAKE_SOURCE_DIR}/include/mg_procedure.syms")
|
||||
set_target_properties(memgraph PROPERTIES
|
||||
|
||||
@@ -2,9 +2,7 @@ set(auth_src_files
|
||||
auth.cpp
|
||||
crypto.cpp
|
||||
models.cpp
|
||||
module.cpp
|
||||
rpc.cpp
|
||||
replication_handlers.cpp)
|
||||
module.cpp)
|
||||
|
||||
find_package(Seccomp REQUIRED)
|
||||
find_package(fmt REQUIRED)
|
||||
@@ -13,7 +11,7 @@ find_package(gflags REQUIRED)
|
||||
|
||||
add_library(mg-auth STATIC ${auth_src_files})
|
||||
target_link_libraries(mg-auth json libbcrypt gflags fmt::fmt)
|
||||
target_link_libraries(mg-auth mg-utils mg-kvstore mg-license mg::system mg-replication)
|
||||
target_link_libraries(mg-auth mg-utils mg-kvstore mg-license )
|
||||
|
||||
target_link_libraries(mg-auth ${Seccomp_LIBRARIES})
|
||||
target_include_directories(mg-auth SYSTEM PRIVATE ${Seccomp_INCLUDE_DIRS})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
||||
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||
@@ -8,18 +8,17 @@
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "auth/crypto.hpp"
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "auth/rpc.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "system/transaction.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
@@ -44,84 +43,9 @@ DEFINE_VALIDATED_int32(auth_module_timeout_ms, 10000,
|
||||
FLAG_IN_RANGE(100, 1800000));
|
||||
|
||||
namespace memgraph::auth {
|
||||
|
||||
namespace {
|
||||
#ifdef MG_ENTERPRISE
|
||||
/**
|
||||
* REPLICATION SYSTEM ACTION IMPLEMENTATIONS
|
||||
*/
|
||||
struct UpdateAuthData : memgraph::system::ISystemAction {
|
||||
explicit UpdateAuthData(User user) : user_{std::move(user)}, role_{std::nullopt} {}
|
||||
explicit UpdateAuthData(Role role) : user_{std::nullopt}, role_{std::move(role)} {}
|
||||
|
||||
void DoDurability() override { /* Done during Auth execution */
|
||||
}
|
||||
|
||||
bool DoReplication(replication::ReplicationClient &client, replication::ReplicationEpoch const &epoch,
|
||||
memgraph::system::Transaction const &txn) const override {
|
||||
auto check_response = [](const replication::UpdateAuthDataRes &response) { return response.success; };
|
||||
if (user_) {
|
||||
return client.SteamAndFinalizeDelta<replication::UpdateAuthDataRpc>(
|
||||
check_response, std::string{epoch.id()}, txn.last_committed_system_timestamp(), txn.timestamp(), *user_);
|
||||
}
|
||||
if (role_) {
|
||||
return client.SteamAndFinalizeDelta<replication::UpdateAuthDataRpc>(
|
||||
check_response, std::string{epoch.id()}, txn.last_committed_system_timestamp(), txn.timestamp(), *role_);
|
||||
}
|
||||
// Should never get here
|
||||
MG_ASSERT(false, "Trying to update auth data that is not a user nor a role");
|
||||
return {};
|
||||
}
|
||||
|
||||
void PostReplication(replication::RoleMainData &mainData) const override {}
|
||||
|
||||
private:
|
||||
std::optional<User> user_;
|
||||
std::optional<Role> role_;
|
||||
};
|
||||
|
||||
struct DropAuthData : memgraph::system::ISystemAction {
|
||||
enum class AuthDataType { USER, ROLE };
|
||||
|
||||
explicit DropAuthData(AuthDataType type, std::string_view name) : type_{type}, name_{name} {}
|
||||
|
||||
void DoDurability() override { /* Done during Auth execution */
|
||||
}
|
||||
|
||||
bool DoReplication(replication::ReplicationClient &client, replication::ReplicationEpoch const &epoch,
|
||||
memgraph::system::Transaction const &txn) const override {
|
||||
auto check_response = [](const replication::DropAuthDataRes &response) { return response.success; };
|
||||
|
||||
memgraph::replication::DropAuthDataReq::DataType type{};
|
||||
switch (type_) {
|
||||
case AuthDataType::USER:
|
||||
type = memgraph::replication::DropAuthDataReq::DataType::USER;
|
||||
break;
|
||||
case AuthDataType::ROLE:
|
||||
type = memgraph::replication::DropAuthDataReq::DataType::ROLE;
|
||||
break;
|
||||
}
|
||||
return client.SteamAndFinalizeDelta<replication::DropAuthDataRpc>(
|
||||
check_response, std::string{epoch.id()}, txn.last_committed_system_timestamp(), txn.timestamp(), type, name_);
|
||||
}
|
||||
void PostReplication(replication::RoleMainData &mainData) const override {}
|
||||
|
||||
private:
|
||||
AuthDataType type_;
|
||||
std::string name_;
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* CONSTANTS
|
||||
*/
|
||||
const std::string kUserPrefix = "user:";
|
||||
const std::string kRolePrefix = "role:";
|
||||
const std::string kLinkPrefix = "link:";
|
||||
const std::string kVersion = "version";
|
||||
|
||||
static constexpr auto kVersionV1 = "V1";
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* All data stored in the `Auth` storage is stored in an underlying
|
||||
@@ -140,59 +64,7 @@ static constexpr auto kVersionV1 = "V1";
|
||||
* key="link:<username>", value="<rolename>"
|
||||
*/
|
||||
|
||||
namespace {
|
||||
void MigrateVersions(kvstore::KVStore &store) {
|
||||
static constexpr auto kPasswordHashV0V1 = "password_hash";
|
||||
auto version_str = store.Get(kVersion);
|
||||
|
||||
if (!version_str) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
// pre versioning, add version to the store
|
||||
auto puts = std::map<std::string, std::string>{{kVersion, kVersionV1}};
|
||||
|
||||
// also add hash kind into durability
|
||||
|
||||
auto it = store.begin(kUserPrefix);
|
||||
auto const e = store.end(kUserPrefix);
|
||||
|
||||
if (it != e) {
|
||||
const auto hash_algo = CurrentHashAlgorithm();
|
||||
spdlog::info("Updating auth durability, assuming previously stored as {}", AsString(hash_algo));
|
||||
|
||||
for (; it != e; ++it) {
|
||||
auto const &[key, value] = *it;
|
||||
try {
|
||||
auto user_data = nlohmann::json::parse(value);
|
||||
|
||||
auto password_hash = user_data[kPasswordHashV0V1];
|
||||
if (!password_hash.is_string()) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
// upgrade the password_hash to include the hash algortihm
|
||||
if (password_hash.empty()) {
|
||||
user_data[kPasswordHashV0V1] = nullptr;
|
||||
} else {
|
||||
user_data[kPasswordHashV0V1] = HashedPassword{hash_algo, password_hash};
|
||||
}
|
||||
puts.emplace(key, user_data.dump());
|
||||
} catch (const nlohmann::json::parse_error &e) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform migration to V1
|
||||
store.PutMultiple(puts);
|
||||
version_str = kVersionV1;
|
||||
}
|
||||
}
|
||||
}; // namespace
|
||||
|
||||
Auth::Auth(std::string storage_directory, Config config)
|
||||
: storage_(std::move(storage_directory)), module_(FLAGS_auth_module_executable), config_{std::move(config)} {
|
||||
MigrateVersions(storage_);
|
||||
}
|
||||
Auth::Auth(const std::string &storage_directory) : storage_(storage_directory), module_(FLAGS_auth_module_executable) {}
|
||||
|
||||
std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) {
|
||||
if (module_.IsUsed()) {
|
||||
@@ -223,12 +95,6 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
||||
// Authenticate the user.
|
||||
if (!is_authenticated) return std::nullopt;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* The auth module should not update auth data.
|
||||
* There is now way to replicate it and we should not be storing sensitive data if we don't have to.
|
||||
*/
|
||||
|
||||
// Find or create the user and return it.
|
||||
auto user = GetUser(username);
|
||||
if (!user) {
|
||||
@@ -247,7 +113,7 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
UpdatePassword(*user, password);
|
||||
user->UpdatePassword(password);
|
||||
}
|
||||
if (FLAGS_auth_module_manage_roles) {
|
||||
if (!rolename.empty()) {
|
||||
@@ -289,10 +155,6 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
||||
username, "https://memgr.ph/auth"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (user->UpgradeHash(password)) {
|
||||
SaveUser(*user);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -321,7 +183,7 @@ std::optional<User> Auth::GetUser(const std::string &username_orig) const {
|
||||
return user;
|
||||
}
|
||||
|
||||
void Auth::SaveUser(const User &user, system::Transaction *system_tx) {
|
||||
void Auth::SaveUser(const User &user) {
|
||||
bool success = false;
|
||||
if (const auto *role = user.role(); role != nullptr) {
|
||||
success = storage_.PutMultiple(
|
||||
@@ -333,72 +195,26 @@ void Auth::SaveUser(const User &user, system::Transaction *system_tx) {
|
||||
if (!success) {
|
||||
throw AuthException("Couldn't save user '{}'!", user.username());
|
||||
}
|
||||
// All changes to the user end up calling this function, so no need to add a delta anywhere else
|
||||
if (system_tx) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
system_tx->AddAction<UpdateAuthData>(user);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Auth::UpdatePassword(auth::User &user, const std::optional<std::string> &password) {
|
||||
// Check if null
|
||||
if (!password) {
|
||||
if (!config_.password_permit_null) {
|
||||
throw AuthException("Null passwords aren't permitted!");
|
||||
}
|
||||
} else {
|
||||
// Check if compliant with our filter
|
||||
if (config_.custom_password_regex) {
|
||||
if (const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw AuthException(
|
||||
"Custom password regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
glue::kDefaultPasswordRegex,
|
||||
license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
|
||||
}
|
||||
}
|
||||
if (!std::regex_match(*password, config_.password_regex)) {
|
||||
throw AuthException(
|
||||
"The user password doesn't conform to the required strength! Regex: "
|
||||
"\"{}\"",
|
||||
config_.password_regex_str);
|
||||
}
|
||||
}
|
||||
|
||||
// All checks passed; update
|
||||
user.UpdatePassword(password);
|
||||
}
|
||||
|
||||
std::optional<User> Auth::AddUser(const std::string &username, const std::optional<std::string> &password,
|
||||
system::Transaction *system_tx) {
|
||||
if (!NameRegexMatch(username)) {
|
||||
throw AuthException("Invalid user name.");
|
||||
}
|
||||
std::optional<User> Auth::AddUser(const std::string &username, const std::optional<std::string> &password) {
|
||||
auto existing_user = GetUser(username);
|
||||
if (existing_user) return std::nullopt;
|
||||
auto existing_role = GetRole(username);
|
||||
if (existing_role) return std::nullopt;
|
||||
auto new_user = User(username);
|
||||
UpdatePassword(new_user, password);
|
||||
SaveUser(new_user, system_tx);
|
||||
new_user.UpdatePassword(password);
|
||||
SaveUser(new_user);
|
||||
return new_user;
|
||||
}
|
||||
|
||||
bool Auth::RemoveUser(const std::string &username_orig, system::Transaction *system_tx) {
|
||||
bool Auth::RemoveUser(const std::string &username_orig) {
|
||||
auto username = utils::ToLowerCase(username_orig);
|
||||
if (!storage_.Get(kUserPrefix + username)) return false;
|
||||
std::vector<std::string> keys({kLinkPrefix + username, kUserPrefix + username});
|
||||
if (!storage_.DeleteMultiple(keys)) {
|
||||
throw AuthException("Couldn't remove user '{}'!", username);
|
||||
}
|
||||
// Handling drop user delta
|
||||
if (system_tx) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
system_tx->AddAction<DropAuthData>(DropAuthData::AuthDataType::USER, username);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -415,19 +231,6 @@ std::vector<auth::User> Auth::AllUsers() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> Auth::AllUsernames() const {
|
||||
std::vector<std::string> ret;
|
||||
for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) {
|
||||
auto username = it->first.substr(kUserPrefix.size());
|
||||
if (username != utils::ToLowerCase(username)) continue;
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
ret.push_back(username);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Auth::HasUsers() const { return storage_.begin(kUserPrefix) != storage_.end(kUserPrefix); }
|
||||
|
||||
std::optional<Role> Auth::GetRole(const std::string &rolename_orig) const {
|
||||
@@ -445,30 +248,21 @@ std::optional<Role> Auth::GetRole(const std::string &rolename_orig) const {
|
||||
return Role::Deserialize(data);
|
||||
}
|
||||
|
||||
void Auth::SaveRole(const Role &role, system::Transaction *system_tx) {
|
||||
void Auth::SaveRole(const Role &role) {
|
||||
if (!storage_.Put(kRolePrefix + role.rolename(), role.Serialize().dump())) {
|
||||
throw AuthException("Couldn't save role '{}'!", role.rolename());
|
||||
}
|
||||
// All changes to the role end up calling this function, so no need to add a delta anywhere else
|
||||
if (system_tx) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
system_tx->AddAction<UpdateAuthData>(role);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Role> Auth::AddRole(const std::string &rolename, system::Transaction *system_tx) {
|
||||
if (!NameRegexMatch(rolename)) {
|
||||
throw AuthException("Invalid role name.");
|
||||
}
|
||||
std::optional<Role> Auth::AddRole(const std::string &rolename) {
|
||||
if (auto existing_role = GetRole(rolename)) return std::nullopt;
|
||||
if (auto existing_user = GetUser(rolename)) return std::nullopt;
|
||||
auto new_role = Role(rolename);
|
||||
SaveRole(new_role, system_tx);
|
||||
SaveRole(new_role);
|
||||
return new_role;
|
||||
}
|
||||
|
||||
bool Auth::RemoveRole(const std::string &rolename_orig, system::Transaction *system_tx) {
|
||||
bool Auth::RemoveRole(const std::string &rolename_orig) {
|
||||
auto rolename = utils::ToLowerCase(rolename_orig);
|
||||
if (!storage_.Get(kRolePrefix + rolename)) return false;
|
||||
std::vector<std::string> keys;
|
||||
@@ -481,12 +275,6 @@ bool Auth::RemoveRole(const std::string &rolename_orig, system::Transaction *sys
|
||||
if (!storage_.DeleteMultiple(keys)) {
|
||||
throw AuthException("Couldn't remove role '{}'!", rolename);
|
||||
}
|
||||
// Handling drop role delta
|
||||
if (system_tx) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
system_tx->AddAction<DropAuthData>(DropAuthData::AuthDataType::ROLE, rolename);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -504,18 +292,6 @@ std::vector<auth::Role> Auth::AllRoles() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> Auth::AllRolenames() const {
|
||||
std::vector<std::string> ret;
|
||||
for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix); ++it) {
|
||||
auto rolename = it->first.substr(kRolePrefix.size());
|
||||
if (rolename != utils::ToLowerCase(rolename)) continue;
|
||||
if (auto role = GetRole(rolename)) {
|
||||
ret.push_back(rolename);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig) const {
|
||||
const auto rolename = utils::ToLowerCase(rolename_orig);
|
||||
std::vector<auth::User> ret;
|
||||
@@ -535,67 +311,52 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name, system::Transaction *system_tx) {
|
||||
bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
|
||||
if (auto user = GetUser(name)) {
|
||||
if (db == kAllDatabases) {
|
||||
user->db_access().GrantAll();
|
||||
} else {
|
||||
user->db_access().Add(db);
|
||||
}
|
||||
SaveUser(*user, system_tx);
|
||||
SaveUser(*user);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name, system::Transaction *system_tx) {
|
||||
bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name) {
|
||||
if (auto user = GetUser(name)) {
|
||||
if (db == kAllDatabases) {
|
||||
user->db_access().DenyAll();
|
||||
} else {
|
||||
user->db_access().Remove(db);
|
||||
}
|
||||
SaveUser(*user, system_tx);
|
||||
SaveUser(*user);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Auth::DeleteDatabase(const std::string &db, system::Transaction *system_tx) {
|
||||
void Auth::DeleteDatabase(const std::string &db) {
|
||||
for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) {
|
||||
auto username = it->first.substr(kUserPrefix.size());
|
||||
if (auto user = GetUser(username)) {
|
||||
user->db_access().Delete(db);
|
||||
SaveUser(*user, system_tx);
|
||||
SaveUser(*user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Auth::SetMainDatabase(std::string_view db, const std::string &name, system::Transaction *system_tx) {
|
||||
bool Auth::SetMainDatabase(std::string_view db, const std::string &name) {
|
||||
if (auto user = GetUser(name)) {
|
||||
if (!user->db_access().SetDefault(db)) {
|
||||
throw AuthException("Couldn't set default database '{}' for user '{}'!", db, name);
|
||||
}
|
||||
SaveUser(*user, system_tx);
|
||||
SaveUser(*user);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Auth::NameRegexMatch(const std::string &user_or_role) const {
|
||||
if (config_.custom_name_regex) {
|
||||
if (const auto license_check_result =
|
||||
memgraph::license::global_license_checker.IsEnterpriseValid(memgraph::utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw memgraph::auth::AuthException(
|
||||
"Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
glue::kDefaultUserRoleRegex,
|
||||
memgraph::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
|
||||
}
|
||||
}
|
||||
return std::regex_match(user_or_role, config_.name_regex);
|
||||
}
|
||||
|
||||
} // namespace memgraph::auth
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
||||
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||
@@ -10,23 +10,16 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <vector>
|
||||
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "auth/module.hpp"
|
||||
#include "glue/auth_global.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "system/action.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace memgraph::auth {
|
||||
|
||||
class Auth;
|
||||
using SynchedAuth = memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>;
|
||||
|
||||
static const constexpr char *const kAllDatabases = "*";
|
||||
|
||||
/**
|
||||
@@ -38,47 +31,7 @@ static const constexpr char *const kAllDatabases = "*";
|
||||
*/
|
||||
class Auth final {
|
||||
public:
|
||||
struct Config {
|
||||
Config() {}
|
||||
Config(std::string name_regex, std::string password_regex, bool password_permit_null)
|
||||
: name_regex_str{std::move(name_regex)},
|
||||
password_regex_str{std::move(password_regex)},
|
||||
password_permit_null{password_permit_null},
|
||||
custom_name_regex{name_regex_str != glue::kDefaultUserRoleRegex},
|
||||
name_regex{name_regex_str},
|
||||
custom_password_regex{password_regex_str != glue::kDefaultPasswordRegex},
|
||||
password_regex{password_regex_str} {}
|
||||
|
||||
std::string name_regex_str{glue::kDefaultUserRoleRegex};
|
||||
std::string password_regex_str{glue::kDefaultPasswordRegex};
|
||||
bool password_permit_null{true};
|
||||
|
||||
private:
|
||||
friend class Auth;
|
||||
bool custom_name_regex{false};
|
||||
std::regex name_regex{name_regex_str};
|
||||
bool custom_password_regex{false};
|
||||
std::regex password_regex{password_regex_str};
|
||||
};
|
||||
|
||||
explicit Auth(std::string storage_directory, Config config);
|
||||
|
||||
/**
|
||||
* @brief Set the Config object
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
void SetConfig(Config config) {
|
||||
// NOTE: The Auth class itself is not thread-safe, higher-level code needs to synchronize it when using it.
|
||||
config_ = std::move(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
Config GetConfig() const { return config_; }
|
||||
explicit Auth(const std::string &storage_directory);
|
||||
|
||||
/**
|
||||
* Authenticates a user using his username and password.
|
||||
@@ -108,7 +61,7 @@ class Auth final {
|
||||
*
|
||||
* @throw AuthException if unable to save the user.
|
||||
*/
|
||||
void SaveUser(const User &user, system::Transaction *system_tx = nullptr);
|
||||
void SaveUser(const User &user);
|
||||
|
||||
/**
|
||||
* Creates a user if the user doesn't exist.
|
||||
@@ -119,8 +72,7 @@ class Auth final {
|
||||
* @return a user when the user is created, nullopt if the user exists
|
||||
* @throw AuthException if unable to save the user.
|
||||
*/
|
||||
std::optional<User> AddUser(const std::string &username, const std::optional<std::string> &password = std::nullopt,
|
||||
system::Transaction *system_tx = nullptr);
|
||||
std::optional<User> AddUser(const std::string &username, const std::optional<std::string> &password = std::nullopt);
|
||||
|
||||
/**
|
||||
* Removes a user from the storage.
|
||||
@@ -131,15 +83,7 @@ class Auth final {
|
||||
* doesn't exist
|
||||
* @throw AuthException if unable to remove the user.
|
||||
*/
|
||||
bool RemoveUser(const std::string &username, system::Transaction *system_tx = nullptr);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param user
|
||||
* @param password
|
||||
*/
|
||||
void UpdatePassword(auth::User &user, const std::optional<std::string> &password);
|
||||
bool RemoveUser(const std::string &username);
|
||||
|
||||
/**
|
||||
* Gets all users from the storage.
|
||||
@@ -149,13 +93,6 @@ class Auth final {
|
||||
*/
|
||||
std::vector<User> AllUsers() const;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @return std::vector<std::string>
|
||||
*/
|
||||
std::vector<std::string> AllUsernames() const;
|
||||
|
||||
/**
|
||||
* Returns whether there are users in the storage.
|
||||
*
|
||||
@@ -180,7 +117,7 @@ class Auth final {
|
||||
*
|
||||
* @throw AuthException if unable to save the role.
|
||||
*/
|
||||
void SaveRole(const Role &role, system::Transaction *system_tx = nullptr);
|
||||
void SaveRole(const Role &role);
|
||||
|
||||
/**
|
||||
* Creates a role if the role doesn't exist.
|
||||
@@ -190,7 +127,7 @@ class Auth final {
|
||||
* @return a role when the role is created, nullopt if the role exists
|
||||
* @throw AuthException if unable to save the role.
|
||||
*/
|
||||
std::optional<Role> AddRole(const std::string &rolename, system::Transaction *system_tx = nullptr);
|
||||
std::optional<Role> AddRole(const std::string &rolename);
|
||||
|
||||
/**
|
||||
* Removes a role from the storage.
|
||||
@@ -201,7 +138,7 @@ class Auth final {
|
||||
* doesn't exist
|
||||
* @throw AuthException if unable to remove the role.
|
||||
*/
|
||||
bool RemoveRole(const std::string &rolename, system::Transaction *system_tx = nullptr);
|
||||
bool RemoveRole(const std::string &rolename);
|
||||
|
||||
/**
|
||||
* Gets all roles from the storage.
|
||||
@@ -211,13 +148,6 @@ class Auth final {
|
||||
*/
|
||||
std::vector<Role> AllRoles() const;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @return std::vector<std::string>
|
||||
*/
|
||||
std::vector<std::string> AllRolenames() const;
|
||||
|
||||
/**
|
||||
* Gets all users for a role from the storage.
|
||||
*
|
||||
@@ -237,7 +167,7 @@ class Auth final {
|
||||
* @return true on success
|
||||
* @throw AuthException if unable to find or update the user
|
||||
*/
|
||||
bool RevokeDatabaseFromUser(const std::string &db, const std::string &name, system::Transaction *system_tx = nullptr);
|
||||
bool RevokeDatabaseFromUser(const std::string &db, const std::string &name);
|
||||
|
||||
/**
|
||||
* @brief Grant access to individual database for a user.
|
||||
@@ -247,7 +177,7 @@ class Auth final {
|
||||
* @return true on success
|
||||
* @throw AuthException if unable to find or update the user
|
||||
*/
|
||||
bool GrantDatabaseToUser(const std::string &db, const std::string &name, system::Transaction *system_tx = nullptr);
|
||||
bool GrantDatabaseToUser(const std::string &db, const std::string &name);
|
||||
|
||||
/**
|
||||
* @brief Delete a database from all users.
|
||||
@@ -255,7 +185,7 @@ class Auth final {
|
||||
* @param db name of the database to delete
|
||||
* @throw AuthException if unable to read data
|
||||
*/
|
||||
void DeleteDatabase(const std::string &db, system::Transaction *system_tx = nullptr);
|
||||
void DeleteDatabase(const std::string &db);
|
||||
|
||||
/**
|
||||
* @brief Set main database for an individual user.
|
||||
@@ -265,24 +195,14 @@ class Auth final {
|
||||
* @return true on success
|
||||
* @throw AuthException if unable to find or update the user
|
||||
*/
|
||||
bool SetMainDatabase(std::string_view db, const std::string &name, system::Transaction *system_tx = nullptr);
|
||||
bool SetMainDatabase(std::string_view db, const std::string &name);
|
||||
#endif
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param user_or_role
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool NameRegexMatch(const std::string &user_or_role) const;
|
||||
|
||||
// Even though the `kvstore::KVStore` class is guaranteed to be thread-safe,
|
||||
// Auth is not thread-safe because modifying users and roles might require
|
||||
// more than one operation on the storage.
|
||||
kvstore::KVStore storage_;
|
||||
auth::Module module_;
|
||||
Config config_;
|
||||
};
|
||||
} // namespace memgraph::auth
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
||||
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||
@@ -22,14 +22,10 @@
|
||||
|
||||
namespace {
|
||||
using namespace std::literals;
|
||||
|
||||
constexpr auto kHashAlgo = "hash_algo";
|
||||
constexpr auto kPasswordHash = "password_hash";
|
||||
|
||||
inline constexpr std::array password_hash_mappings{
|
||||
std::pair{"bcrypt"sv, memgraph::auth::PasswordHashAlgorithm::BCRYPT},
|
||||
std::pair{"sha256"sv, memgraph::auth::PasswordHashAlgorithm::SHA256},
|
||||
std::pair{"sha256-multiple"sv, memgraph::auth::PasswordHashAlgorithm::SHA256_MULTIPLE}};
|
||||
inline constexpr std::array password_encryption_mappings{
|
||||
std::pair{"bcrypt"sv, memgraph::auth::PasswordEncryptionAlgorithm::BCRYPT},
|
||||
std::pair{"sha256"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256},
|
||||
std::pair{"sha256-multiple"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256_MULTIPLE}};
|
||||
|
||||
inline constexpr uint64_t ONE_SHA_ITERATION = 1;
|
||||
inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024;
|
||||
@@ -39,7 +35,7 @@ inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024;
|
||||
DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
|
||||
"The password encryption algorithm used for authentication.", {
|
||||
if (const auto result =
|
||||
memgraph::utils::IsValidEnumValueString(value, password_hash_mappings);
|
||||
memgraph::utils::IsValidEnumValueString(value, password_encryption_mappings);
|
||||
result.HasError()) {
|
||||
const auto error = result.GetError();
|
||||
switch (error) {
|
||||
@@ -49,7 +45,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
|
||||
}
|
||||
case memgraph::utils::ValidationError::InvalidValue: {
|
||||
std::cout << "Invalid value for password encryption algorithm. Allowed values: "
|
||||
<< memgraph::utils::GetAllowedEnumValuesString(password_hash_mappings)
|
||||
<< memgraph::utils::GetAllowedEnumValuesString(password_encryption_mappings)
|
||||
<< std::endl;
|
||||
break;
|
||||
}
|
||||
@@ -62,7 +58,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
|
||||
|
||||
namespace memgraph::auth {
|
||||
namespace BCrypt {
|
||||
std::string HashPassword(const std::string &password) {
|
||||
std::string EncryptPassword(const std::string &password) {
|
||||
char salt[BCRYPT_HASHSIZE];
|
||||
char hash[BCRYPT_HASHSIZE];
|
||||
|
||||
@@ -90,30 +86,16 @@ bool VerifyPassword(const std::string &password, const std::string &hash) {
|
||||
} // namespace BCrypt
|
||||
|
||||
namespace SHA {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto SHA_LENGTH = 64U;
|
||||
constexpr auto SALT_SIZE = 16U;
|
||||
constexpr auto SALT_SIZE_DURABLE = SALT_SIZE * 2;
|
||||
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
std::string HashPasswordOpenSSL3(std::string_view password, const uint64_t number_of_iterations,
|
||||
std::string_view salt) {
|
||||
std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t number_of_iterations) {
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
EVP_MD *md = EVP_MD_fetch(nullptr, "SHA2-256", nullptr);
|
||||
|
||||
EVP_DigestInit_ex(ctx, md, nullptr);
|
||||
|
||||
if (!salt.empty()) {
|
||||
DMG_ASSERT(salt.size() == SALT_SIZE);
|
||||
EVP_DigestUpdate(ctx, salt.data(), salt.size());
|
||||
}
|
||||
|
||||
for (auto i = 0; i < number_of_iterations; i++) {
|
||||
EVP_DigestUpdate(ctx, password.data(), password.size());
|
||||
EVP_DigestUpdate(ctx, password.c_str(), password.size());
|
||||
}
|
||||
EVP_DigestFinal_ex(ctx, hash, nullptr);
|
||||
|
||||
@@ -121,11 +103,6 @@ std::string HashPasswordOpenSSL3(std::string_view password, const uint64_t numbe
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
std::stringstream result_stream;
|
||||
|
||||
for (unsigned char salt_char : salt) {
|
||||
result_stream << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU);
|
||||
}
|
||||
|
||||
for (auto hash_char : hash) {
|
||||
result_stream << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
|
||||
}
|
||||
@@ -133,27 +110,17 @@ std::string HashPasswordOpenSSL3(std::string_view password, const uint64_t numbe
|
||||
return result_stream.str();
|
||||
}
|
||||
#else
|
||||
std::string HashPasswordOpenSSL1_1(std::string_view password, const uint64_t number_of_iterations,
|
||||
std::string_view salt) {
|
||||
std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_t number_of_iterations) {
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
|
||||
if (!salt.empty()) {
|
||||
DMG_ASSERT(salt.size() == SALT_SIZE);
|
||||
SHA256_Update(&sha256, salt.data(), salt.size());
|
||||
}
|
||||
|
||||
for (auto i = 0; i < number_of_iterations; i++) {
|
||||
SHA256_Update(&sha256, password.data(), password.size());
|
||||
SHA256_Update(&sha256, password.c_str(), password.size());
|
||||
}
|
||||
SHA256_Final(hash, &sha256);
|
||||
|
||||
std::stringstream ss;
|
||||
for (unsigned char salt_char : salt) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU);
|
||||
}
|
||||
for (auto hash_char : hash) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
|
||||
}
|
||||
@@ -162,144 +129,55 @@ std::string HashPasswordOpenSSL1_1(std::string_view password, const uint64_t num
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string HashPassword(std::string_view password, const uint64_t number_of_iterations, std::string_view salt) {
|
||||
std::string EncryptPassword(const std::string &password, const uint64_t number_of_iterations) {
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
return HashPasswordOpenSSL3(password, number_of_iterations, salt);
|
||||
return EncryptPasswordOpenSSL3(password, number_of_iterations);
|
||||
#else
|
||||
return HashPasswordOpenSSL1_1(password, number_of_iterations, salt);
|
||||
return EncryptPasswordOpenSSL1_1(password, number_of_iterations);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ExtractSalt(std::string_view salt_durable) -> std::array<char, SALT_SIZE> {
|
||||
static_assert(SALT_SIZE_DURABLE % 2 == 0);
|
||||
static_assert(SALT_SIZE_DURABLE / 2 == SALT_SIZE);
|
||||
|
||||
MG_ASSERT(salt_durable.size() == SALT_SIZE_DURABLE);
|
||||
auto const *b = salt_durable.cbegin();
|
||||
auto const *const e = salt_durable.cend();
|
||||
|
||||
auto salt = std::array<char, SALT_SIZE>{};
|
||||
auto *inserter = salt.begin();
|
||||
|
||||
auto const toval = [](char a) -> uint8_t {
|
||||
if ('0' <= a && a <= '9') {
|
||||
return a - '0';
|
||||
}
|
||||
if ('a' <= a && a <= 'f') {
|
||||
return 10 + (a - 'a');
|
||||
}
|
||||
MG_ASSERT(false, "Currupt hash, can't extract salt");
|
||||
__builtin_unreachable();
|
||||
};
|
||||
|
||||
for (; b != e; b += 2, ++inserter) {
|
||||
*inserter = static_cast<char>(static_cast<uint8_t>(toval(b[0]) << 4U) | toval(b[1]));
|
||||
}
|
||||
return salt;
|
||||
}
|
||||
|
||||
bool IsSalted(std::string_view hash) { return hash.size() == SHA_LENGTH + SALT_SIZE_DURABLE; }
|
||||
|
||||
bool VerifyPassword(std::string_view password, std::string_view hash, const uint64_t number_of_iterations) {
|
||||
auto password_hash = std::invoke([&] {
|
||||
if (hash.size() == SHA_LENGTH) [[unlikely]] {
|
||||
// Just SHA256
|
||||
return HashPassword(password, number_of_iterations, {});
|
||||
} else {
|
||||
// SHA256 + SALT
|
||||
MG_ASSERT(IsSalted(hash));
|
||||
auto const salt_durable = std::string_view{hash.data(), SALT_SIZE_DURABLE};
|
||||
std::array<char, SALT_SIZE> salt = ExtractSalt(salt_durable);
|
||||
return HashPassword(password, number_of_iterations, {salt.data(), salt.size()});
|
||||
}
|
||||
});
|
||||
bool VerifyPassword(const std::string &password, const std::string &hash, const uint64_t number_of_iterations) {
|
||||
auto password_hash = EncryptPassword(password, number_of_iterations);
|
||||
return password_hash == hash;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace SHA
|
||||
|
||||
HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo) {
|
||||
auto const hash_algo = override_algo.value_or(CurrentHashAlgorithm());
|
||||
auto password_hash = std::invoke([&] {
|
||||
switch (hash_algo) {
|
||||
case PasswordHashAlgorithm::BCRYPT: {
|
||||
return BCrypt::HashPassword(password);
|
||||
}
|
||||
case PasswordHashAlgorithm::SHA256:
|
||||
case PasswordHashAlgorithm::SHA256_MULTIPLE: {
|
||||
auto gen = std::mt19937(std::random_device{}());
|
||||
auto salt = std::array<char, SHA::SALT_SIZE>{};
|
||||
auto dis = std::uniform_int_distribution<unsigned char>(0, 255);
|
||||
std::generate(salt.begin(), salt.end(), [&]() { return dis(gen); });
|
||||
auto iterations = (hash_algo == PasswordHashAlgorithm::SHA256) ? ONE_SHA_ITERATION : MULTIPLE_SHA_ITERATIONS;
|
||||
return SHA::HashPassword(password, iterations, {salt.data(), salt.size()});
|
||||
}
|
||||
}
|
||||
});
|
||||
return HashedPassword{hash_algo, std::move(password_hash)};
|
||||
};
|
||||
bool VerifyPassword(const std::string &password, const std::string &hash) {
|
||||
const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>(
|
||||
FLAGS_password_encryption_algorithm, password_encryption_mappings);
|
||||
|
||||
namespace {
|
||||
|
||||
auto InternalParseHashAlgorithm(std::string_view algo) -> PasswordHashAlgorithm {
|
||||
auto maybe_parsed = utils::StringToEnum<PasswordHashAlgorithm>(algo, password_hash_mappings);
|
||||
if (!maybe_parsed) {
|
||||
throw AuthException("Invalid password encryption '{}'!", algo);
|
||||
if (!password_encryption_algorithm.has_value()) {
|
||||
throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
|
||||
}
|
||||
return *maybe_parsed;
|
||||
}
|
||||
|
||||
PasswordHashAlgorithm &InternalCurrentHashAlgorithm() {
|
||||
static auto current = PasswordHashAlgorithm::BCRYPT;
|
||||
static std::once_flag flag;
|
||||
std::call_once(flag, [] { current = InternalParseHashAlgorithm(FLAGS_password_encryption_algorithm); });
|
||||
return current;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
auto CurrentHashAlgorithm() -> PasswordHashAlgorithm { return InternalCurrentHashAlgorithm(); }
|
||||
|
||||
void SetHashAlgorithm(std::string_view algo) {
|
||||
auto ¤t = InternalCurrentHashAlgorithm();
|
||||
current = InternalParseHashAlgorithm(algo);
|
||||
}
|
||||
|
||||
auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view {
|
||||
return *utils::EnumToString<PasswordHashAlgorithm>(hash_algo, password_hash_mappings);
|
||||
}
|
||||
|
||||
bool HashedPassword::VerifyPassword(const std::string &password) {
|
||||
switch (hash_algo) {
|
||||
case PasswordHashAlgorithm::BCRYPT:
|
||||
return BCrypt::VerifyPassword(password, password_hash);
|
||||
case PasswordHashAlgorithm::SHA256:
|
||||
return SHA::VerifyPassword(password, password_hash, ONE_SHA_ITERATION);
|
||||
case PasswordHashAlgorithm::SHA256_MULTIPLE:
|
||||
return SHA::VerifyPassword(password, password_hash, MULTIPLE_SHA_ITERATIONS);
|
||||
switch (password_encryption_algorithm.value()) {
|
||||
case PasswordEncryptionAlgorithm::BCRYPT:
|
||||
return BCrypt::VerifyPassword(password, hash);
|
||||
case PasswordEncryptionAlgorithm::SHA256:
|
||||
return SHA::VerifyPassword(password, hash, ONE_SHA_ITERATION);
|
||||
case PasswordEncryptionAlgorithm::SHA256_MULTIPLE:
|
||||
return SHA::VerifyPassword(password, hash, MULTIPLE_SHA_ITERATIONS);
|
||||
}
|
||||
|
||||
throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const HashedPassword &p) {
|
||||
j = nlohmann::json{{kHashAlgo, p.hash_algo}, {kPasswordHash, p.password_hash}};
|
||||
}
|
||||
std::string EncryptPassword(const std::string &password) {
|
||||
const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>(
|
||||
FLAGS_password_encryption_algorithm, password_encryption_mappings);
|
||||
|
||||
void from_json(const nlohmann::json &j, HashedPassword &p) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
||||
PasswordHashAlgorithm hash_algo;
|
||||
j.at(kHashAlgo).get_to(hash_algo);
|
||||
auto password_hash = j.value(kPasswordHash, std::string());
|
||||
p = HashedPassword{hash_algo, std::move(password_hash)};
|
||||
}
|
||||
if (!password_encryption_algorithm.has_value()) {
|
||||
throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
|
||||
}
|
||||
|
||||
bool HashedPassword::IsSalted() const {
|
||||
switch (hash_algo) {
|
||||
case PasswordHashAlgorithm::BCRYPT:
|
||||
return true;
|
||||
case PasswordHashAlgorithm::SHA256:
|
||||
case PasswordHashAlgorithm::SHA256_MULTIPLE:
|
||||
return SHA::IsSalted(password_hash);
|
||||
switch (password_encryption_algorithm.value()) {
|
||||
case PasswordEncryptionAlgorithm::BCRYPT:
|
||||
return BCrypt::EncryptPassword(password);
|
||||
case PasswordEncryptionAlgorithm::SHA256:
|
||||
return SHA::EncryptPassword(password, ONE_SHA_ITERATION);
|
||||
case PasswordEncryptionAlgorithm::SHA256_MULTIPLE:
|
||||
return SHA::EncryptPassword(password, MULTIPLE_SHA_ITERATIONS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
||||
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||
@@ -8,45 +8,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <json/json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace memgraph::auth {
|
||||
/// Need to be stable, auth durability depends on this
|
||||
enum class PasswordHashAlgorithm : uint8_t { BCRYPT = 0, SHA256 = 1, SHA256_MULTIPLE = 2 };
|
||||
enum class PasswordEncryptionAlgorithm : uint8_t { BCRYPT, SHA256, SHA256_MULTIPLE };
|
||||
|
||||
void SetHashAlgorithm(std::string_view algo);
|
||||
/// @throw AuthException if unable to encrypt the password.
|
||||
std::string EncryptPassword(const std::string &password);
|
||||
|
||||
auto CurrentHashAlgorithm() -> PasswordHashAlgorithm;
|
||||
|
||||
auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view;
|
||||
|
||||
struct HashedPassword {
|
||||
HashedPassword() = default;
|
||||
HashedPassword(PasswordHashAlgorithm hash_algo, std::string password_hash)
|
||||
: hash_algo{hash_algo}, password_hash{std::move(password_hash)} {}
|
||||
HashedPassword(HashedPassword const &) = default;
|
||||
HashedPassword(HashedPassword &&) = default;
|
||||
HashedPassword &operator=(HashedPassword const &) = default;
|
||||
HashedPassword &operator=(HashedPassword &&) = default;
|
||||
|
||||
friend bool operator==(HashedPassword const &, HashedPassword const &) = default;
|
||||
|
||||
bool VerifyPassword(const std::string &password);
|
||||
|
||||
bool IsSalted() const;
|
||||
|
||||
auto HashAlgo() const -> PasswordHashAlgorithm { return hash_algo; }
|
||||
|
||||
friend void to_json(nlohmann::json &j, const HashedPassword &p);
|
||||
friend void from_json(const nlohmann::json &j, HashedPassword &p);
|
||||
|
||||
private:
|
||||
PasswordHashAlgorithm hash_algo{PasswordHashAlgorithm::BCRYPT};
|
||||
std::string password_hash{};
|
||||
};
|
||||
|
||||
/// @throw AuthException if unable to hash the password.
|
||||
HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo = {});
|
||||
/// @throw AuthException if unable to verify the password.
|
||||
bool VerifyPassword(const std::string &password, const std::string &hash);
|
||||
} // namespace memgraph::auth
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "auth/models.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <regex>
|
||||
#include <utility>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
@@ -20,26 +21,22 @@
|
||||
#include "query/constants.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords.");
|
||||
|
||||
inline constexpr std::string_view default_password_regex = ".+";
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
|
||||
"The regular expression that should be used to match the entire "
|
||||
"entered password to ensure its strength.");
|
||||
|
||||
namespace memgraph::auth {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRoleName = "rolename";
|
||||
constexpr auto kPermissions = "permissions";
|
||||
constexpr auto kGrants = "grants";
|
||||
constexpr auto kDenies = "denies";
|
||||
constexpr auto kUsername = "username";
|
||||
constexpr auto kPasswordHash = "password_hash";
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
constexpr auto kGlobalPermission = "global_permission";
|
||||
constexpr auto kFineGrainedAccessHandler = "fine_grained_access_handler";
|
||||
constexpr auto kAllowAll = "allow_all";
|
||||
constexpr auto kDefault = "default";
|
||||
constexpr auto kDatabases = "databases";
|
||||
#endif
|
||||
|
||||
// Constant list of all available permissions.
|
||||
const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
|
||||
Permission::CREATE,
|
||||
@@ -248,9 +245,8 @@ std::vector<Permission> Permissions::GetDenies() const {
|
||||
|
||||
nlohmann::json Permissions::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
|
||||
data[kGrants] = grants_;
|
||||
data[kDenies] = denies_;
|
||||
data["grants"] = grants_;
|
||||
data["denies"] = denies_;
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -258,10 +254,10 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load permissions data!");
|
||||
}
|
||||
if (!data[kGrants].is_number_unsigned() || !data[kDenies].is_number_unsigned()) {
|
||||
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
|
||||
throw AuthException("Couldn't load permissions data!");
|
||||
}
|
||||
return Permissions{data[kGrants], data[kDenies]};
|
||||
return Permissions{data["grants"], data["denies"]};
|
||||
}
|
||||
|
||||
uint64_t Permissions::grants() const { return grants_; }
|
||||
@@ -323,8 +319,8 @@ nlohmann::json FineGrainedAccessPermissions::Serialize() const {
|
||||
return {};
|
||||
}
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data[kPermissions] = permissions_;
|
||||
data[kGlobalPermission] = global_permission_.has_value() ? global_permission_.value() : -1;
|
||||
data["permissions"] = permissions_;
|
||||
data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -337,13 +333,13 @@ FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlo
|
||||
}
|
||||
std::optional<uint64_t> global_permission;
|
||||
|
||||
if (data[kGlobalPermission].empty() || data[kGlobalPermission] == -1) {
|
||||
if (data["global_permission"].empty() || data["global_permission"] == -1) {
|
||||
global_permission = std::nullopt;
|
||||
} else {
|
||||
global_permission = data[kGlobalPermission];
|
||||
global_permission = data["global_permission"];
|
||||
}
|
||||
|
||||
return FineGrainedAccessPermissions(data[kPermissions], global_permission);
|
||||
return FineGrainedAccessPermissions(data["permissions"], global_permission);
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
|
||||
@@ -449,13 +445,13 @@ const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermission
|
||||
|
||||
nlohmann::json Role::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data[kRoleName] = rolename_;
|
||||
data[kPermissions] = permissions_.Serialize();
|
||||
data["rolename"] = rolename_;
|
||||
data["permissions"] = permissions_.Serialize();
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize();
|
||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
||||
} else {
|
||||
data[kFineGrainedAccessHandler] = {};
|
||||
data["fine_grained_access_handler"] = {};
|
||||
}
|
||||
#endif
|
||||
return data;
|
||||
@@ -465,21 +461,21 @@ Role Role::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
if (!data[kRoleName].is_string() || !data[kPermissions].is_object()) {
|
||||
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
auto permissions = Permissions::Deserialize(data[kPermissions]);
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
FineGrainedAccessHandler fine_grained_access_handler;
|
||||
// We can have an empty fine_grained if the user was created without a valid license
|
||||
if (data[kFineGrainedAccessHandler].is_object()) {
|
||||
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]);
|
||||
if (data["fine_grained_access_handler"].is_object()) {
|
||||
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
||||
}
|
||||
return {data[kRoleName], permissions, std::move(fine_grained_access_handler)};
|
||||
return {data["rolename"], permissions, std::move(fine_grained_access_handler)};
|
||||
}
|
||||
#endif
|
||||
return {data[kRoleName], permissions};
|
||||
return {data["rolename"], permissions};
|
||||
}
|
||||
|
||||
bool operator==(const Role &first, const Role &second) {
|
||||
@@ -549,10 +545,10 @@ const std::string &Databases::GetDefault() const {
|
||||
|
||||
nlohmann::json Databases::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data[kGrants] = grants_dbs_;
|
||||
data[kDenies] = denies_dbs_;
|
||||
data[kAllowAll] = allow_all_;
|
||||
data[kDefault] = default_db_;
|
||||
data["grants"] = grants_dbs_;
|
||||
data["denies"] = denies_dbs_;
|
||||
data["allow_all"] = allow_all_;
|
||||
data["default"] = default_db_;
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -560,22 +556,22 @@ Databases Databases::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load database data!");
|
||||
}
|
||||
if (!data[kGrants].is_structured() || !data[kDenies].is_structured() || !data[kAllowAll].is_boolean() ||
|
||||
!data[kDefault].is_string()) {
|
||||
if (!data["grants"].is_structured() || !data["denies"].is_structured() || !data["allow_all"].is_boolean() ||
|
||||
!data["default"].is_string()) {
|
||||
throw AuthException("Couldn't load database data!");
|
||||
}
|
||||
return {data[kAllowAll], data[kGrants], data[kDenies], data[kDefault]};
|
||||
return {data["allow_all"], data["grants"], data["denies"], data["default"]};
|
||||
}
|
||||
#endif
|
||||
|
||||
User::User() = default;
|
||||
|
||||
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
|
||||
User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions)
|
||||
User::User(const std::string &username, std::string password_hash, const Permissions &permissions)
|
||||
: username_(utils::ToLowerCase(username)), password_hash_(std::move(password_hash)), permissions_(permissions) {}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions,
|
||||
User::User(const std::string &username, std::string password_hash, const Permissions &permissions,
|
||||
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access)
|
||||
: username_(utils::ToLowerCase(username)),
|
||||
password_hash_(std::move(password_hash)),
|
||||
@@ -585,16 +581,38 @@ User::User(const std::string &username, std::optional<HashedPassword> password_h
|
||||
#endif
|
||||
|
||||
bool User::CheckPassword(const std::string &password) {
|
||||
return password_hash_ ? password_hash_->VerifyPassword(password) : true;
|
||||
if (password_hash_.empty()) return true;
|
||||
return VerifyPassword(password, password_hash_);
|
||||
}
|
||||
|
||||
void User::UpdatePassword(const std::optional<std::string> &password,
|
||||
std::optional<PasswordHashAlgorithm> algo_override) {
|
||||
void User::UpdatePassword(const std::optional<std::string> &password) {
|
||||
if (!password) {
|
||||
password_hash_.reset();
|
||||
if (!FLAGS_auth_password_permit_null) {
|
||||
throw AuthException("Null passwords aren't permitted!");
|
||||
}
|
||||
password_hash_ = "";
|
||||
return;
|
||||
}
|
||||
password_hash_ = HashPassword(*password, algo_override);
|
||||
|
||||
if (FLAGS_auth_password_strength_regex != default_password_regex) {
|
||||
if (const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw AuthException(
|
||||
"Custom password regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
default_password_regex,
|
||||
license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
|
||||
}
|
||||
}
|
||||
std::regex re(FLAGS_auth_password_strength_regex);
|
||||
if (!std::regex_match(*password, re)) {
|
||||
throw AuthException(
|
||||
"The user password doesn't conform to the required strength! Regex: "
|
||||
"\"{}\"",
|
||||
FLAGS_auth_password_strength_regex);
|
||||
}
|
||||
|
||||
password_hash_ = EncryptPassword(*password);
|
||||
}
|
||||
|
||||
void User::SetRole(const Role &role) { role_.emplace(role); }
|
||||
@@ -611,49 +629,27 @@ Permissions User::GetPermissions() const {
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
|
||||
return Merge(GetUserFineGrainedAccessLabelPermissions(), GetRoleFineGrainedAccessLabelPermissions());
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
|
||||
return Merge(GetUserFineGrainedAccessEdgeTypePermissions(), GetRoleFineGrainedAccessEdgeTypePermissions());
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetUserFineGrainedAccessEdgeTypePermissions() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessPermissions{};
|
||||
}
|
||||
|
||||
return fine_grained_access_handler_.edge_type_permissions();
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetUserFineGrainedAccessLabelPermissions() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessPermissions{};
|
||||
if (role_) {
|
||||
return Merge(role()->fine_grained_access_handler().label_permissions(),
|
||||
fine_grained_access_handler_.label_permissions());
|
||||
}
|
||||
|
||||
return fine_grained_access_handler_.label_permissions();
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetRoleFineGrainedAccessEdgeTypePermissions() const {
|
||||
FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessPermissions{};
|
||||
}
|
||||
|
||||
if (role_) {
|
||||
return role()->fine_grained_access_handler().edge_type_permissions();
|
||||
return Merge(role()->fine_grained_access_handler().edge_type_permissions(),
|
||||
fine_grained_access_handler_.edge_type_permissions());
|
||||
}
|
||||
return FineGrainedAccessPermissions{};
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetRoleFineGrainedAccessLabelPermissions() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessPermissions{};
|
||||
}
|
||||
|
||||
if (role_) {
|
||||
return role()->fine_grained_access_handler().label_permissions();
|
||||
}
|
||||
return FineGrainedAccessPermissions{};
|
||||
return fine_grained_access_handler_.edge_type_permissions();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -675,20 +671,16 @@ const Role *User::role() const {
|
||||
|
||||
nlohmann::json User::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data[kUsername] = username_;
|
||||
if (password_hash_.has_value()) {
|
||||
data[kPasswordHash] = *password_hash_;
|
||||
} else {
|
||||
data[kPasswordHash] = nullptr;
|
||||
}
|
||||
data[kPermissions] = permissions_.Serialize();
|
||||
data["username"] = username_;
|
||||
data["password_hash"] = password_hash_;
|
||||
data["permissions"] = permissions_.Serialize();
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize();
|
||||
data[kDatabases] = database_access_.Serialize();
|
||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
||||
data["databases"] = database_access_.Serialize();
|
||||
} else {
|
||||
data[kFineGrainedAccessHandler] = {};
|
||||
data[kDatabases] = {};
|
||||
data["fine_grained_access_handler"] = {};
|
||||
data["databases"] = {};
|
||||
}
|
||||
#endif
|
||||
// The role shouldn't be serialized here, it is stored as a foreign key.
|
||||
@@ -699,23 +691,15 @@ User User::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
auto password_hash_json = data[kPasswordHash];
|
||||
if (!data[kUsername].is_string() || !(password_hash_json.is_object() || password_hash_json.is_null()) ||
|
||||
!data[kPermissions].is_object()) {
|
||||
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
|
||||
std::optional<HashedPassword> password_hash{};
|
||||
if (password_hash_json.is_object()) {
|
||||
password_hash = password_hash_json.get<HashedPassword>();
|
||||
}
|
||||
|
||||
auto permissions = Permissions::Deserialize(data[kPermissions]);
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
Databases db_access;
|
||||
if (data[kDatabases].is_structured()) {
|
||||
db_access = Databases::Deserialize(data[kDatabases]);
|
||||
if (data["databases"].is_structured()) {
|
||||
db_access = Databases::Deserialize(data["databases"]);
|
||||
} else {
|
||||
// Back-compatibility
|
||||
spdlog::warn("User without specified database access. Given access to the default database.");
|
||||
@@ -724,13 +708,13 @@ User User::Deserialize(const nlohmann::json &data) {
|
||||
}
|
||||
FineGrainedAccessHandler fine_grained_access_handler;
|
||||
// We can have an empty fine_grained if the user was created without a valid license
|
||||
if (data[kFineGrainedAccessHandler].is_object()) {
|
||||
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]);
|
||||
if (data["fine_grained_access_handler"].is_object()) {
|
||||
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
||||
}
|
||||
return {data[kUsername], std::move(password_hash), permissions, std::move(fine_grained_access_handler), db_access};
|
||||
return {data["username"], data["password_hash"], permissions, std::move(fine_grained_access_handler), db_access};
|
||||
}
|
||||
#endif
|
||||
return {data[kUsername], std::move(password_hash), permissions};
|
||||
return {data["username"], data["password_hash"], permissions};
|
||||
}
|
||||
|
||||
bool operator==(const User &first, const User &second) {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#include <json/json.hpp>
|
||||
#include <utility>
|
||||
#include "crypto.hpp"
|
||||
#include "dbms/constants.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
@@ -207,8 +206,6 @@ bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHa
|
||||
|
||||
class Role final {
|
||||
public:
|
||||
Role() = default;
|
||||
|
||||
explicit Role(const std::string &rolename);
|
||||
Role(const std::string &rolename, const Permissions &permissions);
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -335,9 +332,9 @@ class User final {
|
||||
User();
|
||||
|
||||
explicit User(const std::string &username);
|
||||
User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions);
|
||||
User(const std::string &username, std::string password_hash, const Permissions &permissions);
|
||||
#ifdef MG_ENTERPRISE
|
||||
User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions,
|
||||
User(const std::string &username, std::string password_hash, const Permissions &permissions,
|
||||
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access = {});
|
||||
#endif
|
||||
User(const User &) = default;
|
||||
@@ -349,18 +346,8 @@ class User final {
|
||||
/// @throw AuthException if unable to verify the password.
|
||||
bool CheckPassword(const std::string &password);
|
||||
|
||||
bool UpgradeHash(const std::string password) {
|
||||
if (!password_hash_) return false;
|
||||
if (password_hash_->IsSalted()) return false;
|
||||
|
||||
auto const algo = password_hash_->HashAlgo();
|
||||
UpdatePassword(password, algo);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @throw AuthException if unable to set the password.
|
||||
void UpdatePassword(const std::optional<std::string> &password = {},
|
||||
std::optional<PasswordHashAlgorithm> algo_override = std::nullopt);
|
||||
void UpdatePassword(const std::optional<std::string> &password = std::nullopt);
|
||||
|
||||
void SetRole(const Role &role);
|
||||
|
||||
@@ -371,10 +358,6 @@ class User final {
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessPermissions GetFineGrainedAccessLabelPermissions() const;
|
||||
FineGrainedAccessPermissions GetFineGrainedAccessEdgeTypePermissions() const;
|
||||
FineGrainedAccessPermissions GetUserFineGrainedAccessLabelPermissions() const;
|
||||
FineGrainedAccessPermissions GetUserFineGrainedAccessEdgeTypePermissions() const;
|
||||
FineGrainedAccessPermissions GetRoleFineGrainedAccessLabelPermissions() const;
|
||||
FineGrainedAccessPermissions GetRoleFineGrainedAccessEdgeTypePermissions() const;
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler() const;
|
||||
FineGrainedAccessHandler &fine_grained_access_handler();
|
||||
#endif
|
||||
@@ -399,7 +382,7 @@ class User final {
|
||||
|
||||
private:
|
||||
std::string username_;
|
||||
std::optional<HashedPassword> password_hash_;
|
||||
std::string password_hash_;
|
||||
Permissions permissions_;
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "auth/replication_handlers.hpp"
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/rpc.hpp"
|
||||
#include "license/license.hpp"
|
||||
|
||||
namespace memgraph::auth {
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
void UpdateAuthDataHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access, auth::SynchedAuth &auth,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::UpdateAuthDataReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::replication::UpdateAuthDataRes;
|
||||
UpdateAuthDataRes res(false);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != system_state_access.LastCommitedTS()) {
|
||||
spdlog::debug("UpdateAuthDataHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
system_state_access.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Update
|
||||
if (req.user) auth->SaveUser(*req.user);
|
||||
if (req.role) auth->SaveRole(*req.role);
|
||||
// Success
|
||||
system_state_access.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = UpdateAuthDataRes(true);
|
||||
spdlog::debug("UpdateAuthDataHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
} catch (const auth::AuthException & /* not used */) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void DropAuthDataHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access, auth::SynchedAuth &auth,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::DropAuthDataReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::replication::DropAuthDataRes;
|
||||
DropAuthDataRes res(false);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != system_state_access.LastCommitedTS()) {
|
||||
spdlog::debug("DropAuthDataHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
system_state_access.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Remove
|
||||
switch (req.type) {
|
||||
case replication::DropAuthDataReq::DataType::USER:
|
||||
auth->RemoveUser(req.name);
|
||||
break;
|
||||
case replication::DropAuthDataReq::DataType::ROLE:
|
||||
auth->RemoveRole(req.name);
|
||||
break;
|
||||
}
|
||||
// Success
|
||||
system_state_access.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = DropAuthDataRes(true);
|
||||
spdlog::debug("DropAuthDataHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
} catch (const auth::AuthException & /* not used */) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
bool SystemRecoveryHandler(auth::SynchedAuth &auth, auth::Auth::Config auth_config,
|
||||
const std::vector<auth::User> &users, const std::vector<auth::Role> &roles) {
|
||||
return auth.WithLock([&](auto &locked_auth) {
|
||||
// Update config
|
||||
locked_auth.SetConfig(std::move(auth_config));
|
||||
// Get all current users
|
||||
auto old_users = locked_auth.AllUsernames();
|
||||
// Save incoming users
|
||||
for (const auto &user : users) {
|
||||
// Missing users
|
||||
try {
|
||||
locked_auth.SaveUser(user);
|
||||
} catch (const auth::AuthException &) {
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to save user");
|
||||
return false;
|
||||
}
|
||||
const auto it = std::find(old_users.begin(), old_users.end(), user.username());
|
||||
if (it != old_users.end()) old_users.erase(it);
|
||||
}
|
||||
// Delete all the leftover users
|
||||
for (const auto &user : old_users) {
|
||||
if (!locked_auth.RemoveUser(user)) {
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to remove user \"{}\".", user);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Roles are only supported with a license
|
||||
if (license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
// Get all current roles
|
||||
auto old_roles = locked_auth.AllRolenames();
|
||||
// Save incoming users
|
||||
for (const auto &role : roles) {
|
||||
// Missing users
|
||||
try {
|
||||
locked_auth.SaveRole(role);
|
||||
} catch (const auth::AuthException &) {
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to save user");
|
||||
return false;
|
||||
}
|
||||
const auto it = std::find(old_roles.begin(), old_roles.end(), role.rolename());
|
||||
if (it != old_roles.end()) old_roles.erase(it);
|
||||
}
|
||||
// Delete all the leftover users
|
||||
for (const auto &role : old_roles) {
|
||||
if (!locked_auth.RemoveRole(role)) {
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to remove user \"{}\".", role);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Success
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void Register(replication::RoleReplicaData const &data, system::ReplicaHandlerAccessToState &system_state_access,
|
||||
auth::SynchedAuth &auth) {
|
||||
// NOTE: Register even without license as the user could add a license at run-time
|
||||
data.server->rpc_server_.Register<replication::UpdateAuthDataRpc>(
|
||||
[system_state_access, &auth](auto *req_reader, auto *res_builder) mutable {
|
||||
spdlog::debug("Received UpdateAuthDataRpc");
|
||||
UpdateAuthDataHandler(system_state_access, auth, req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<replication::DropAuthDataRpc>(
|
||||
[system_state_access, &auth](auto *req_reader, auto *res_builder) mutable {
|
||||
spdlog::debug("Received DropAuthDataRpc");
|
||||
DropAuthDataHandler(system_state_access, auth, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace memgraph::auth
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
#include "system/state.hpp"
|
||||
|
||||
namespace memgraph::auth {
|
||||
#ifdef MG_ENTERPRISE
|
||||
void UpdateAuthDataHandler(system::ReplicaHandlerAccessToState &system_state_access, auth::SynchedAuth &auth,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void DropAuthDataHandler(system::ReplicaHandlerAccessToState &system_state_access, auth::SynchedAuth &auth,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
|
||||
bool SystemRecoveryHandler(auth::SynchedAuth &auth, auth::Auth::Config auth_config,
|
||||
const std::vector<auth::User> &users, const std::vector<auth::Role> &roles);
|
||||
void Register(replication::RoleReplicaData const &data, system::ReplicaHandlerAccessToState &system_state_access,
|
||||
auth::SynchedAuth &auth);
|
||||
#endif
|
||||
} // namespace memgraph::auth
|
||||
178
src/auth/rpc.cpp
178
src/auth/rpc.cpp
@@ -1,178 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "auth/rpc.hpp"
|
||||
|
||||
#include <json/json.hpp>
|
||||
#include "auth/auth.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
#include "utils/enum.hpp"
|
||||
|
||||
namespace memgraph::slk {
|
||||
|
||||
// Serialize code for auth::Role
|
||||
void Save(const auth::Role &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.Serialize().dump(), builder);
|
||||
}
|
||||
namespace {
|
||||
auth::Role LoadAuthRole(memgraph::slk::Reader *reader) {
|
||||
std::string tmp;
|
||||
memgraph::slk::Load(&tmp, reader);
|
||||
const auto json = nlohmann::json::parse(tmp);
|
||||
return memgraph::auth::Role::Deserialize(json);
|
||||
}
|
||||
} // namespace
|
||||
// Deserialize code for auth::Role
|
||||
void Load(auth::Role *self, memgraph::slk::Reader *reader) { *self = LoadAuthRole(reader); }
|
||||
// Special case for optional<Role>
|
||||
template <>
|
||||
inline void Load<auth::Role>(std::optional<auth::Role> *obj, Reader *reader) {
|
||||
bool exists = false;
|
||||
Load(&exists, reader);
|
||||
if (exists) {
|
||||
obj->emplace(LoadAuthRole(reader));
|
||||
} else {
|
||||
*obj = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize code for auth::User
|
||||
void Save(const auth::User &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.Serialize().dump(), builder);
|
||||
std::optional<auth::Role> role{};
|
||||
if (const auto *role_ptr = self.role(); role_ptr) {
|
||||
role.emplace(*role_ptr);
|
||||
}
|
||||
memgraph::slk::Save(role, builder);
|
||||
}
|
||||
// Deserialize code for auth::User
|
||||
void Load(auth::User *self, memgraph::slk::Reader *reader) {
|
||||
std::string tmp;
|
||||
memgraph::slk::Load(&tmp, reader);
|
||||
const auto json = nlohmann::json::parse(tmp);
|
||||
*self = memgraph::auth::User::Deserialize(json);
|
||||
std::optional<auth::Role> role{};
|
||||
memgraph::slk::Load(&role, reader);
|
||||
if (role)
|
||||
self->SetRole(*role);
|
||||
else
|
||||
self->ClearRole();
|
||||
}
|
||||
|
||||
// Serialize code for auth::Auth::Config
|
||||
void Save(const auth::Auth::Config &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.name_regex_str, builder);
|
||||
memgraph::slk::Save(self.password_regex_str, builder);
|
||||
memgraph::slk::Save(self.password_permit_null, builder);
|
||||
}
|
||||
// Deserialize code for auth::Auth::Config
|
||||
void Load(auth::Auth::Config *self, memgraph::slk::Reader *reader) {
|
||||
std::string name_regex_str{};
|
||||
std::string password_regex_str{};
|
||||
bool password_permit_null{};
|
||||
|
||||
memgraph::slk::Load(&name_regex_str, reader);
|
||||
memgraph::slk::Load(&password_regex_str, reader);
|
||||
memgraph::slk::Load(&password_permit_null, reader);
|
||||
|
||||
*self = auth::Auth::Config{std::move(name_regex_str), std::move(password_regex_str), password_permit_null};
|
||||
}
|
||||
|
||||
// Serialize code for UpdateAuthDataReq
|
||||
void Save(const memgraph::replication::UpdateAuthDataReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.user, builder);
|
||||
memgraph::slk::Save(self.role, builder);
|
||||
}
|
||||
void Load(memgraph::replication::UpdateAuthDataReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->user, reader);
|
||||
memgraph::slk::Load(&self->role, reader);
|
||||
}
|
||||
|
||||
// Serialize code for UpdateAuthDataRes
|
||||
void Save(const memgraph::replication::UpdateAuthDataRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
}
|
||||
void Load(memgraph::replication::UpdateAuthDataRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
// Serialize code for DropAuthDataReq
|
||||
void Save(const memgraph::replication::DropAuthDataReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
memgraph::slk::Save(utils::EnumToNum<2, uint8_t>(self.type), builder);
|
||||
memgraph::slk::Save(self.name, builder);
|
||||
}
|
||||
void Load(memgraph::replication::DropAuthDataReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
uint8_t type_tmp = 0;
|
||||
memgraph::slk::Load(&type_tmp, reader);
|
||||
if (!utils::NumToEnum<2>(type_tmp, self->type)) {
|
||||
throw SlkReaderException("Unexpected result line:{}!", __LINE__);
|
||||
}
|
||||
memgraph::slk::Load(&self->name, reader);
|
||||
}
|
||||
|
||||
// Serialize code for DropAuthDataRes
|
||||
void Save(const memgraph::replication::DropAuthDataRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
}
|
||||
void Load(memgraph::replication::DropAuthDataRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
constexpr utils::TypeInfo UpdateAuthDataReq::kType{utils::TypeId::REP_UPDATE_AUTH_DATA_REQ, "UpdateAuthDataReq",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo UpdateAuthDataRes::kType{utils::TypeId::REP_UPDATE_AUTH_DATA_RES, "UpdateAuthDataRes",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo DropAuthDataReq::kType{utils::TypeId::REP_DROP_AUTH_DATA_REQ, "DropAuthDataReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo DropAuthDataRes::kType{utils::TypeId::REP_DROP_AUTH_DATA_RES, "DropAuthDataRes", nullptr};
|
||||
|
||||
void UpdateAuthDataReq::Save(const UpdateAuthDataReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void UpdateAuthDataReq::Load(UpdateAuthDataReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void UpdateAuthDataRes::Save(const UpdateAuthDataRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void UpdateAuthDataRes::Load(UpdateAuthDataRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void DropAuthDataReq::Save(const DropAuthDataReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void DropAuthDataReq::Load(DropAuthDataReq *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
void DropAuthDataRes::Save(const DropAuthDataRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void DropAuthDataRes::Load(DropAuthDataRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
|
||||
} // namespace memgraph::replication
|
||||
119
src/auth/rpc.hpp
119
src/auth/rpc.hpp
@@ -1,119 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "rpc/messages.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
struct UpdateAuthDataReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(UpdateAuthDataReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const UpdateAuthDataReq &self, memgraph::slk::Builder *builder);
|
||||
UpdateAuthDataReq() = default;
|
||||
UpdateAuthDataReq(std::string epoch_id, uint64_t expected_ts, uint64_t new_ts, auth::User user)
|
||||
: epoch_id{std::move(epoch_id)},
|
||||
expected_group_timestamp{expected_ts},
|
||||
new_group_timestamp{new_ts},
|
||||
user{std::move(user)} {}
|
||||
UpdateAuthDataReq(std::string epoch_id, uint64_t expected_ts, uint64_t new_ts, auth::Role role)
|
||||
: epoch_id{std::move(epoch_id)},
|
||||
expected_group_timestamp{expected_ts},
|
||||
new_group_timestamp{new_ts},
|
||||
role{std::move(role)} {}
|
||||
|
||||
std::string epoch_id;
|
||||
uint64_t expected_group_timestamp;
|
||||
uint64_t new_group_timestamp;
|
||||
std::optional<auth::User> user;
|
||||
std::optional<auth::Role> role;
|
||||
};
|
||||
|
||||
struct UpdateAuthDataRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(UpdateAuthDataRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const UpdateAuthDataRes &self, memgraph::slk::Builder *builder);
|
||||
UpdateAuthDataRes() = default;
|
||||
explicit UpdateAuthDataRes(bool success) : success{success} {}
|
||||
|
||||
bool success;
|
||||
};
|
||||
|
||||
using UpdateAuthDataRpc = rpc::RequestResponse<UpdateAuthDataReq, UpdateAuthDataRes>;
|
||||
|
||||
struct DropAuthDataReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(DropAuthDataReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DropAuthDataReq &self, memgraph::slk::Builder *builder);
|
||||
DropAuthDataReq() = default;
|
||||
|
||||
enum class DataType { USER, ROLE };
|
||||
|
||||
DropAuthDataReq(std::string epoch_id, uint64_t expected_ts, uint64_t new_ts, DataType type, std::string_view name)
|
||||
: epoch_id{std::move(epoch_id)},
|
||||
expected_group_timestamp{expected_ts},
|
||||
new_group_timestamp{new_ts},
|
||||
type{type},
|
||||
name{name} {}
|
||||
|
||||
std::string epoch_id;
|
||||
uint64_t expected_group_timestamp;
|
||||
uint64_t new_group_timestamp;
|
||||
DataType type;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct DropAuthDataRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(DropAuthDataRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DropAuthDataRes &self, memgraph::slk::Builder *builder);
|
||||
DropAuthDataRes() = default;
|
||||
explicit DropAuthDataRes(bool success) : success{success} {}
|
||||
|
||||
bool success;
|
||||
};
|
||||
|
||||
using DropAuthDataRpc = rpc::RequestResponse<DropAuthDataReq, DropAuthDataRes>;
|
||||
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::slk {
|
||||
|
||||
void Save(const auth::Role &self, memgraph::slk::Builder *builder);
|
||||
void Load(auth::Role *self, memgraph::slk::Reader *reader);
|
||||
void Save(const auth::User &self, memgraph::slk::Builder *builder);
|
||||
void Load(auth::User *self, memgraph::slk::Reader *reader);
|
||||
void Save(const auth::Auth::Config &self, memgraph::slk::Builder *builder);
|
||||
void Load(auth::Auth::Config *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::replication::UpdateAuthDataRes &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::UpdateAuthDataRes *self, memgraph::slk::Reader *reader);
|
||||
void Save(const memgraph::replication::UpdateAuthDataReq & /*self*/, memgraph::slk::Builder * /*builder*/);
|
||||
void Load(memgraph::replication::UpdateAuthDataReq * /*self*/, memgraph::slk::Reader * /*reader*/);
|
||||
void Save(const memgraph::replication::DropAuthDataRes &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::DropAuthDataRes *self, memgraph::slk::Reader *reader);
|
||||
void Save(const memgraph::replication::DropAuthDataReq & /*self*/, memgraph::slk::Builder * /*builder*/);
|
||||
void Load(memgraph::replication::DropAuthDataReq * /*self*/, memgraph::slk::Reader * /*reader*/);
|
||||
} // namespace memgraph::slk
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <string>
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace memgraph::communication::websocket {
|
||||
|
||||
@@ -28,7 +30,7 @@ class AuthenticationInterface {
|
||||
|
||||
class SafeAuth : public AuthenticationInterface {
|
||||
public:
|
||||
explicit SafeAuth(auth::SynchedAuth *auth) : auth_{auth} {}
|
||||
explicit SafeAuth(utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth) : auth_{auth} {}
|
||||
|
||||
bool Authenticate(const std::string &username, const std::string &password) const override;
|
||||
|
||||
@@ -37,6 +39,6 @@ class SafeAuth : public AuthenticationInterface {
|
||||
bool HasAnyUsers() const override;
|
||||
|
||||
private:
|
||||
auth::SynchedAuth *auth_;
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth_;
|
||||
};
|
||||
} // namespace memgraph::communication::websocket
|
||||
|
||||
@@ -12,8 +12,8 @@ target_sources(mg-coordination
|
||||
include/coordination/coordinator_slk.hpp
|
||||
include/coordination/coordinator_data.hpp
|
||||
include/coordination/constants.hpp
|
||||
include/coordination/failover_status.hpp
|
||||
include/coordination/coordinator_cluster_config.hpp
|
||||
include/coordination/coordinator_handlers.hpp
|
||||
|
||||
PRIVATE
|
||||
coordinator_client.cpp
|
||||
@@ -22,10 +22,9 @@ target_sources(mg-coordination
|
||||
coordinator_server.cpp
|
||||
coordinator_data.cpp
|
||||
coordinator_instance.cpp
|
||||
coordinator_handlers.cpp
|
||||
)
|
||||
target_include_directories(mg-coordination PUBLIC include)
|
||||
|
||||
target_link_libraries(mg-coordination
|
||||
PUBLIC mg::utils mg::rpc mg::slk mg::io mg::repl_coord_glue lib::rangev3 nuraft mg-replication_handler
|
||||
PUBLIC mg::utils mg::rpc mg::slk mg::io mg::repl_coord_glue lib::rangev3
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
namespace memgraph::coordination {
|
||||
|
||||
namespace {
|
||||
auto CreateClientContext(memgraph::coordination::CoordinatorClientConfig const &config)
|
||||
auto CreateClientContext(const memgraph::coordination::CoordinatorClientConfig &config)
|
||||
-> communication::ClientContext {
|
||||
return (config.ssl) ? communication::ClientContext{config.ssl->key_file, config.ssl->cert_file}
|
||||
: communication::ClientContext{};
|
||||
@@ -49,12 +49,10 @@ void CoordinatorClient::StartFrequentCheck() {
|
||||
try {
|
||||
spdlog::trace("Sending frequent heartbeat to machine {} on {}", instance_name,
|
||||
rpc_client_.Endpoint().SocketAddress());
|
||||
{ // NOTE: This is intentionally scoped so that stream lock could get released.
|
||||
auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||
stream.AwaitResponse();
|
||||
}
|
||||
auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||
stream.AwaitResponse();
|
||||
succ_cb_(coord_data_, instance_name);
|
||||
} catch (rpc::RpcFailedException const &) {
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
fail_cb_(coord_data_, instance_name);
|
||||
}
|
||||
});
|
||||
@@ -69,9 +67,12 @@ auto CoordinatorClient::SetCallbacks(HealthCheckCallback succ_cb, HealthCheckCal
|
||||
fail_cb_ = std::move(fail_cb);
|
||||
}
|
||||
|
||||
auto CoordinatorClient::ReplicationClientInfo() const -> ReplClientInfo { return config_.replication_client_info; }
|
||||
auto CoordinatorClient::ReplicationClientInfo() const -> CoordinatorClientConfig::ReplicationClientInfo {
|
||||
return config_.replication_client_info;
|
||||
}
|
||||
|
||||
auto CoordinatorClient::SendPromoteReplicaToMainRpc(ReplicationClientsInfo replication_clients_info) const -> bool {
|
||||
auto CoordinatorClient::SendPromoteReplicaToMainRpc(
|
||||
std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info) const -> bool {
|
||||
try {
|
||||
auto stream{rpc_client_.Stream<PromoteReplicaToMainRpc>(std::move(replication_clients_info))};
|
||||
if (!stream.AwaitResponse().success) {
|
||||
@@ -79,23 +80,23 @@ auto CoordinatorClient::SendPromoteReplicaToMainRpc(ReplicationClientsInfo repli
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (rpc::RpcFailedException const &) {
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
spdlog::error("RPC error occurred while sending failover RPC!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CoordinatorClient::DemoteToReplica() const -> bool {
|
||||
auto const &instance_name = config_.instance_name;
|
||||
const auto instance_name = config_.instance_name;
|
||||
try {
|
||||
auto stream{rpc_client_.Stream<DemoteMainToReplicaRpc>(config_.replication_client_info)};
|
||||
auto stream{rpc_client_.Stream<SetMainToReplicaRpc>(config_.replication_client_info)};
|
||||
if (!stream.AwaitResponse().success) {
|
||||
spdlog::error("Failed to receive successful RPC response for setting instance {} to replica!", instance_name);
|
||||
return false;
|
||||
}
|
||||
spdlog::info("Sent request RPC from coordinator to instance to set it as replica!");
|
||||
return true;
|
||||
} catch (rpc::RpcFailedException const &) {
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
spdlog::error("Failed to set instance {} to replica!", instance_name);
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include <range/v3/view.hpp>
|
||||
#include <shared_mutex>
|
||||
#include "libnuraft/nuraft.hxx"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
@@ -25,7 +24,7 @@ CoordinatorData::CoordinatorData() {
|
||||
auto find_instance = [](CoordinatorData *coord_data, std::string_view instance_name) -> CoordinatorInstance & {
|
||||
auto instance = std::ranges::find_if(
|
||||
coord_data->registered_instances_,
|
||||
[instance_name](CoordinatorInstance const &instance) { return instance.InstanceName() == instance_name; });
|
||||
[instance_name](const CoordinatorInstance &instance) { return instance.InstanceName() == instance_name; });
|
||||
|
||||
MG_ASSERT(instance != coord_data->registered_instances_.end(), "Instance {} not found during callback!",
|
||||
instance_name);
|
||||
@@ -35,59 +34,85 @@ CoordinatorData::CoordinatorData() {
|
||||
replica_succ_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_data->coord_data_lock_};
|
||||
spdlog::trace("Instance {} performing replica successful callback", instance_name);
|
||||
find_instance(coord_data, instance_name).OnSuccessPing();
|
||||
auto &instance = find_instance(coord_data, instance_name);
|
||||
instance.OnSuccessPing();
|
||||
};
|
||||
|
||||
replica_fail_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_data->coord_data_lock_};
|
||||
spdlog::trace("Instance {} performing replica failure callback", instance_name);
|
||||
find_instance(coord_data, instance_name).OnFailPing();
|
||||
auto &instance = find_instance(coord_data, instance_name);
|
||||
instance.OnFailPing();
|
||||
};
|
||||
|
||||
main_succ_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
|
||||
main_succ_cb_ = [this, find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_data->coord_data_lock_};
|
||||
spdlog::trace("Instance {} performing main successful callback", instance_name);
|
||||
|
||||
auto &instance = find_instance(coord_data, instance_name);
|
||||
|
||||
if (instance.IsAlive() || !coord_data->ClusterHasAliveMain_()) {
|
||||
if (instance.IsAlive()) {
|
||||
instance.OnSuccessPing();
|
||||
return;
|
||||
}
|
||||
|
||||
bool const demoted = instance.DemoteToReplica(coord_data->replica_succ_cb_, coord_data->replica_fail_cb_);
|
||||
if (demoted) {
|
||||
instance.OnSuccessPing();
|
||||
spdlog::info("Instance {} demoted to replica", instance_name);
|
||||
} else {
|
||||
spdlog::error("Instance {} failed to become replica", instance_name);
|
||||
auto const new_role = coord_data->ClusterHasAliveMain() ? replication_coordination_glue::ReplicationRole::REPLICA
|
||||
: replication_coordination_glue::ReplicationRole::MAIN;
|
||||
if (new_role == replication_coordination_glue::ReplicationRole::REPLICA) {
|
||||
thread_pool_.AddTask([&instance, coord_data, instance_name]() {
|
||||
auto lock = std::lock_guard{coord_data->coord_data_lock_};
|
||||
spdlog::info("Demoting instance {} to replica", instance_name);
|
||||
instance.PauseFrequentCheck();
|
||||
utils::OnScopeExit scope_exit{[&instance] { instance.ResumeFrequentCheck(); }};
|
||||
auto const status = instance.DemoteToReplica(coord_data->replica_succ_cb_, coord_data->replica_fail_cb_);
|
||||
if (!status) {
|
||||
spdlog::error("Instance {} failed to demote to replica", instance_name);
|
||||
} else {
|
||||
spdlog::info("Instance {} demoted to replica", instance_name);
|
||||
instance.OnSuccessPing();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
instance.OnSuccessPing();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
main_fail_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
|
||||
main_fail_cb_ = [this, find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void {
|
||||
auto lock = std::lock_guard{coord_data->coord_data_lock_};
|
||||
spdlog::trace("Instance {} performing main failure callback", instance_name);
|
||||
find_instance(coord_data, instance_name).OnFailPing();
|
||||
auto &instance = find_instance(coord_data, instance_name);
|
||||
instance.OnFailPing();
|
||||
|
||||
if (!coord_data->ClusterHasAliveMain_()) {
|
||||
spdlog::info("Cluster without main instance, trying automatic failover");
|
||||
coord_data->TryFailover();
|
||||
if (!ClusterHasAliveMain()) {
|
||||
spdlog::info("Cluster without main instance, starting automatic failover");
|
||||
switch (auto failover_status = DoFailover(); failover_status) {
|
||||
using enum DoFailoverStatus;
|
||||
case ALL_REPLICAS_DOWN:
|
||||
spdlog::warn("Failover aborted since all replicas are down!");
|
||||
break;
|
||||
case MAIN_ALIVE:
|
||||
spdlog::warn("Failover aborted since main is alive!");
|
||||
break;
|
||||
case RPC_FAILED:
|
||||
spdlog::warn("Failover aborted since promoting replica to main failed!");
|
||||
break;
|
||||
case SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
auto CoordinatorData::ClusterHasAliveMain_() const -> bool {
|
||||
auto const alive_main = [](CoordinatorInstance const &instance) { return instance.IsMain() && instance.IsAlive(); };
|
||||
auto CoordinatorData::ClusterHasAliveMain() const -> bool {
|
||||
auto const alive_main = [](const CoordinatorInstance &instance) { return instance.IsMain() && instance.IsAlive(); };
|
||||
return std::ranges::any_of(registered_instances_, alive_main);
|
||||
}
|
||||
|
||||
auto CoordinatorData::TryFailover() -> void {
|
||||
auto CoordinatorData::DoFailover() -> DoFailoverStatus {
|
||||
auto replica_instances = registered_instances_ | ranges::views::filter(&CoordinatorInstance::IsReplica);
|
||||
|
||||
auto chosen_replica_instance = std::ranges::find_if(replica_instances, &CoordinatorInstance::IsAlive);
|
||||
if (chosen_replica_instance == replica_instances.end()) {
|
||||
spdlog::warn("Failover failed since all replicas are down!");
|
||||
return;
|
||||
return DoFailoverStatus::ALL_REPLICAS_DOWN;
|
||||
}
|
||||
|
||||
chosen_replica_instance->PauseFrequentCheck();
|
||||
@@ -96,33 +121,32 @@ auto CoordinatorData::TryFailover() -> void {
|
||||
std::vector<ReplClientInfo> repl_clients_info;
|
||||
repl_clients_info.reserve(std::ranges::distance(replica_instances));
|
||||
|
||||
auto const not_chosen_replica_instance = [&chosen_replica_instance](CoordinatorInstance const &instance) {
|
||||
auto const not_chosen_replica_instance = [&chosen_replica_instance](const CoordinatorInstance &instance) {
|
||||
return instance != *chosen_replica_instance;
|
||||
};
|
||||
|
||||
std::ranges::transform(registered_instances_ | ranges::views::filter(not_chosen_replica_instance),
|
||||
std::ranges::transform(replica_instances | ranges::views::filter(not_chosen_replica_instance),
|
||||
std::back_inserter(repl_clients_info),
|
||||
[](const CoordinatorInstance &instance) { return instance.ReplicationClientInfo(); });
|
||||
|
||||
if (!chosen_replica_instance->PromoteToMain(std::move(repl_clients_info), main_succ_cb_, main_fail_cb_)) {
|
||||
spdlog::warn("Failover failed since promoting replica to main failed!");
|
||||
return;
|
||||
return DoFailoverStatus::RPC_FAILED;
|
||||
}
|
||||
spdlog::info("Failover successful! Instance {} promoted to main.", chosen_replica_instance->InstanceName());
|
||||
return DoFailoverStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorData::ShowInstances() const -> std::vector<CoordinatorInstanceStatus> {
|
||||
std::vector<CoordinatorInstanceStatus> instances_status;
|
||||
instances_status.reserve(registered_instances_.size());
|
||||
|
||||
auto const stringify_repl_role = [](CoordinatorInstance const &instance) -> std::string {
|
||||
auto const stringify_repl_role = [](const CoordinatorInstance &instance) -> std::string {
|
||||
if (!instance.IsAlive()) return "unknown";
|
||||
if (instance.IsMain()) return "main";
|
||||
return "replica";
|
||||
};
|
||||
|
||||
auto const instance_to_status =
|
||||
[&stringify_repl_role](CoordinatorInstance const &instance) -> CoordinatorInstanceStatus {
|
||||
[&stringify_repl_role](const CoordinatorInstance &instance) -> CoordinatorInstanceStatus {
|
||||
return {.instance_name = instance.InstanceName(),
|
||||
.socket_address = instance.SocketAddress(),
|
||||
.replication_role = stringify_repl_role(instance),
|
||||
@@ -140,7 +164,7 @@ auto CoordinatorData::ShowInstances() const -> std::vector<CoordinatorInstanceSt
|
||||
auto CoordinatorData::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus {
|
||||
auto lock = std::lock_guard{coord_data_lock_};
|
||||
|
||||
auto const is_new_main = [&instance_name](CoordinatorInstance const &instance) {
|
||||
auto const is_new_main = [&instance_name](const CoordinatorInstance &instance) {
|
||||
return instance.InstanceName() == instance_name;
|
||||
};
|
||||
auto new_main = std::ranges::find_if(registered_instances_, is_new_main);
|
||||
@@ -154,10 +178,10 @@ auto CoordinatorData::SetInstanceToMain(std::string instance_name) -> SetInstanc
|
||||
new_main->PauseFrequentCheck();
|
||||
utils::OnScopeExit scope_exit{[&new_main] { new_main->ResumeFrequentCheck(); }};
|
||||
|
||||
ReplicationClientsInfo repl_clients_info;
|
||||
std::vector<CoordinatorClientConfig::ReplicationClientInfo> repl_clients_info;
|
||||
repl_clients_info.reserve(registered_instances_.size() - 1);
|
||||
|
||||
auto const is_not_new_main = [&instance_name](CoordinatorInstance const &instance) {
|
||||
auto const is_not_new_main = [&instance_name](const CoordinatorInstance &instance) {
|
||||
return instance.InstanceName() != instance_name;
|
||||
};
|
||||
std::ranges::transform(registered_instances_ | ranges::views::filter(is_not_new_main),
|
||||
@@ -174,16 +198,16 @@ auto CoordinatorData::SetInstanceToMain(std::string instance_name) -> SetInstanc
|
||||
|
||||
auto CoordinatorData::RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus {
|
||||
auto lock = std::lock_guard{coord_data_lock_};
|
||||
if (std::ranges::any_of(registered_instances_, [&config](CoordinatorInstance const &instance) {
|
||||
if (std::ranges::any_of(registered_instances_, [&config](const CoordinatorInstance &instance) {
|
||||
return instance.InstanceName() == config.instance_name;
|
||||
})) {
|
||||
return RegisterInstanceCoordinatorStatus::NAME_EXISTS;
|
||||
}
|
||||
|
||||
if (std::ranges::any_of(registered_instances_, [&config](CoordinatorInstance const &instance) {
|
||||
if (std::ranges::any_of(registered_instances_, [&config](const CoordinatorInstance &instance) {
|
||||
return instance.SocketAddress() == config.SocketAddress();
|
||||
})) {
|
||||
return RegisterInstanceCoordinatorStatus::ENDPOINT_EXISTS;
|
||||
return RegisterInstanceCoordinatorStatus::END_POINT_EXISTS;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -196,4 +220,5 @@ auto CoordinatorData::RegisterInstance(CoordinatorClientConfig config) -> Regist
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "coordination/coordinator_handlers.hpp"
|
||||
|
||||
#include <range/v3/view.hpp>
|
||||
|
||||
#include "coordination/coordinator_rpc.hpp"
|
||||
#include "coordination/include/coordination/coordinator_server.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
void CoordinatorHandlers::Register(memgraph::coordination::CoordinatorServer &server,
|
||||
replication::ReplicationHandler &replication_handler) {
|
||||
server.Register<coordination::PromoteReplicaToMainRpc>(
|
||||
[&](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received PromoteReplicaToMainRpc");
|
||||
CoordinatorHandlers::PromoteReplicaToMainHandler(replication_handler, req_reader, res_builder);
|
||||
});
|
||||
|
||||
server.Register<coordination::DemoteMainToReplicaRpc>(
|
||||
[&replication_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received DemoteMainToReplicaRpc from coordinator server");
|
||||
CoordinatorHandlers::DemoteMainToReplicaHandler(replication_handler, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::DemoteMainToReplicaHandler(replication::ReplicationHandler &replication_handler,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
spdlog::info("Executing DemoteMainToReplicaHandler");
|
||||
|
||||
if (!replication_handler.IsMain()) {
|
||||
spdlog::error("Setting to replica must be performed on main.");
|
||||
slk::Save(coordination::DemoteMainToReplicaRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
coordination::DemoteMainToReplicaReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
const replication::ReplicationServerConfig clients_config{
|
||||
.ip_address = req.replication_client_info.replication_ip_address,
|
||||
.port = req.replication_client_info.replication_port};
|
||||
|
||||
if (!replication_handler.SetReplicationRoleReplica(clients_config)) {
|
||||
spdlog::error("Demoting main to replica failed!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::PromoteReplicaToMainHandler(replication::ReplicationHandler &replication_handler,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
if (!replication_handler.IsReplica()) {
|
||||
spdlog::error("Failover must be performed on replica!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
// This can fail because of disk. If it does, the cluster state could get inconsistent.
|
||||
// We don't handle disk issues.
|
||||
if (!replication_handler.DoReplicaToMainPromotion()) {
|
||||
spdlog::error("Promoting replica to main failed!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
coordination::PromoteReplicaToMainReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
auto const converter = [](const auto &repl_info_config) {
|
||||
return replication::ReplicationClientConfig{
|
||||
.name = repl_info_config.instance_name,
|
||||
.mode = repl_info_config.replication_mode,
|
||||
.ip_address = repl_info_config.replication_ip_address,
|
||||
.port = repl_info_config.replication_port,
|
||||
};
|
||||
};
|
||||
|
||||
// registering replicas
|
||||
for (auto const &config : req.replication_clients_info | ranges::views::transform(converter)) {
|
||||
auto instance_client = replication_handler.RegisterReplica(config);
|
||||
if (instance_client.HasError()) {
|
||||
using enum memgraph::replication::RegisterReplicaError;
|
||||
switch (instance_client.GetError()) {
|
||||
// Can't happen, checked on the coordinator side
|
||||
case memgraph::query::RegisterReplicaError::NAME_EXISTS:
|
||||
spdlog::error("Replica with the same name already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
// Can't happen, checked on the coordinator side
|
||||
case memgraph::query::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
spdlog::error("Replica with the same endpoint already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
// We don't handle disk issues
|
||||
case memgraph::query::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
spdlog::error("Registered replica could not be persisted!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::query::RegisterReplicaError::CONNECTION_FAILED:
|
||||
// Connection failure is not a fatal error
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
@@ -36,19 +36,19 @@ void PromoteReplicaToMainRes::Load(PromoteReplicaToMainRes *self, memgraph::slk:
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void DemoteMainToReplicaReq::Save(const DemoteMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||
void SetMainToReplicaReq::Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void DemoteMainToReplicaReq::Load(DemoteMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
void SetMainToReplicaReq::Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void DemoteMainToReplicaRes::Save(const DemoteMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||
void SetMainToReplicaRes::Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void DemoteMainToReplicaRes::Load(DemoteMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
void SetMainToReplicaRes::Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
@@ -60,11 +60,11 @@ constexpr utils::TypeInfo coordination::PromoteReplicaToMainReq::kType{utils::Ty
|
||||
constexpr utils::TypeInfo coordination::PromoteReplicaToMainRes::kType{utils::TypeId::COORD_FAILOVER_RES,
|
||||
"CoordPromoteReplicaToMainRes", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::DemoteMainToReplicaReq::kType{utils::TypeId::COORD_SET_REPL_MAIN_REQ,
|
||||
"CoordDemoteToReplicaReq", nullptr};
|
||||
constexpr utils::TypeInfo coordination::SetMainToReplicaReq::kType{utils::TypeId::COORD_SET_REPL_MAIN_REQ,
|
||||
"CoordDemoteToReplicaReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::DemoteMainToReplicaRes::kType{utils::TypeId::COORD_SET_REPL_MAIN_RES,
|
||||
"CoordDemoteToReplicaRes", nullptr};
|
||||
constexpr utils::TypeInfo coordination::SetMainToReplicaRes::kType{utils::TypeId::COORD_SET_REPL_MAIN_RES,
|
||||
"CoordDemoteToReplicaRes", nullptr};
|
||||
|
||||
namespace slk {
|
||||
|
||||
@@ -84,19 +84,19 @@ void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::
|
||||
memgraph::slk::Load(&self->replication_clients_info, reader);
|
||||
}
|
||||
|
||||
void Save(const memgraph::coordination::DemoteMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||
void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.replication_client_info, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::DemoteMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->replication_client_info, reader);
|
||||
}
|
||||
|
||||
void Save(const memgraph::coordination::DemoteMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||
void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::DemoteMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,12 @@ auto CoordinatorState::ShowInstances() const -> std::vector<CoordinatorInstanceS
|
||||
return std::get<CoordinatorData>(data_).ShowInstances();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto CoordinatorState::DoFailover() -> DoFailoverStatus {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), "Cannot do failover since variant holds wrong alternative");
|
||||
auto &coord_state = std::get<CoordinatorData>(data_);
|
||||
return coord_state.DoFailover();
|
||||
}
|
||||
|
||||
auto CoordinatorState::GetCoordinatorServer() const -> CoordinatorServer & {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorMainReplicaData>(data_),
|
||||
"Cannot get coordinator server since variant holds wrong alternative");
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "coordination/coordinator_instance.hpp"
|
||||
#include "coordination/coordinator_instance_status.hpp"
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "coordination/failover_status.hpp"
|
||||
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
@@ -27,21 +28,21 @@ class CoordinatorData {
|
||||
public:
|
||||
CoordinatorData();
|
||||
|
||||
[[nodiscard]] auto DoFailover() -> DoFailoverStatus;
|
||||
|
||||
[[nodiscard]] auto RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
|
||||
|
||||
[[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
|
||||
|
||||
auto TryFailover() -> void;
|
||||
|
||||
auto ShowInstances() const -> std::vector<CoordinatorInstanceStatus>;
|
||||
|
||||
private:
|
||||
auto ClusterHasAliveMain_() const -> bool;
|
||||
auto ClusterHasAliveMain() const -> bool;
|
||||
|
||||
mutable utils::RWLock coord_data_lock_{utils::RWLock::Priority::READ};
|
||||
HealthCheckCallback main_succ_cb_, main_fail_cb_, replica_succ_cb_, replica_fail_cb_;
|
||||
// NOTE: Must be std::list because we rely on pointer stability
|
||||
// Must be std::list because we rely on pointer stability
|
||||
std::list<CoordinatorInstance> registered_instances_;
|
||||
utils::ThreadPool thread_pool_{1};
|
||||
};
|
||||
|
||||
struct CoordinatorMainReplicaData {
|
||||
|
||||
@@ -48,35 +48,35 @@ struct PromoteReplicaToMainRes {
|
||||
|
||||
using PromoteReplicaToMainRpc = rpc::RequestResponse<PromoteReplicaToMainReq, PromoteReplicaToMainRes>;
|
||||
|
||||
struct DemoteMainToReplicaReq {
|
||||
struct SetMainToReplicaReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(DemoteMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DemoteMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||
static void Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit DemoteMainToReplicaReq(CoordinatorClientConfig::ReplicationClientInfo replication_client_info)
|
||||
explicit SetMainToReplicaReq(CoordinatorClientConfig::ReplicationClientInfo replication_client_info)
|
||||
: replication_client_info(std::move(replication_client_info)) {}
|
||||
|
||||
DemoteMainToReplicaReq() = default;
|
||||
SetMainToReplicaReq() = default;
|
||||
|
||||
CoordinatorClientConfig::ReplicationClientInfo replication_client_info;
|
||||
};
|
||||
|
||||
struct DemoteMainToReplicaRes {
|
||||
struct SetMainToReplicaRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(DemoteMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DemoteMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||
static void Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit DemoteMainToReplicaRes(bool success) : success(success) {}
|
||||
DemoteMainToReplicaRes() = default;
|
||||
explicit SetMainToReplicaRes(bool success) : success(success) {}
|
||||
SetMainToReplicaRes() = default;
|
||||
|
||||
bool success;
|
||||
};
|
||||
|
||||
using DemoteMainToReplicaRpc = rpc::RequestResponse<DemoteMainToReplicaReq, DemoteMainToReplicaRes>;
|
||||
using SetMainToReplicaRpc = rpc::RequestResponse<SetMainToReplicaReq, SetMainToReplicaRes>;
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
@@ -91,13 +91,13 @@ void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph:
|
||||
|
||||
void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::coordination::DemoteMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||
void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::coordination::DemoteMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::coordination::DemoteMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||
void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::coordination::DemoteMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "coordination/coordinator_data.hpp"
|
||||
#include "coordination/coordinator_instance_status.hpp"
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "coordination/failover_status.hpp"
|
||||
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||
|
||||
#include <variant>
|
||||
@@ -27,8 +28,8 @@ class CoordinatorState {
|
||||
CoordinatorState();
|
||||
~CoordinatorState() = default;
|
||||
|
||||
CoordinatorState(CoordinatorState const &) = delete;
|
||||
CoordinatorState &operator=(CoordinatorState const &) = delete;
|
||||
CoordinatorState(const CoordinatorState &) = delete;
|
||||
CoordinatorState &operator=(const CoordinatorState &) = delete;
|
||||
|
||||
CoordinatorState(CoordinatorState &&) noexcept = delete;
|
||||
CoordinatorState &operator=(CoordinatorState &&) noexcept = delete;
|
||||
@@ -42,6 +43,8 @@ class CoordinatorState {
|
||||
// The client code must check that the server exists before calling this method.
|
||||
auto GetCoordinatorServer() const -> CoordinatorServer &;
|
||||
|
||||
[[nodiscard]] auto DoFailover() -> DoFailoverStatus;
|
||||
|
||||
private:
|
||||
std::variant<CoordinatorData, CoordinatorMainReplicaData> data_;
|
||||
};
|
||||
|
||||
@@ -8,4 +8,14 @@
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
#include "system/include/system/system.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
enum class DoFailoverStatus : uint8_t { SUCCESS, ALL_REPLICAS_DOWN, MAIN_ALIVE, RPC_FAILED };
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -19,7 +19,7 @@ namespace memgraph::coordination {
|
||||
|
||||
enum class RegisterInstanceCoordinatorStatus : uint8_t {
|
||||
NAME_EXISTS,
|
||||
ENDPOINT_EXISTS,
|
||||
END_POINT_EXISTS,
|
||||
NOT_COORDINATOR,
|
||||
RPC_FAILED,
|
||||
SUCCESS
|
||||
|
||||
@@ -1,10 +1,2 @@
|
||||
add_library(mg-dbms STATIC
|
||||
dbms_handler.cpp
|
||||
database.cpp
|
||||
coordinator_handler.cpp
|
||||
inmemory/replication_handlers.cpp
|
||||
replication_handlers.cpp
|
||||
rpc.cpp
|
||||
|
||||
)
|
||||
target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query mg-auth mg-replication mg-coordination)
|
||||
add_library(mg-dbms STATIC dbms_handler.cpp database.cpp replication_handler.cpp coordinator_handler.cpp replication_client.cpp inmemory/replication_handlers.cpp coordinator_handlers.cpp)
|
||||
target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query mg-replication mg-coordination)
|
||||
|
||||
@@ -18,21 +18,20 @@
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
CoordinatorHandler::CoordinatorHandler(coordination::CoordinatorState &coordinator_state)
|
||||
: coordinator_state_(coordinator_state) {}
|
||||
CoordinatorHandler::CoordinatorHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
|
||||
|
||||
auto CoordinatorHandler::RegisterInstance(memgraph::coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterInstanceCoordinatorStatus {
|
||||
return coordinator_state_.RegisterInstance(config);
|
||||
return dbms_handler_.CoordinatorState().RegisterInstance(config);
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::SetInstanceToMain(std::string instance_name)
|
||||
-> coordination::SetInstanceToMainCoordinatorStatus {
|
||||
return coordinator_state_.SetInstanceToMain(std::move(instance_name));
|
||||
return dbms_handler_.CoordinatorState().SetInstanceToMain(std::move(instance_name));
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus> {
|
||||
return coordinator_state_.ShowInstances();
|
||||
return dbms_handler_.CoordinatorState().ShowInstances();
|
||||
}
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
|
||||
@@ -13,11 +13,15 @@
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "utils/result.hpp"
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "coordination/coordinator_instance_status.hpp"
|
||||
#include "coordination/coordinator_state.hpp"
|
||||
#include "coordination/failover_status.hpp"
|
||||
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace memgraph::dbms {
|
||||
@@ -26,7 +30,7 @@ class DbmsHandler;
|
||||
|
||||
class CoordinatorHandler {
|
||||
public:
|
||||
explicit CoordinatorHandler(coordination::CoordinatorState &coordinator_state);
|
||||
explicit CoordinatorHandler(DbmsHandler &dbms_handler);
|
||||
|
||||
auto RegisterInstance(coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterInstanceCoordinatorStatus;
|
||||
@@ -36,7 +40,7 @@ class CoordinatorHandler {
|
||||
auto ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus>;
|
||||
|
||||
private:
|
||||
coordination::CoordinatorState &coordinator_state_;
|
||||
DbmsHandler &dbms_handler_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
155
src/dbms/coordinator_handlers.cpp
Normal file
155
src/dbms/coordinator_handlers.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "dbms/coordinator_handlers.hpp"
|
||||
#include "dbms/utils.hpp"
|
||||
|
||||
#include "coordination/coordinator_exceptions.hpp"
|
||||
#include "coordination/coordinator_rpc.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "dbms/replication_client.hpp"
|
||||
|
||||
#include "range/v3/view.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
void CoordinatorHandlers::Register(DbmsHandler &dbms_handler) {
|
||||
auto &server = dbms_handler.CoordinatorState().GetCoordinatorServer();
|
||||
|
||||
server.Register<coordination::PromoteReplicaToMainRpc>(
|
||||
[&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received PromoteReplicaToMainRpc");
|
||||
CoordinatorHandlers::PromoteReplicaToMainHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
|
||||
server.Register<coordination::SetMainToReplicaRpc>(
|
||||
[&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received SetMainToReplicaRpc from coordinator server");
|
||||
CoordinatorHandlers::SetMainToReplicaHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder) {
|
||||
auto &repl_state = dbms_handler.ReplicationState();
|
||||
spdlog::info("Executing SetMainToReplicaHandler");
|
||||
|
||||
if (repl_state.IsReplica()) {
|
||||
spdlog::error("Setting to replica must be performed on main.");
|
||||
slk::Save(coordination::SetMainToReplicaRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
coordination::SetMainToReplicaReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
const replication::ReplicationServerConfig clients_config{
|
||||
.ip_address = req.replication_client_info.replication_ip_address,
|
||||
.port = req.replication_client_info.replication_port};
|
||||
|
||||
if (bool success = memgraph::dbms::SetReplicationRoleReplica(dbms_handler, clients_config); !success) {
|
||||
spdlog::error("Setting main to replica failed!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder) {
|
||||
auto &repl_state = dbms_handler.ReplicationState();
|
||||
|
||||
if (!repl_state.IsReplica()) {
|
||||
spdlog::error("Only replica can be promoted to main!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
auto repl_server_config = std::get<replication::RoleReplicaData>(repl_state.ReplicationData()).config;
|
||||
|
||||
// This can fail because of disk. If it does, the cluster state could get inconsistent.
|
||||
// We don't handle disk issues.
|
||||
if (bool success = memgraph::dbms::DoReplicaToMainPromotion(dbms_handler); !success) {
|
||||
spdlog::error("Promoting replica to main failed!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
coordination::PromoteReplicaToMainReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
auto const converter = [](const auto &repl_info_config) {
|
||||
return replication::ReplicationClientConfig{
|
||||
.name = repl_info_config.instance_name,
|
||||
.mode = repl_info_config.replication_mode,
|
||||
.ip_address = repl_info_config.replication_ip_address,
|
||||
.port = repl_info_config.replication_port,
|
||||
};
|
||||
};
|
||||
|
||||
MG_ASSERT(
|
||||
std::get<replication::RoleMainData>(repl_state.ReplicationData()).registered_replicas_.empty(),
|
||||
"No replicas should be registered after promoting replica to main and before registering replication clients!");
|
||||
|
||||
// registering replicas
|
||||
for (auto const &config : req.replication_clients_info | ranges::views::transform(converter)) {
|
||||
auto instance_client = repl_state.RegisterReplica(config);
|
||||
if (instance_client.HasError()) {
|
||||
switch (instance_client.GetError()) {
|
||||
// Can't happen, we are already replica
|
||||
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
|
||||
spdlog::error("Failover must be performed to main!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
// Can't happen, checked on the coordinator side
|
||||
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
|
||||
spdlog::error("Replica with the same name already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
// Can't happen, checked on the coordinator side
|
||||
case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
spdlog::error("Replica with the same endpoint already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
// We don't handle disk issues
|
||||
case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
spdlog::error("Registered replica could not be persisted!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allow_mt_repl && dbms_handler.All().size() > 1) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
|
||||
auto &instance_client_ref = *instance_client.GetValue();
|
||||
|
||||
// Update system before enabling individual storage <-> replica clients
|
||||
dbms_handler.SystemRestore(instance_client_ref);
|
||||
|
||||
// TODO: (andi) Policy for register all databases
|
||||
// Will be resolved after deciding about choosing new replica
|
||||
const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler, instance_client_ref);
|
||||
MG_ASSERT(all_clients_good, "Failed to register one or more databases to the REPLICA \"{}\".", config.name);
|
||||
|
||||
StartReplicaClient(dbms_handler, instance_client_ref);
|
||||
}
|
||||
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
@@ -13,9 +13,7 @@
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "replication_handler/replication_handler.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
@@ -23,14 +21,12 @@ class DbmsHandler;
|
||||
|
||||
class CoordinatorHandlers {
|
||||
public:
|
||||
static void Register(memgraph::coordination::CoordinatorServer &server,
|
||||
replication::ReplicationHandler &replication_handler);
|
||||
static void Register(DbmsHandler &dbms_handler);
|
||||
|
||||
private:
|
||||
static void PromoteReplicaToMainHandler(replication::ReplicationHandler &replication_handler, slk::Reader *req_reader,
|
||||
static void PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
static void DemoteMainToReplicaHandler(replication::ReplicationHandler &replication_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
static void SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -11,7 +11,10 @@
|
||||
|
||||
#include "dbms/database.hpp"
|
||||
#include "dbms/inmemory/storage_helper.hpp"
|
||||
#include "dbms/replication_handler.hpp"
|
||||
#include "flags/storage_mode.hpp"
|
||||
#include "storage/v2/disk/storage.hpp"
|
||||
#include "storage/v2/inmemory/storage.hpp"
|
||||
#include "storage/v2/storage_mode.hpp"
|
||||
|
||||
template struct memgraph::utils::Gatekeeper<memgraph::dbms::Database>;
|
||||
|
||||
@@ -11,73 +11,29 @@
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
|
||||
#include "dbms/coordinator_handlers.hpp"
|
||||
#include "flags/replication.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/global.hpp"
|
||||
#include "dbms/replication_client.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "system/include/system/system.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view kDBPrefix = "database:"; // Key prefix for database durability
|
||||
|
||||
std::string RegisterReplicaErrorToString(query::RegisterReplicaError error) {
|
||||
switch (error) {
|
||||
using enum query::RegisterReplicaError;
|
||||
case NAME_EXISTS:
|
||||
return "NAME_EXISTS";
|
||||
case ENDPOINT_EXISTS:
|
||||
return "ENDPOINT_EXISTS";
|
||||
case CONNECTION_FAILED:
|
||||
return "CONNECTION_FAILED";
|
||||
case COULD_NOT_BE_PERSISTED:
|
||||
return "COULD_NOT_BE_PERSISTED";
|
||||
}
|
||||
}
|
||||
|
||||
// Per storage
|
||||
// NOTE Storage will connect to all replicas. Future work might change this
|
||||
void RestoreReplication(replication::RoleMainData &mainData, DatabaseAccess db_acc) {
|
||||
spdlog::info("Restoring replication role.");
|
||||
|
||||
// Each individual client has already been restored and started. Here we just go through each database and start its
|
||||
// client
|
||||
for (auto &instance_client : mainData.registered_replicas_) {
|
||||
spdlog::info("Replica {} restoration started for {}.", instance_client.name_, db_acc->name());
|
||||
const auto &ret = db_acc->storage()->repl_storage_state_.replication_clients_.WithLock(
|
||||
[&, db_acc](auto &storage_clients) mutable -> utils::BasicResult<query::RegisterReplicaError> {
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client);
|
||||
auto *storage = db_acc->storage();
|
||||
client->Start(storage, std::move(db_acc));
|
||||
// After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
|
||||
// MAYBE_BEHIND isn't a statement of the current state, this is the default value
|
||||
// Failed to start due to branching of MAIN and REPLICA
|
||||
if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.",
|
||||
instance_client.name_);
|
||||
}
|
||||
storage_clients.push_back(std::move(client));
|
||||
return {};
|
||||
});
|
||||
|
||||
if (ret.HasError()) {
|
||||
MG_ASSERT(query::RegisterReplicaError::CONNECTION_FAILED != ret.GetError());
|
||||
LOG_FATAL("Failure when restoring replica {}: {}.", instance_client.name_,
|
||||
RegisterReplicaErrorToString(ret.GetError()));
|
||||
}
|
||||
spdlog::info("Replica {} restored for {}.", instance_client.name_, db_acc->name());
|
||||
}
|
||||
spdlog::info("Replication role restored to MAIN.");
|
||||
}
|
||||
constexpr std::string_view kDBPrefix = "database:"; // Key prefix for database durability
|
||||
constexpr std::string_view kLastCommitedSystemTsKey = "last_commited_system_ts"; // Key for timestamp durability
|
||||
} // namespace
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
struct Durability {
|
||||
enum class DurabilityVersion : uint8_t {
|
||||
V0 = 0,
|
||||
@@ -156,9 +112,11 @@ struct Durability {
|
||||
}
|
||||
};
|
||||
|
||||
DbmsHandler::DbmsHandler(storage::Config config, memgraph::system::System &system,
|
||||
replication::ReplicationState &repl_state, auth::SynchedAuth &auth, bool recovery_on_startup)
|
||||
: default_config_{std::move(config)}, auth_{auth}, repl_state_{repl_state}, system_{&system} {
|
||||
DbmsHandler::DbmsHandler(
|
||||
storage::Config config,
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
bool recovery_on_startup)
|
||||
: default_config_{std::move(config)}, repl_state_{ReplicationStateRootPath(default_config_)} {
|
||||
// TODO: Decouple storage config from dbms config
|
||||
// TODO: Save individual db configs inside the kvstore and restore from there
|
||||
|
||||
@@ -192,13 +150,19 @@ DbmsHandler::DbmsHandler(storage::Config config, memgraph::system::System &syste
|
||||
const auto uuid = json.at("uuid").get<utils::UUID>();
|
||||
const auto rel_dir = json.at("rel_dir").get<std::filesystem::path>();
|
||||
spdlog::info("Restoring database {} at {}.", name, rel_dir);
|
||||
auto new_db = New_(name, uuid, nullptr, rel_dir);
|
||||
auto new_db = New_(name, uuid, rel_dir);
|
||||
MG_ASSERT(!new_db.HasError(), "Failed while creating database {}.", name);
|
||||
directories.emplace(rel_dir.filename());
|
||||
spdlog::info("Database {} restored.", name);
|
||||
}
|
||||
// Read the last timestamp
|
||||
auto lcst = durability_->Get(kLastCommitedSystemTsKey);
|
||||
if (lcst) {
|
||||
last_commited_system_timestamp_ = std::stoul(*lcst);
|
||||
system_timestamp_ = last_commited_system_timestamp_;
|
||||
}
|
||||
} else { // Clear databases from the durability list and auth
|
||||
auto locked_auth = auth_.Lock();
|
||||
auto locked_auth = auth->Lock();
|
||||
auto it = durability_->begin(std::string{kDBPrefix});
|
||||
auto end = durability_->end(std::string{kDBPrefix});
|
||||
for (; it != end; ++it) {
|
||||
@@ -208,6 +172,8 @@ DbmsHandler::DbmsHandler(storage::Config config, memgraph::system::System &syste
|
||||
locked_auth->DeleteDatabase(name);
|
||||
durability_->Delete(key);
|
||||
}
|
||||
// Delete the last timestamp
|
||||
durability_->Delete(kLastCommitedSystemTsKey);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -232,29 +198,45 @@ DbmsHandler::DbmsHandler(storage::Config config, memgraph::system::System &syste
|
||||
*/
|
||||
// Setup the default DB
|
||||
SetupDefault_();
|
||||
|
||||
/*
|
||||
* REPLICATION RECOVERY AND STARTUP
|
||||
*/
|
||||
// Startup replication state (if recovered at startup)
|
||||
auto replica = [this](replication::RoleReplicaData const &data) { return StartRpcServer(*this, data); };
|
||||
// Replication recovery and frequent check start
|
||||
auto main = [this](replication::RoleMainData &data) {
|
||||
for (auto &client : data.registered_replicas_) {
|
||||
SystemRestore(client);
|
||||
}
|
||||
ForEach([this](DatabaseAccess db) { RecoverReplication(db); });
|
||||
for (auto &client : data.registered_replicas_) {
|
||||
StartReplicaClient(*this, client);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Startup proccess for main/replica
|
||||
MG_ASSERT(std::visit(memgraph::utils::Overloaded{replica, main}, repl_state_.ReplicationData()),
|
||||
"Replica recovery failure!");
|
||||
|
||||
// Warning
|
||||
if (default_config_.durability.snapshot_wal_mode == storage::Config::Durability::SnapshotWalMode::DISABLED &&
|
||||
repl_state_.IsMain()) {
|
||||
spdlog::warn(
|
||||
"The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please "
|
||||
"consider "
|
||||
"enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
|
||||
"without write-ahead logs this instance is not replicating any data.");
|
||||
}
|
||||
|
||||
// MAIN or REPLICA instance
|
||||
if (FLAGS_coordinator_server_port) {
|
||||
CoordinatorHandlers::Register(*this);
|
||||
MG_ASSERT(coordinator_state_.GetCoordinatorServer().Start(), "Failed to start coordinator server!");
|
||||
}
|
||||
}
|
||||
|
||||
struct DropDatabase : memgraph::system::ISystemAction {
|
||||
explicit DropDatabase(utils::UUID uuid) : uuid_{uuid} {}
|
||||
void DoDurability() override { /* Done during DBMS execution */
|
||||
}
|
||||
|
||||
bool DoReplication(replication::ReplicationClient &client, replication::ReplicationEpoch const &epoch,
|
||||
memgraph::system::Transaction const &txn) const override {
|
||||
auto check_response = [](const storage::replication::DropDatabaseRes &response) {
|
||||
return response.result != storage::replication::DropDatabaseRes::Result::FAILURE;
|
||||
};
|
||||
|
||||
return client.SteamAndFinalizeDelta<storage::replication::DropDatabaseRpc>(
|
||||
check_response, epoch.id(), txn.last_committed_system_timestamp(), txn.timestamp(), uuid_);
|
||||
}
|
||||
void PostReplication(replication::RoleMainData &mainData) const override {}
|
||||
|
||||
private:
|
||||
utils::UUID uuid_;
|
||||
};
|
||||
|
||||
DbmsHandler::DeleteResult DbmsHandler::TryDelete(std::string_view db_name, system::Transaction *transaction) {
|
||||
DbmsHandler::DeleteResult DbmsHandler::TryDelete(std::string_view db_name) {
|
||||
std::lock_guard<LockT> wr(lock_);
|
||||
if (db_name == kDefaultDB) {
|
||||
// MSG cannot delete the default db
|
||||
@@ -291,10 +273,9 @@ DbmsHandler::DeleteResult DbmsHandler::TryDelete(std::string_view db_name, syste
|
||||
|
||||
// Success
|
||||
// Save delta
|
||||
if (transaction) {
|
||||
transaction->AddAction<DropDatabase>(uuid);
|
||||
if (system_transaction_) {
|
||||
system_transaction_->delta.emplace(SystemTransaction::Delta::drop_database, uuid);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -315,48 +296,18 @@ DbmsHandler::DeleteResult DbmsHandler::Delete(utils::UUID uuid) {
|
||||
return Delete_(db_name);
|
||||
}
|
||||
|
||||
struct CreateDatabase : memgraph::system::ISystemAction {
|
||||
explicit CreateDatabase(storage::SalientConfig config, DatabaseAccess db_acc)
|
||||
: config_{std::move(config)}, db_acc(db_acc) {}
|
||||
|
||||
void DoDurability() override {
|
||||
// Done during dbms execution
|
||||
}
|
||||
|
||||
bool DoReplication(replication::ReplicationClient &client, replication::ReplicationEpoch const &epoch,
|
||||
memgraph::system::Transaction const &txn) const override {
|
||||
auto check_response = [](const storage::replication::CreateDatabaseRes &response) {
|
||||
return response.result != storage::replication::CreateDatabaseRes::Result::FAILURE;
|
||||
};
|
||||
|
||||
return client.SteamAndFinalizeDelta<storage::replication::CreateDatabaseRpc>(
|
||||
check_response, epoch.id(), txn.last_committed_system_timestamp(), txn.timestamp(), config_);
|
||||
}
|
||||
|
||||
void PostReplication(replication::RoleMainData &mainData) const override {
|
||||
// Sync database with REPLICAs
|
||||
// NOTE: The function bellow is used to create ReplicationStorageClient, so it must be called on a new storage
|
||||
// We don't need to have it here, since the function won't fail even if the replication client fails to
|
||||
// connect We will just have everything ready, for recovery at some point.
|
||||
dbms::DbmsHandler::RecoverStorageReplication(db_acc, mainData);
|
||||
}
|
||||
|
||||
private:
|
||||
storage::SalientConfig config_;
|
||||
DatabaseAccess db_acc;
|
||||
};
|
||||
|
||||
DbmsHandler::NewResultT DbmsHandler::New_(storage::Config storage_config, system::Transaction *txn) {
|
||||
DbmsHandler::NewResultT DbmsHandler::New_(storage::Config storage_config) {
|
||||
auto new_db = db_handler_.New(storage_config, repl_state_);
|
||||
|
||||
if (new_db.HasValue()) { // Success
|
||||
// Save delta
|
||||
UpdateDurability(storage_config);
|
||||
if (txn) {
|
||||
txn->AddAction<CreateDatabase>(storage_config.salient, new_db.GetValue());
|
||||
if (system_transaction_) {
|
||||
system_transaction_->delta.emplace(SystemTransaction::Delta::create_database, storage_config.salient);
|
||||
}
|
||||
UpdateDurability(storage_config);
|
||||
return new_db.GetValue();
|
||||
}
|
||||
return new_db;
|
||||
return new_db.GetError();
|
||||
}
|
||||
|
||||
DbmsHandler::DeleteResult DbmsHandler::Delete_(std::string_view db_name) {
|
||||
@@ -410,16 +361,89 @@ void DbmsHandler::UpdateDurability(const storage::Config &config, std::optional<
|
||||
durability_->Put(key, val);
|
||||
}
|
||||
|
||||
#endif
|
||||
AllSyncReplicaStatus DbmsHandler::Commit() {
|
||||
if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt)
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed; // Nothing to commit
|
||||
const auto &delta = *system_transaction_->delta;
|
||||
|
||||
void DbmsHandler::RecoverStorageReplication(DatabaseAccess db_acc, replication::RoleMainData &role_main_data) {
|
||||
if (allow_mt_repl || db_acc->name() == dbms::kDefaultDB) {
|
||||
// Handle global replication state
|
||||
spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
|
||||
// RECOVER REPLICA CONNECTIONS
|
||||
memgraph::dbms::RestoreReplication(role_main_data, db_acc);
|
||||
} else if (!role_main_data.registered_replicas_.empty()) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
auto sync_status = AllSyncReplicaStatus::AllCommitsConfirmed;
|
||||
// TODO Create a system client that can handle all of this automatically
|
||||
switch (delta.action) {
|
||||
using enum SystemTransaction::Delta::Action;
|
||||
case CREATE_DATABASE: {
|
||||
// Replication
|
||||
auto main_handler = [&](memgraph::replication::RoleMainData &main_data) {
|
||||
// TODO: data race issue? registered_replicas_ access not protected
|
||||
// This is sync in any case, as this is the startup
|
||||
for (auto &client : main_data.registered_replicas_) {
|
||||
bool completed = SteamAndFinalizeDelta<storage::replication::CreateDatabaseRpc>(
|
||||
client,
|
||||
[](const storage::replication::CreateDatabaseRes &response) {
|
||||
return response.result != storage::replication::CreateDatabaseRes::Result::FAILURE;
|
||||
},
|
||||
std::string(main_data.epoch_.id()), last_commited_system_timestamp_,
|
||||
system_transaction_->system_timestamp, delta.config);
|
||||
// TODO: reduce duplicate code
|
||||
if (!completed && client.mode_ == replication_coordination_glue::ReplicationMode::SYNC) {
|
||||
sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
|
||||
}
|
||||
}
|
||||
// Sync database with REPLICAs
|
||||
RecoverReplication(Get_(delta.config.name));
|
||||
};
|
||||
auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ };
|
||||
std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
|
||||
} break;
|
||||
case DROP_DATABASE: {
|
||||
// Replication
|
||||
auto main_handler = [&](memgraph::replication::RoleMainData &main_data) {
|
||||
// TODO: data race issue? registered_replicas_ access not protected
|
||||
// This is sync in any case, as this is the startup
|
||||
for (auto &client : main_data.registered_replicas_) {
|
||||
bool completed = SteamAndFinalizeDelta<storage::replication::DropDatabaseRpc>(
|
||||
client,
|
||||
[](const storage::replication::DropDatabaseRes &response) {
|
||||
return response.result != storage::replication::DropDatabaseRes::Result::FAILURE;
|
||||
},
|
||||
std::string(main_data.epoch_.id()), last_commited_system_timestamp_,
|
||||
system_transaction_->system_timestamp, delta.uuid);
|
||||
// TODO: reduce duplicate code
|
||||
if (!completed && client.mode_ == replication_coordination_glue::ReplicationMode::SYNC) {
|
||||
sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
|
||||
}
|
||||
}
|
||||
};
|
||||
auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ };
|
||||
std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
|
||||
} break;
|
||||
}
|
||||
|
||||
durability_->Put(kLastCommitedSystemTsKey, std::to_string(system_transaction_->system_timestamp));
|
||||
last_commited_system_timestamp_ = system_transaction_->system_timestamp;
|
||||
ResetSystemTransaction();
|
||||
return sync_status;
|
||||
}
|
||||
|
||||
#else // not MG_ENTERPRISE
|
||||
|
||||
AllSyncReplicaStatus DbmsHandler::Commit() {
|
||||
if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt) {
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed; // Nothing to commit
|
||||
}
|
||||
const auto &delta = *system_transaction_->delta;
|
||||
|
||||
switch (delta.action) {
|
||||
using enum SystemTransaction::Delta::Action;
|
||||
case CREATE_DATABASE:
|
||||
case DROP_DATABASE:
|
||||
/* Community edition doesn't support multi-tenant replication */
|
||||
break;
|
||||
}
|
||||
|
||||
last_commited_system_timestamp_ = system_transaction_->system_timestamp;
|
||||
ResetSystemTransaction();
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed;
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -25,24 +25,24 @@
|
||||
#include "constants.hpp"
|
||||
#include "dbms/database.hpp"
|
||||
#include "dbms/inmemory/replication_handlers.hpp"
|
||||
#include "dbms/rpc.hpp"
|
||||
#include "dbms/replication_handler.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "replication/replication_client.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/replication/enums.hpp"
|
||||
#include "storage/v2/replication/rpc.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "system/system.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "coordination/coordinator_state.hpp"
|
||||
#include "dbms/database_handler.hpp"
|
||||
#endif
|
||||
#include "dbms/transaction.hpp"
|
||||
#include "global.hpp"
|
||||
#include "query/config.hpp"
|
||||
#include "query/interpreter_context.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "system/system.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
@@ -51,6 +51,11 @@
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
enum class AllSyncReplicaStatus {
|
||||
AllCommitsConfirmed,
|
||||
SomeCommitsUnconfirmed,
|
||||
};
|
||||
|
||||
struct Statistics {
|
||||
uint64_t num_vertex; //!< Sum of vertexes in every database
|
||||
uint64_t num_edges; //!< Sum of edges in every database
|
||||
@@ -106,8 +111,8 @@ class DbmsHandler {
|
||||
* @param auth pointer to the global authenticator
|
||||
* @param recovery_on_startup restore databases (and its content) and authentication data
|
||||
*/
|
||||
DbmsHandler(storage::Config config, memgraph::system::System &system, replication::ReplicationState &repl_state,
|
||||
auth::SynchedAuth &auth,
|
||||
DbmsHandler(storage::Config config,
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
bool recovery_on_startup); // TODO If more arguments are added use a config struct
|
||||
#else
|
||||
/**
|
||||
@@ -115,14 +120,15 @@ class DbmsHandler {
|
||||
*
|
||||
* @param configs storage configuration
|
||||
*/
|
||||
DbmsHandler(storage::Config config, memgraph::system::System &system, replication::ReplicationState &repl_state)
|
||||
: repl_state_{repl_state},
|
||||
system_{&system},
|
||||
DbmsHandler(storage::Config config)
|
||||
: repl_state_{ReplicationStateRootPath(config)},
|
||||
db_gatekeeper_{[&] {
|
||||
config.salient.name = kDefaultDB;
|
||||
return std::move(config);
|
||||
}(),
|
||||
repl_state_} {}
|
||||
repl_state_} {
|
||||
RecoverReplication(Get());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -132,10 +138,10 @@ class DbmsHandler {
|
||||
* @param name name of the database
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
NewResultT New(const std::string &name, system::Transaction *txn = nullptr) {
|
||||
NewResultT New(const std::string &name) {
|
||||
std::lock_guard<LockT> wr(lock_);
|
||||
const auto uuid = utils::UUID{};
|
||||
return New_(name, uuid, txn);
|
||||
return New_(name, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +234,7 @@ class DbmsHandler {
|
||||
* @param db_name database name
|
||||
* @return DeleteResult error on failure
|
||||
*/
|
||||
DeleteResult TryDelete(std::string_view db_name, system::Transaction *transaction = nullptr);
|
||||
DeleteResult TryDelete(std::string_view db_name);
|
||||
|
||||
/**
|
||||
* @brief Delete or defer deletion of database.
|
||||
@@ -261,12 +267,23 @@ class DbmsHandler {
|
||||
#endif
|
||||
}
|
||||
|
||||
replication::ReplicationState &ReplicationState() { return repl_state_; }
|
||||
replication::ReplicationState const &ReplicationState() const { return repl_state_; }
|
||||
|
||||
bool IsMain() const { return repl_state_.IsMain(); }
|
||||
bool IsReplica() const { return repl_state_.IsReplica(); }
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
coordination::CoordinatorState &CoordinatorState() { return coordinator_state_; }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return the statistics all databases.
|
||||
*
|
||||
* @return Statistics
|
||||
*/
|
||||
Statistics Stats(memgraph::replication_coordination_glue::ReplicationRole replication_role) {
|
||||
Statistics Stats() {
|
||||
auto const replication_role = repl_state_.GetRole();
|
||||
Statistics stats{};
|
||||
// TODO: Handle overflow?
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -302,7 +319,8 @@ class DbmsHandler {
|
||||
*
|
||||
* @return std::vector<DatabaseInfo>
|
||||
*/
|
||||
std::vector<DatabaseInfo> Info(memgraph::replication_coordination_glue::ReplicationRole replication_role) {
|
||||
std::vector<DatabaseInfo> Info() {
|
||||
auto const replication_role = repl_state_.GetRole();
|
||||
std::vector<DatabaseInfo> res;
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::shared_lock<LockT> rd(lock_);
|
||||
@@ -389,18 +407,99 @@ class DbmsHandler {
|
||||
}
|
||||
}
|
||||
|
||||
static void RecoverStorageReplication(DatabaseAccess db_acc, replication::RoleMainData &role_main_data);
|
||||
|
||||
auto default_config() const -> storage::Config const & {
|
||||
#ifdef MG_ENTERPRISE
|
||||
return default_config_;
|
||||
#else
|
||||
const auto acc = db_gatekeeper_.access();
|
||||
MG_ASSERT(acc, "Failed to get default database!");
|
||||
return acc->get()->config();
|
||||
#endif
|
||||
void NewSystemTransaction() {
|
||||
DMG_ASSERT(!system_transaction_, "Already running a system transaction");
|
||||
system_transaction_.emplace(++system_timestamp_);
|
||||
}
|
||||
|
||||
void ResetSystemTransaction() { system_transaction_.reset(); }
|
||||
|
||||
//! \tparam RPC An rpc::RequestResponse
|
||||
//! \tparam Args the args type
|
||||
//! \param client the client to use for rpc communication
|
||||
//! \param check predicate to check response is ok
|
||||
//! \param args arguments to forward to the rpc request
|
||||
//! \return If replica stream is completed or enqueued
|
||||
template <typename RPC, typename... Args>
|
||||
bool SteamAndFinalizeDelta(auto &client, auto &&check, Args &&...args) {
|
||||
try {
|
||||
auto stream = client.rpc_client_.template Stream<RPC>(std::forward<Args>(args)...);
|
||||
auto task = [&client, check = std::forward<decltype(check)>(check), stream = std::move(stream)]() mutable {
|
||||
if (stream.IsDefunct()) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (check(stream.AwaitResponse())) {
|
||||
return true;
|
||||
}
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
// swallow error, fallthrough to error handling
|
||||
}
|
||||
// This replica needs SYSTEM recovery
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
};
|
||||
|
||||
if (client.mode_ == memgraph::replication_coordination_glue::ReplicationMode::ASYNC) {
|
||||
client.thread_pool_.AddTask([task = utils::CopyMovableFunctionWrapper{std::move(task)}]() mutable { task(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
return task();
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
// This replica needs SYSTEM recovery
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
AllSyncReplicaStatus Commit();
|
||||
|
||||
auto LastCommitedTS() const -> uint64_t { return last_commited_system_timestamp_; }
|
||||
void SetLastCommitedTS(uint64_t new_ts) { last_commited_system_timestamp_.store(new_ts); }
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// When being called by intepreter no need to gain lock, it should already be under a system transaction
|
||||
// But concurrently the FrequentCheck is running and will need to lock before reading last_commited_system_timestamp_
|
||||
template <bool REQUIRE_LOCK = false>
|
||||
void SystemRestore(replication::ReplicationClient &client) {
|
||||
// Check if system is up to date
|
||||
if (client.state_.WithLock(
|
||||
[](auto &state) { return state == memgraph::replication::ReplicationClient::State::READY; }))
|
||||
return;
|
||||
|
||||
// Try to recover...
|
||||
{
|
||||
auto [database_configs, last_commited_system_timestamp] = std::invoke([&] {
|
||||
auto sys_guard =
|
||||
std::unique_lock{system_lock_, std::defer_lock}; // ensure no other system transaction in progress
|
||||
if constexpr (REQUIRE_LOCK) {
|
||||
sys_guard.lock();
|
||||
}
|
||||
auto configs = std::vector<storage::SalientConfig>{};
|
||||
ForEach([&configs](DatabaseAccess acc) { configs.emplace_back(acc->config().salient); });
|
||||
return std::pair{configs, last_commited_system_timestamp_.load()};
|
||||
});
|
||||
try {
|
||||
auto stream = client.rpc_client_.Stream<storage::replication::SystemRecoveryRpc>(last_commited_system_timestamp,
|
||||
std::move(database_configs));
|
||||
const auto response = stream.AwaitResponse();
|
||||
if (response.result == storage::replication::SystemRecoveryRes::Result::FAILURE) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return;
|
||||
}
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully recovered
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::READY; });
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef MG_ENTERPRISE
|
||||
/**
|
||||
@@ -425,8 +524,7 @@ class DbmsHandler {
|
||||
* @param uuid undelying RocksDB directory
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
NewResultT New_(std::string_view name, utils::UUID uuid, system::Transaction *txn = nullptr,
|
||||
std::optional<std::filesystem::path> rel_dir = {}) {
|
||||
NewResultT New_(std::string_view name, utils::UUID uuid, std::optional<std::filesystem::path> rel_dir = {}) {
|
||||
auto config_copy = default_config_;
|
||||
config_copy.salient.name = name;
|
||||
config_copy.salient.uuid = uuid;
|
||||
@@ -437,7 +535,7 @@ class DbmsHandler {
|
||||
storage::UpdatePaths(config_copy,
|
||||
default_config_.durability.storage_directory / kMultiTenantDir / std::string{uuid});
|
||||
}
|
||||
return New_(std::move(config_copy), txn);
|
||||
return New_(std::move(config_copy));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,11 +544,11 @@ class DbmsHandler {
|
||||
* @param config configuration to be used
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
NewResultT New_(const storage::SalientConfig &config, system::Transaction *txn = nullptr) {
|
||||
NewResultT New_(const storage::SalientConfig &config) {
|
||||
auto config_copy = default_config_;
|
||||
config_copy.salient = config; // name, uuid, mode, etc
|
||||
UpdatePaths(config_copy, config_copy.durability.storage_directory / kMultiTenantDir / std::string{config.uuid});
|
||||
return New_(std::move(config_copy), txn);
|
||||
return New_(std::move(config_copy));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,7 +557,7 @@ class DbmsHandler {
|
||||
* @param storage_config storage configuration
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
DbmsHandler::NewResultT New_(storage::Config storage_config, system::Transaction *txn = nullptr);
|
||||
NewResultT New_(storage::Config storage_config);
|
||||
|
||||
// TODO: new overload of Delete_ with DatabaseAccess
|
||||
DeleteResult Delete_(std::string_view db_name);
|
||||
@@ -474,8 +572,7 @@ class DbmsHandler {
|
||||
Get(kDefaultDB);
|
||||
} catch (const UnknownDatabaseException &) {
|
||||
// No default DB restored, create it
|
||||
MG_ASSERT(New_(kDefaultDB, {/* random UUID */}, nullptr, ".").HasValue(),
|
||||
"Failed while creating the default database");
|
||||
MG_ASSERT(New_(kDefaultDB, {/* random UUID */}, ".").HasValue(), "Failed while creating the default database");
|
||||
}
|
||||
|
||||
// For back-compatibility...
|
||||
@@ -562,24 +659,35 @@ class DbmsHandler {
|
||||
}
|
||||
#endif
|
||||
|
||||
void RecoverReplication(DatabaseAccess db_acc) {
|
||||
if (allow_mt_repl || db_acc->name() == dbms::kDefaultDB) {
|
||||
// Handle global replication state
|
||||
spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
|
||||
// RECOVER REPLICA CONNECTIONS
|
||||
memgraph::dbms::RestoreReplication(repl_state_, std::move(db_acc));
|
||||
} else if (const ::memgraph::replication::RoleMainData *data =
|
||||
std::get_if<::memgraph::replication::RoleMainData>(&repl_state_.ReplicationData());
|
||||
data && !data->registered_replicas_.empty()) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
mutable LockT lock_{utils::RWLock::Priority::READ}; //!< protective lock
|
||||
storage::Config default_config_; //!< Storage configuration used when creating new databases
|
||||
DatabaseHandler db_handler_; //!< multi-tenancy storage handler
|
||||
// TODO: move to be common
|
||||
std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation)
|
||||
auth::SynchedAuth &auth_; //!< Synchronized auth::Auth
|
||||
std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation)
|
||||
coordination::CoordinatorState coordinator_state_; //!< Replication coordinator
|
||||
#endif
|
||||
private:
|
||||
// NOTE: atm the only reason this exists here, is because we pass it into the construction of New Database's
|
||||
// Database only uses it as a convience to make the correct Access without out needing to be told the
|
||||
// current replication role. TODO: make Database Access explicit about the role and remove this from
|
||||
// dbms stuff
|
||||
replication::ReplicationState &repl_state_; //!< Ref to global replication state
|
||||
// TODO: Make an api
|
||||
public:
|
||||
// TODO fix to be non public/remove from dbms....maybe
|
||||
system::System *system_;
|
||||
|
||||
utils::ResourceLock system_lock_{}; //!> Ensure exclusive access for system queries
|
||||
private:
|
||||
std::optional<SystemTransaction> system_transaction_; //!< Current system transaction (only one at a time)
|
||||
uint64_t system_timestamp_{storage::kTimestampInitialId}; //!< System timestamp
|
||||
std::atomic_uint64_t last_commited_system_timestamp_{
|
||||
storage::kTimestampInitialId}; //!< Last commited system timestamp
|
||||
replication::ReplicationState repl_state_; //!< Global replication state
|
||||
#ifndef MG_ENTERPRISE
|
||||
mutable utils::Gatekeeper<Database> db_gatekeeper_; //!< Single databases gatekeeper
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/replication_handler.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/inmemory/storage.hpp"
|
||||
|
||||
43
src/dbms/replication_client.cpp
Normal file
43
src/dbms/replication_client.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "dbms/replication_client.hpp"
|
||||
#include "replication/replication_client.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
void StartReplicaClient(DbmsHandler &dbms_handler, replication::ReplicationClient &client) {
|
||||
// No client error, start instance level client
|
||||
auto const &endpoint = client.rpc_client_.Endpoint();
|
||||
spdlog::trace("Replication client started at: {}:{}", endpoint.address, endpoint.port);
|
||||
client.StartFrequentCheck([&dbms_handler](bool reconnect, replication::ReplicationClient &client) {
|
||||
// Working connection
|
||||
// Check if system needs restoration
|
||||
if (reconnect) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
dbms_handler.SystemRestore<true>(client);
|
||||
#endif
|
||||
// Check if any database has been left behind
|
||||
dbms_handler.ForEach([&name = client.name_, reconnect](dbms::DatabaseAccess db_acc) {
|
||||
// Specific database <-> replica client
|
||||
db_acc->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) {
|
||||
if (reconnect || client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
// Database <-> replica might be behind, check and recover
|
||||
client->TryCheckReplicaStateAsync(db_acc->storage(), db_acc);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -8,4 +8,14 @@
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
#include "system/include/system/action.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "replication/replication_client.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
void StartReplicaClient(DbmsHandler &dbms_handler, replication::ReplicationClient &client);
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
400
src/dbms/replication_handler.cpp
Normal file
400
src/dbms/replication_handler.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "dbms/replication_handler.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "dbms/global.hpp"
|
||||
#include "dbms/inmemory/replication_handlers.hpp"
|
||||
#include "dbms/replication_client.hpp"
|
||||
#include "dbms/utils.hpp"
|
||||
#include "replication/messages.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/replication/rpc.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
|
||||
using memgraph::replication::RoleMainData;
|
||||
using memgraph::replication::RoleReplicaData;
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string RegisterReplicaErrorToString(RegisterReplicaError error) {
|
||||
switch (error) {
|
||||
using enum RegisterReplicaError;
|
||||
case NAME_EXISTS:
|
||||
return "NAME_EXISTS";
|
||||
case ENDPOINT_EXISTS:
|
||||
return "ENDPOINT_EXISTS";
|
||||
case CONNECTION_FAILED:
|
||||
return "CONNECTION_FAILED";
|
||||
case COULD_NOT_BE_PERSISTED:
|
||||
return "COULD_NOT_BE_PERSISTED";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ReplicationHandler::ReplicationHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
|
||||
|
||||
bool ReplicationHandler::SetReplicationRoleMain() {
|
||||
auto const main_handler = [](RoleMainData &) {
|
||||
// If we are already MAIN, we don't want to change anything
|
||||
return false;
|
||||
};
|
||||
|
||||
auto const replica_handler = [this](RoleReplicaData const &) {
|
||||
return memgraph::dbms::DoReplicaToMainPromotion(dbms_handler_);
|
||||
};
|
||||
|
||||
// TODO: under lock
|
||||
return std::visit(utils::Overloaded{main_handler, replica_handler},
|
||||
dbms_handler_.ReplicationState().ReplicationData());
|
||||
}
|
||||
|
||||
bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config) {
|
||||
// We don't want to restart the server if we're already a REPLICA
|
||||
if (dbms_handler_.ReplicationState().IsReplica()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are
|
||||
// deleting the replica.
|
||||
// Remove database specific clients
|
||||
dbms_handler_.ForEach([&](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
|
||||
});
|
||||
// Remove instance level clients
|
||||
std::get<RoleMainData>(dbms_handler_.ReplicationState().ReplicationData()).registered_replicas_.clear();
|
||||
|
||||
// Creates the server
|
||||
dbms_handler_.ReplicationState().SetReplicationRoleReplica(config);
|
||||
|
||||
// Start
|
||||
const auto success =
|
||||
std::visit(utils::Overloaded{[](RoleMainData const &) {
|
||||
// ASSERT
|
||||
return false;
|
||||
},
|
||||
[this](RoleReplicaData const &data) { return StartRpcServer(dbms_handler_, data); }},
|
||||
dbms_handler_.ReplicationState().ReplicationData());
|
||||
// TODO Handle error (restore to main?)
|
||||
return success;
|
||||
}
|
||||
|
||||
auto ReplicationHandler::RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> memgraph::utils::BasicResult<RegisterReplicaError> {
|
||||
MG_ASSERT(dbms_handler_.ReplicationState().IsMain(), "Only main instance can register a replica!");
|
||||
|
||||
auto maybe_client = dbms_handler_.ReplicationState().RegisterReplica(config);
|
||||
if (maybe_client.HasError()) {
|
||||
switch (maybe_client.GetError()) {
|
||||
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
|
||||
MG_ASSERT(false, "Only main instance can register a replica!");
|
||||
return {};
|
||||
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
|
||||
return memgraph::dbms::RegisterReplicaError::NAME_EXISTS;
|
||||
case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
return memgraph::dbms::RegisterReplicaError::ENDPOINT_EXISTS;
|
||||
case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return memgraph::dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case memgraph::replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allow_mt_repl && dbms_handler_.All().size() > 1) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Update system before enabling individual storage <-> replica clients
|
||||
dbms_handler_.SystemRestore(*maybe_client.GetValue());
|
||||
#endif
|
||||
|
||||
const auto dbms_error = memgraph::dbms::HandleRegisterReplicaStatus(maybe_client);
|
||||
if (dbms_error.has_value()) {
|
||||
return *dbms_error;
|
||||
}
|
||||
auto &instance_client_ptr = maybe_client.GetValue();
|
||||
const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler_, *instance_client_ptr);
|
||||
|
||||
// NOTE Currently if any databases fails, we revert back
|
||||
if (!all_clients_good) {
|
||||
spdlog::error("Failed to register all databases on the REPLICA \"{}\"", config.name);
|
||||
UnregisterReplica(config.name);
|
||||
return RegisterReplicaError::CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
// No client error, start instance level client
|
||||
StartReplicaClient(dbms_handler_, *instance_client_ptr);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterReplicaResult {
|
||||
auto const replica_handler = [](RoleReplicaData const &) -> UnregisterReplicaResult {
|
||||
return UnregisterReplicaResult::NOT_MAIN;
|
||||
};
|
||||
auto const main_handler = [this, name](RoleMainData &mainData) -> UnregisterReplicaResult {
|
||||
if (!dbms_handler_.ReplicationState().TryPersistUnregisterReplica(name)) {
|
||||
return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
|
||||
}
|
||||
// Remove database specific clients
|
||||
dbms_handler_.ForEach([name](DatabaseAccess db_acc) {
|
||||
db_acc->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) {
|
||||
std::erase_if(clients, [name](const auto &client) { return client->Name() == name; });
|
||||
});
|
||||
});
|
||||
// Remove instance level clients
|
||||
auto const n_unregistered =
|
||||
std::erase_if(mainData.registered_replicas_, [name](auto const &client) { return client.name_ == name; });
|
||||
return n_unregistered != 0 ? UnregisterReplicaResult::SUCCESS : UnregisterReplicaResult::CAN_NOT_UNREGISTER;
|
||||
};
|
||||
|
||||
return std::visit(utils::Overloaded{main_handler, replica_handler},
|
||||
dbms_handler_.ReplicationState().ReplicationData());
|
||||
}
|
||||
|
||||
auto ReplicationHandler::GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole {
|
||||
return dbms_handler_.ReplicationState().GetRole();
|
||||
}
|
||||
|
||||
bool ReplicationHandler::IsMain() const { return dbms_handler_.ReplicationState().IsMain(); }
|
||||
|
||||
bool ReplicationHandler::IsReplica() const { return dbms_handler_.ReplicationState().IsReplica(); }
|
||||
|
||||
// Per storage
|
||||
// NOTE Storage will connect to all replicas. Future work might change this
|
||||
void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc) {
|
||||
spdlog::info("Restoring replication role.");
|
||||
|
||||
/// MAIN
|
||||
auto const recover_main = [db_acc = std::move(db_acc)](RoleMainData &mainData) mutable { // NOLINT
|
||||
// Each individual client has already been restored and started. Here we just go through each database and start its
|
||||
// client
|
||||
for (auto &instance_client : mainData.registered_replicas_) {
|
||||
spdlog::info("Replica {} restoration started for {}.", instance_client.name_, db_acc->name());
|
||||
const auto &ret = db_acc->storage()->repl_storage_state_.replication_clients_.WithLock(
|
||||
[&, db_acc](auto &storage_clients) mutable -> utils::BasicResult<RegisterReplicaError> {
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client);
|
||||
auto *storage = db_acc->storage();
|
||||
client->Start(storage, std::move(db_acc));
|
||||
// After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
|
||||
// MAYBE_BEHIND isn't a statement of the current state, this is the default value
|
||||
// Failed to start due to branching of MAIN and REPLICA
|
||||
if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.",
|
||||
instance_client.name_);
|
||||
}
|
||||
storage_clients.push_back(std::move(client));
|
||||
return {};
|
||||
});
|
||||
|
||||
if (ret.HasError()) {
|
||||
MG_ASSERT(RegisterReplicaError::CONNECTION_FAILED != ret.GetError());
|
||||
LOG_FATAL("Failure when restoring replica {}: {}.", instance_client.name_,
|
||||
RegisterReplicaErrorToString(ret.GetError()));
|
||||
}
|
||||
spdlog::info("Replica {} restored for {}.", instance_client.name_, db_acc->name());
|
||||
}
|
||||
spdlog::info("Replication role restored to MAIN.");
|
||||
};
|
||||
|
||||
/// REPLICA
|
||||
auto const recover_replica = [](RoleReplicaData const &data) { /*nothing to do*/ };
|
||||
|
||||
std::visit(
|
||||
utils::Overloaded{
|
||||
recover_main,
|
||||
recover_replica,
|
||||
},
|
||||
repl_state.ReplicationData());
|
||||
}
|
||||
|
||||
namespace system_replication {
|
||||
#ifdef MG_ENTERPRISE
|
||||
void SystemHeartbeatHandler(const uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::SystemHeartbeatReq req;
|
||||
replication::SystemHeartbeatReq::Load(&req, req_reader);
|
||||
|
||||
replication::SystemHeartbeatRes res(ts);
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
memgraph::storage::replication::CreateDatabaseReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::storage::replication::CreateDatabaseRes;
|
||||
CreateDatabaseRes res(CreateDatabaseRes::Result::FAILURE);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) {
|
||||
spdlog::debug("CreateDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
dbms_handler.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create new
|
||||
auto new_db = dbms_handler.Update(req.config);
|
||||
if (new_db.HasValue()) {
|
||||
// Successfully create db
|
||||
dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = CreateDatabaseRes(CreateDatabaseRes::Result::SUCCESS);
|
||||
spdlog::debug("CreateDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
}
|
||||
} catch (...) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void DropDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
memgraph::storage::replication::DropDatabaseReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::storage::replication::DropDatabaseRes;
|
||||
DropDatabaseRes res(DropDatabaseRes::Result::FAILURE);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) {
|
||||
spdlog::debug("DropDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
dbms_handler.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// NOTE: Single communication channel can exist at a time, no other database can be deleted/created at the moment.
|
||||
auto new_db = dbms_handler.Delete(req.uuid);
|
||||
if (new_db.HasError()) {
|
||||
if (new_db.GetError() == DeleteError::NON_EXISTENT) {
|
||||
// Nothing to drop
|
||||
dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = DropDatabaseRes(DropDatabaseRes::Result::NO_NEED);
|
||||
}
|
||||
} else {
|
||||
// Successfully drop db
|
||||
dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = DropDatabaseRes(DropDatabaseRes::Result::SUCCESS);
|
||||
spdlog::debug("DropDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
}
|
||||
} catch (...) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
// TODO Speed up
|
||||
memgraph::storage::replication::SystemRecoveryReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::storage::replication::SystemRecoveryRes;
|
||||
SystemRecoveryRes res(SystemRecoveryRes::Result::FAILURE);
|
||||
|
||||
utils::OnScopeExit send_on_exit([&]() { memgraph::slk::Save(res, res_builder); });
|
||||
|
||||
// Get all current dbs
|
||||
auto old = dbms_handler.All();
|
||||
|
||||
// Check/create the incoming dbs
|
||||
for (const auto &config : req.database_configs) {
|
||||
// Missing db
|
||||
try {
|
||||
if (dbms_handler.Update(config).HasError()) {
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to update database \"{}\".", config.name);
|
||||
return; // Send failure on exit
|
||||
}
|
||||
} catch (const UnknownDatabaseException &) {
|
||||
spdlog::debug("SystemRecoveryHandler: UnknownDatabaseException");
|
||||
return; // Send failure on exit
|
||||
}
|
||||
const auto it = std::find(old.begin(), old.end(), config.name);
|
||||
if (it != old.end()) old.erase(it);
|
||||
}
|
||||
|
||||
// Delete all the leftover old dbs
|
||||
for (const auto &remove_db : old) {
|
||||
const auto del = dbms_handler.Delete(remove_db);
|
||||
if (del.HasError()) {
|
||||
// Some errors are not terminal
|
||||
if (del.GetError() == DeleteError::DEFAULT_DB || del.GetError() == DeleteError::NON_EXISTENT) {
|
||||
spdlog::debug("SystemRecoveryHandler: Dropped database \"{}\".", remove_db);
|
||||
continue;
|
||||
}
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to drop database \"{}\".", remove_db);
|
||||
return; // Send failure on exit
|
||||
}
|
||||
}
|
||||
// Successfully recovered
|
||||
dbms_handler.SetLastCommitedTS(req.forced_group_timestamp);
|
||||
spdlog::debug("SystemRecoveryHandler: SUCCESS updated LCTS to {}", req.forced_group_timestamp);
|
||||
res = SystemRecoveryRes(SystemRecoveryRes::Result::SUCCESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
data.server->rpc_server_.Register<replication::SystemHeartbeatRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SystemHeartbeatRpc");
|
||||
SystemHeartbeatHandler(dbms_handler.LastCommitedTS(), req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<storage::replication::CreateDatabaseRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received CreateDatabaseRpc");
|
||||
CreateDatabaseHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<storage::replication::DropDatabaseRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received DropDatabaseRpc");
|
||||
DropDatabaseHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<storage::replication::SystemRecoveryRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SystemRecoveryRpc");
|
||||
SystemRecoveryHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
} // namespace system_replication
|
||||
|
||||
bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data) {
|
||||
// Register handlers
|
||||
InMemoryReplicationHandlers::Register(&dbms_handler, *data.server);
|
||||
system_replication::Register(data, dbms_handler);
|
||||
// Start server
|
||||
if (!data.server->Start()) {
|
||||
spdlog::error("Unable to start the replication server.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace memgraph::dbms
|
||||
82
src/dbms/replication_handler.hpp
Normal file
82
src/dbms/replication_handler.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "replication_coordination_glue/role.hpp"
|
||||
#include "dbms/database.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
struct ReplicationState;
|
||||
struct ReplicationServerConfig;
|
||||
struct ReplicationClientConfig;
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
class DbmsHandler;
|
||||
|
||||
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, ENDPOINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
|
||||
|
||||
enum class UnregisterReplicaResult : uint8_t {
|
||||
NOT_MAIN,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
CAN_NOT_UNREGISTER,
|
||||
SUCCESS,
|
||||
};
|
||||
|
||||
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
|
||||
/// TODO: extend to do multiple storages
|
||||
struct ReplicationHandler {
|
||||
explicit ReplicationHandler(DbmsHandler &dbms_handler);
|
||||
|
||||
// as REPLICA, become MAIN
|
||||
bool SetReplicationRoleMain();
|
||||
|
||||
// as MAIN, become REPLICA
|
||||
bool SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config);
|
||||
|
||||
// as MAIN, define and connect to REPLICAs
|
||||
auto RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> utils::BasicResult<RegisterReplicaError>;
|
||||
|
||||
// as MAIN, remove a REPLICA connection
|
||||
auto UnregisterReplica(std::string_view name) -> UnregisterReplicaResult;
|
||||
|
||||
// Helper pass-through (TODO: remove)
|
||||
auto GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole;
|
||||
bool IsMain() const;
|
||||
bool IsReplica() const;
|
||||
|
||||
private:
|
||||
DbmsHandler &dbms_handler_;
|
||||
};
|
||||
|
||||
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
|
||||
/// TODO: extend to do multiple storages
|
||||
void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc);
|
||||
|
||||
namespace system_replication {
|
||||
// System handlers
|
||||
#ifdef MG_ENTERPRISE
|
||||
void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void SystemHeartbeatHandler(uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
#endif
|
||||
|
||||
/// Register all DBMS level RPC handlers
|
||||
void Register(replication::RoleReplicaData const &data, DbmsHandler &dbms_handler);
|
||||
} // namespace system_replication
|
||||
|
||||
bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data);
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
@@ -1,191 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "dbms/replication_handlers.hpp"
|
||||
|
||||
#include "dbms/database.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "system/state.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
void CreateDatabaseHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access,
|
||||
DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
using memgraph::storage::replication::CreateDatabaseRes;
|
||||
CreateDatabaseRes res(CreateDatabaseRes::Result::FAILURE);
|
||||
|
||||
// Ignore if no license
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
spdlog::error("Handling CreateDatabase, an enterprise RPC message, without license.");
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
memgraph::storage::replication::CreateDatabaseReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != system_state_access.LastCommitedTS()) {
|
||||
spdlog::debug("CreateDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
system_state_access.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create new
|
||||
auto new_db = dbms_handler.Update(req.config);
|
||||
if (new_db.HasValue()) {
|
||||
// Successfully create db
|
||||
system_state_access.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = CreateDatabaseRes(CreateDatabaseRes::Result::SUCCESS);
|
||||
spdlog::debug("CreateDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
}
|
||||
} catch (...) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void DropDatabaseHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access, DbmsHandler &dbms_handler,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
using memgraph::storage::replication::DropDatabaseRes;
|
||||
DropDatabaseRes res(DropDatabaseRes::Result::FAILURE);
|
||||
|
||||
// Ignore if no license
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
spdlog::error("Handling DropDatabase, an enterprise RPC message, without license.");
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
memgraph::storage::replication::DropDatabaseReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != system_state_access.LastCommitedTS()) {
|
||||
spdlog::debug("DropDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
system_state_access.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// NOTE: Single communication channel can exist at a time, no other database can be deleted/created at the moment.
|
||||
auto new_db = dbms_handler.Delete(req.uuid);
|
||||
if (new_db.HasError()) {
|
||||
if (new_db.GetError() == DeleteError::NON_EXISTENT) {
|
||||
// Nothing to drop
|
||||
system_state_access.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = DropDatabaseRes(DropDatabaseRes::Result::NO_NEED);
|
||||
}
|
||||
} else {
|
||||
// Successfully drop db
|
||||
system_state_access.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = DropDatabaseRes(DropDatabaseRes::Result::SUCCESS);
|
||||
spdlog::debug("DropDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
}
|
||||
} catch (...) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
bool SystemRecoveryHandler(DbmsHandler &dbms_handler, const std::vector<storage::SalientConfig> &database_configs) {
|
||||
/*
|
||||
* NO LICENSE
|
||||
*/
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
spdlog::error("Handling SystemRecovery, an enterprise RPC message, without license.");
|
||||
for (const auto &config : database_configs) {
|
||||
// Only handle default DB
|
||||
if (config.name != kDefaultDB) continue;
|
||||
try {
|
||||
if (dbms_handler.Update(config).HasError()) {
|
||||
return false;
|
||||
}
|
||||
} catch (const UnknownDatabaseException &) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* MULTI-TENANCY
|
||||
*/
|
||||
// Get all current dbs
|
||||
auto old = dbms_handler.All();
|
||||
// Check/create the incoming dbs
|
||||
for (const auto &config : database_configs) {
|
||||
// Missing db
|
||||
try {
|
||||
if (dbms_handler.Update(config).HasError()) {
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to update database \"{}\".", config.name);
|
||||
return false;
|
||||
}
|
||||
} catch (const UnknownDatabaseException &) {
|
||||
spdlog::debug("SystemRecoveryHandler: UnknownDatabaseException");
|
||||
return false;
|
||||
}
|
||||
const auto it = std::find(old.begin(), old.end(), config.name);
|
||||
if (it != old.end()) old.erase(it);
|
||||
}
|
||||
|
||||
// Delete all the leftover old dbs
|
||||
for (const auto &remove_db : old) {
|
||||
const auto del = dbms_handler.Delete(remove_db);
|
||||
if (del.HasError()) {
|
||||
// Some errors are not terminal
|
||||
if (del.GetError() == DeleteError::DEFAULT_DB || del.GetError() == DeleteError::NON_EXISTENT) {
|
||||
spdlog::debug("SystemRecoveryHandler: Dropped database \"{}\".", remove_db);
|
||||
continue;
|
||||
}
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to drop database \"{}\".", remove_db);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* SUCCESS
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
void Register(replication::RoleReplicaData const &data, system::ReplicaHandlerAccessToState &system_state_access,
|
||||
dbms::DbmsHandler &dbms_handler) {
|
||||
// NOTE: Register even without license as the user could add a license at run-time
|
||||
data.server->rpc_server_.Register<storage::replication::CreateDatabaseRpc>(
|
||||
[system_state_access, &dbms_handler](auto *req_reader, auto *res_builder) mutable {
|
||||
spdlog::debug("Received CreateDatabaseRpc");
|
||||
CreateDatabaseHandler(system_state_access, dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<storage::replication::DropDatabaseRpc>(
|
||||
[system_state_access, &dbms_handler](auto *req_reader, auto *res_builder) mutable {
|
||||
spdlog::debug("Received DropDatabaseRpc");
|
||||
DropDatabaseHandler(system_state_access, dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
} // namespace memgraph::dbms
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
#include "system/state.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
#ifdef MG_ENTERPRISE
|
||||
// RPC handlers
|
||||
void CreateDatabaseHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access,
|
||||
DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void DropDatabaseHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access, DbmsHandler &dbms_handler,
|
||||
slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
bool SystemRecoveryHandler(DbmsHandler &dbms_handler, const std::vector<storage::SalientConfig> &database_configs);
|
||||
|
||||
// RPC registration
|
||||
void Register(replication::RoleReplicaData const &data, system::ReplicaHandlerAccessToState &system_state_access,
|
||||
dbms::DbmsHandler &dbms_handler);
|
||||
#endif
|
||||
} // namespace memgraph::dbms
|
||||
118
src/dbms/rpc.cpp
118
src/dbms/rpc.cpp
@@ -1,118 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "dbms/rpc.hpp"
|
||||
|
||||
#include "slk/streams.hpp"
|
||||
#include "storage/v2/replication/rpc.hpp"
|
||||
#include "utils/enum.hpp"
|
||||
#include "utils/typeinfo.hpp"
|
||||
|
||||
namespace memgraph {
|
||||
|
||||
namespace storage::replication {
|
||||
|
||||
void CreateDatabaseReq::Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void CreateDatabaseReq::Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void CreateDatabaseRes::Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void CreateDatabaseRes::Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void DropDatabaseReq::Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void DropDatabaseReq::Load(DropDatabaseReq *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
void DropDatabaseRes::Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void DropDatabaseRes::Load(DropDatabaseRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
|
||||
const utils::TypeInfo CreateDatabaseReq::kType{utils::TypeId::REP_CREATE_DATABASE_REQ, "CreateDatabaseReq", nullptr};
|
||||
|
||||
const utils::TypeInfo CreateDatabaseRes::kType{utils::TypeId::REP_CREATE_DATABASE_RES, "CreateDatabaseRes", nullptr};
|
||||
|
||||
const utils::TypeInfo DropDatabaseReq::kType{utils::TypeId::REP_DROP_DATABASE_REQ, "DropDatabaseReq", nullptr};
|
||||
|
||||
const utils::TypeInfo DropDatabaseRes::kType{utils::TypeId::REP_DROP_DATABASE_RES, "DropDatabaseRes", nullptr};
|
||||
|
||||
} // namespace storage::replication
|
||||
|
||||
// Autogenerated SLK serialization code
|
||||
namespace slk {
|
||||
|
||||
// Serialize code for CreateDatabaseReq
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.config, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->config, reader);
|
||||
}
|
||||
|
||||
// Serialize code for CreateDatabaseRes
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader) {
|
||||
uint8_t res = 0;
|
||||
memgraph::slk::Load(&res, reader);
|
||||
if (!utils::NumToEnum(res, self->result)) {
|
||||
throw SlkReaderException("Unexpected result line:{}!", __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize code for DropDatabaseReq
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.uuid, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->uuid, reader);
|
||||
}
|
||||
|
||||
// Serialize code for DropDatabaseRes
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader) {
|
||||
uint8_t res = 0;
|
||||
memgraph::slk::Load(&res, reader);
|
||||
if (!utils::NumToEnum(res, self->result)) {
|
||||
throw SlkReaderException("Unexpected result line:{}!", __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace slk
|
||||
} // namespace memgraph
|
||||
118
src/dbms/rpc.hpp
118
src/dbms/rpc.hpp
@@ -1,118 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "rpc/messages.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
|
||||
namespace memgraph::storage::replication {
|
||||
|
||||
struct CreateDatabaseReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
CreateDatabaseReq() = default;
|
||||
CreateDatabaseReq(std::string_view epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp,
|
||||
storage::SalientConfig config)
|
||||
: epoch_id(std::string(epoch_id)),
|
||||
expected_group_timestamp{expected_group_timestamp},
|
||||
new_group_timestamp(new_group_timestamp),
|
||||
config(std::move(config)) {}
|
||||
|
||||
std::string epoch_id;
|
||||
uint64_t expected_group_timestamp;
|
||||
uint64_t new_group_timestamp;
|
||||
storage::SalientConfig config;
|
||||
};
|
||||
|
||||
struct CreateDatabaseRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
|
||||
|
||||
static void Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
CreateDatabaseRes() = default;
|
||||
explicit CreateDatabaseRes(Result res) : result(res) {}
|
||||
|
||||
Result result;
|
||||
};
|
||||
|
||||
using CreateDatabaseRpc = rpc::RequestResponse<CreateDatabaseReq, CreateDatabaseRes>;
|
||||
|
||||
struct DropDatabaseReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(DropDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
DropDatabaseReq() = default;
|
||||
DropDatabaseReq(std::string_view epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp,
|
||||
const utils::UUID &uuid)
|
||||
: epoch_id(std::string(epoch_id)),
|
||||
expected_group_timestamp{expected_group_timestamp},
|
||||
new_group_timestamp(new_group_timestamp),
|
||||
uuid(uuid) {}
|
||||
|
||||
std::string epoch_id;
|
||||
uint64_t expected_group_timestamp;
|
||||
uint64_t new_group_timestamp;
|
||||
utils::UUID uuid;
|
||||
};
|
||||
|
||||
struct DropDatabaseRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
|
||||
|
||||
static void Load(DropDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
DropDatabaseRes() = default;
|
||||
explicit DropDatabaseRes(Result res) : result(res) {}
|
||||
|
||||
Result result;
|
||||
};
|
||||
|
||||
using DropDatabaseRpc = rpc::RequestResponse<DropDatabaseReq, DropDatabaseRes>;
|
||||
|
||||
} // namespace memgraph::storage::replication
|
||||
|
||||
// SLK serialization declarations
|
||||
namespace memgraph::slk {
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
} // namespace memgraph::slk
|
||||
@@ -11,10 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include "auth/models.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
@@ -23,70 +20,17 @@ struct SystemTransaction {
|
||||
enum class Action {
|
||||
CREATE_DATABASE,
|
||||
DROP_DATABASE,
|
||||
UPDATE_AUTH_DATA,
|
||||
DROP_AUTH_DATA,
|
||||
/**
|
||||
*
|
||||
* CREATE USER user_name [IDENTIFIED BY 'password'];
|
||||
* SET PASSWORD FOR user_name TO 'new_password';
|
||||
* ^ SaveUser
|
||||
*
|
||||
* DROP USER user_name;
|
||||
* ^ Directly on KVStore
|
||||
*
|
||||
* CREATE ROLE role_name;
|
||||
* ^ SaveRole
|
||||
*
|
||||
* DROP ROLE
|
||||
* ^ RemoveRole
|
||||
*
|
||||
* SET ROLE FOR user_name TO role_name;
|
||||
* CLEAR ROLE FOR user_name;
|
||||
* ^ Do stuff then do SaveUser
|
||||
*
|
||||
* GRANT privilege_list TO user_or_role;
|
||||
* DENY AUTH, INDEX TO moderator:
|
||||
* REVOKE AUTH, INDEX TO moderator:
|
||||
* GRANT permission_level ON (LABELS | EDGE_TYPES) label_list TO user_or_role;
|
||||
* REVOKE (LABELS | EDGE_TYPES) label_or_edge_type_list FROM user_or_role
|
||||
* DENY (LABELS | EDGE_TYPES) label_or_edge_type_list TO user_or_role
|
||||
* ^ all of these are EditPermissions <-> SaveUser/Role
|
||||
*
|
||||
* Multi-tenant TODO Doc;
|
||||
* ^ Should all call SaveUser
|
||||
*
|
||||
*/
|
||||
};
|
||||
|
||||
static constexpr struct CreateDatabase {
|
||||
} create_database;
|
||||
static constexpr struct DropDatabase {
|
||||
} drop_database;
|
||||
static constexpr struct UpdateAuthData {
|
||||
} update_auth_data;
|
||||
static constexpr struct DropAuthData {
|
||||
} drop_auth_data;
|
||||
|
||||
enum class AuthData { USER, ROLE };
|
||||
|
||||
// Multi-tenancy
|
||||
Delta(CreateDatabase /*tag*/, storage::SalientConfig config)
|
||||
: action(Action::CREATE_DATABASE), config(std::move(config)) {}
|
||||
Delta(DropDatabase /*tag*/, const utils::UUID &uuid) : action(Action::DROP_DATABASE), uuid(uuid) {}
|
||||
|
||||
// Auth
|
||||
Delta(UpdateAuthData /*tag*/, std::optional<auth::User> user)
|
||||
: action(Action::UPDATE_AUTH_DATA), auth_data{std::move(user), std::nullopt} {}
|
||||
Delta(UpdateAuthData /*tag*/, std::optional<auth::Role> role)
|
||||
: action(Action::UPDATE_AUTH_DATA), auth_data{std::nullopt, std::move(role)} {}
|
||||
Delta(DropAuthData /*tag*/, AuthData type, std::string_view name)
|
||||
: action(Action::DROP_AUTH_DATA),
|
||||
auth_data_key{
|
||||
.type = type,
|
||||
.name = std::string{name},
|
||||
} {}
|
||||
|
||||
// Generic
|
||||
Delta(const Delta &) = delete;
|
||||
Delta(Delta &&) = delete;
|
||||
Delta &operator=(const Delta &) = delete;
|
||||
@@ -98,14 +42,8 @@ struct SystemTransaction {
|
||||
std::destroy_at(&config);
|
||||
break;
|
||||
case Action::DROP_DATABASE:
|
||||
std::destroy_at(&uuid);
|
||||
break;
|
||||
case Action::UPDATE_AUTH_DATA:
|
||||
std::destroy_at(&auth_data);
|
||||
break;
|
||||
case Action::DROP_AUTH_DATA:
|
||||
std::destroy_at(&auth_data_key);
|
||||
break;
|
||||
// Some deltas might have special destructor handling
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,20 +51,13 @@ struct SystemTransaction {
|
||||
union {
|
||||
storage::SalientConfig config;
|
||||
utils::UUID uuid;
|
||||
struct {
|
||||
std::optional<auth::User> user;
|
||||
std::optional<auth::Role> role;
|
||||
} auth_data;
|
||||
struct {
|
||||
AuthData type;
|
||||
std::string name;
|
||||
} auth_data_key;
|
||||
};
|
||||
};
|
||||
|
||||
explicit SystemTransaction(uint64_t timestamp) : system_timestamp(timestamp) {}
|
||||
|
||||
std::list<Delta> deltas{};
|
||||
// Currently system transitions support a single delta
|
||||
std::optional<Delta> delta{};
|
||||
uint64_t system_timestamp;
|
||||
};
|
||||
|
||||
|
||||
133
src/dbms/utils.hpp
Normal file
133
src/dbms/utils.hpp
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
#pragma once
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "dbms/replication_handler.hpp"
|
||||
#include "replication/include/replication/state.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
inline bool DoReplicaToMainPromotion(dbms::DbmsHandler &dbms_handler) {
|
||||
auto &repl_state = dbms_handler.ReplicationState();
|
||||
// STEP 1) bring down all REPLICA servers
|
||||
dbms_handler.ForEach([](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
// Remember old epoch + storage timestamp association
|
||||
storage->PrepareForNewEpoch();
|
||||
});
|
||||
|
||||
// STEP 2) Change to MAIN
|
||||
// TODO: restore replication servers if false?
|
||||
if (!repl_state.SetReplicationRoleMain()) {
|
||||
// TODO: Handle recovery on failure???
|
||||
return false;
|
||||
}
|
||||
|
||||
// STEP 3) We are now MAIN, update storage local epoch
|
||||
const auto &epoch =
|
||||
std::get<replication::RoleMainData>(std::as_const(dbms_handler.ReplicationState()).ReplicationData()).epoch_;
|
||||
dbms_handler.ForEach([&](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
storage->repl_storage_state_.epoch_ = epoch;
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
inline bool SetReplicationRoleReplica(dbms::DbmsHandler &dbms_handler,
|
||||
const memgraph::replication::ReplicationServerConfig &config) {
|
||||
if (dbms_handler.ReplicationState().IsReplica()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are
|
||||
// deleting the replica.
|
||||
// Remove database specific clients
|
||||
dbms_handler.ForEach([&](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
|
||||
});
|
||||
// Remove instance level clients
|
||||
std::get<replication::RoleMainData>(dbms_handler.ReplicationState().ReplicationData()).registered_replicas_.clear();
|
||||
|
||||
// Creates the server
|
||||
dbms_handler.ReplicationState().SetReplicationRoleReplica(config);
|
||||
|
||||
// Start
|
||||
const auto success = std::visit(utils::Overloaded{[](replication::RoleMainData const &) {
|
||||
// ASSERT
|
||||
return false;
|
||||
},
|
||||
[&dbms_handler](replication::RoleReplicaData const &data) {
|
||||
return StartRpcServer(dbms_handler, data);
|
||||
}},
|
||||
dbms_handler.ReplicationState().ReplicationData());
|
||||
// TODO Handle error (restore to main?)
|
||||
return success;
|
||||
}
|
||||
|
||||
inline bool RegisterAllDatabasesClients(dbms::DbmsHandler &dbms_handler,
|
||||
replication::ReplicationClient &instance_client) {
|
||||
if (!allow_mt_repl && dbms_handler.All().size() > 1) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
|
||||
bool all_clients_good = true;
|
||||
|
||||
// Add database specific clients (NOTE Currently all databases are connected to each replica)
|
||||
dbms_handler.ForEach([&](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
if (!allow_mt_repl && storage->name() != kDefaultDB) {
|
||||
return;
|
||||
}
|
||||
// TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes
|
||||
if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return;
|
||||
|
||||
all_clients_good &= storage->repl_storage_state_.replication_clients_.WithLock(
|
||||
[storage, &instance_client, db_acc = std::move(db_acc)](auto &storage_clients) mutable { // NOLINT
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client);
|
||||
// All good, start replica client
|
||||
client->Start(storage, std::move(db_acc));
|
||||
// After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
|
||||
// MAYBE_BEHIND isn't a statement of the current state, this is the default value
|
||||
// Failed to start due an error like branching of MAIN and REPLICA
|
||||
if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
return false; // TODO: sometimes we need to still add to storage_clients
|
||||
}
|
||||
storage_clients.push_back(std::move(client));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return all_clients_good;
|
||||
}
|
||||
|
||||
inline std::optional<RegisterReplicaError> HandleRegisterReplicaStatus(
|
||||
utils::BasicResult<replication::RegisterReplicaError, replication::ReplicationClient *> &instance_client) {
|
||||
if (instance_client.HasError()) switch (instance_client.GetError()) {
|
||||
case replication::RegisterReplicaError::NOT_MAIN:
|
||||
MG_ASSERT(false, "Only main instance can register a replica!");
|
||||
return {};
|
||||
case replication::RegisterReplicaError::NAME_EXISTS:
|
||||
return dbms::RegisterReplicaError::NAME_EXISTS;
|
||||
case replication::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
return dbms::RegisterReplicaError::ENDPOINT_EXISTS;
|
||||
case replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
@@ -195,9 +195,3 @@ DEFINE_HIDDEN_string(organization_name, "", "Organization name.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(),
|
||||
"Set to the regular expression that each user or role name must fulfill.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(auth_password_strength_regex, memgraph::glue::kDefaultPasswordRegex.data(),
|
||||
"The regular expression that should be used to match the entire "
|
||||
"entered password to ensure its strength.");
|
||||
|
||||
@@ -118,7 +118,3 @@ DECLARE_string(license_key);
|
||||
DECLARE_string(organization_name);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_string(auth_user_or_role_name_regex);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_bool(auth_password_permit_null);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_string(auth_password_strength_regex);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -33,7 +33,7 @@ class WritePrioritizedRWLock;
|
||||
|
||||
struct Context {
|
||||
memgraph::query::InterpreterContext *ic;
|
||||
memgraph::auth::SynchedAuth *auth;
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth;
|
||||
#if MG_ENTERPRISE
|
||||
memgraph::audit::Log *audit_log;
|
||||
#endif
|
||||
|
||||
@@ -319,7 +319,8 @@ void SessionHL::Configure(const std::map<std::string, memgraph::communication::b
|
||||
SessionHL::SessionHL(memgraph::query::InterpreterContext *interpreter_context,
|
||||
memgraph::communication::v2::ServerEndpoint endpoint,
|
||||
memgraph::communication::v2::InputStream *input_stream,
|
||||
memgraph::communication::v2::OutputStream *output_stream, memgraph::auth::SynchedAuth *auth
|
||||
memgraph::communication::v2::OutputStream *output_stream,
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
memgraph::audit::Log *audit_log
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -25,7 +25,8 @@ class SessionHL final : public memgraph::communication::bolt::Session<memgraph::
|
||||
SessionHL(memgraph::query::InterpreterContext *interpreter_context,
|
||||
memgraph::communication::v2::ServerEndpoint endpoint,
|
||||
memgraph::communication::v2::InputStream *input_stream,
|
||||
memgraph::communication::v2::OutputStream *output_stream, memgraph::auth::SynchedAuth *auth
|
||||
memgraph::communication::v2::OutputStream *output_stream,
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
memgraph::audit::Log *audit_log
|
||||
@@ -87,7 +88,7 @@ class SessionHL final : public memgraph::communication::bolt::Session<memgraph::
|
||||
memgraph::audit::Log *audit_log_;
|
||||
bool in_explicit_db_{false}; //!< If true, the user has defined the database to use via metadata
|
||||
#endif
|
||||
memgraph::auth::SynchedAuth *auth_;
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
memgraph::communication::v2::ServerEndpoint endpoint_;
|
||||
std::optional<std::string> implicit_db_;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -66,7 +66,9 @@ bool IsUserAuthorizedEdgeType(const memgraph::auth::User &user, const memgraph::
|
||||
#endif
|
||||
namespace memgraph::glue {
|
||||
|
||||
AuthChecker::AuthChecker(memgraph::auth::SynchedAuth *auth) : auth_(auth) {}
|
||||
AuthChecker::AuthChecker(
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
|
||||
: auth_(auth) {}
|
||||
|
||||
bool AuthChecker::IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -22,7 +22,8 @@ namespace memgraph::glue {
|
||||
|
||||
class AuthChecker : public query::AuthChecker {
|
||||
public:
|
||||
explicit AuthChecker(memgraph::auth::SynchedAuth *auth);
|
||||
explicit AuthChecker(
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth);
|
||||
|
||||
bool IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<query::AuthQuery::Privilege> &privileges,
|
||||
@@ -40,7 +41,7 @@ class AuthChecker : public query::AuthChecker {
|
||||
const std::string &db_name = "");
|
||||
|
||||
private:
|
||||
memgraph::auth::SynchedAuth *auth_;
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
mutable memgraph::utils::Synchronized<auth::User, memgraph::utils::SpinLock> user_; // cached user
|
||||
};
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -13,5 +13,4 @@
|
||||
|
||||
namespace memgraph::glue {
|
||||
inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+";
|
||||
static constexpr std::string_view kDefaultPasswordRegex = ".+";
|
||||
} // namespace memgraph::glue
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -210,25 +210,16 @@ std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedUserPrivile
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return {};
|
||||
}
|
||||
const auto &label_permissions = user->GetFineGrainedAccessLabelPermissions();
|
||||
const auto &edge_type_permissions = user->GetFineGrainedAccessEdgeTypePermissions();
|
||||
|
||||
auto all_fine_grained_permissions = GetFineGrainedPermissionForPrivilegeForUserOrRole(
|
||||
user->GetUserFineGrainedAccessLabelPermissions(), "LABEL", "USER");
|
||||
auto all_role_fine_grained_permissions = GetFineGrainedPermissionForPrivilegeForUserOrRole(
|
||||
user->GetRoleFineGrainedAccessLabelPermissions(), "LABEL", "ROLE");
|
||||
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(),
|
||||
std::make_move_iterator(all_role_fine_grained_permissions.begin()),
|
||||
std::make_move_iterator(all_role_fine_grained_permissions.end()));
|
||||
auto all_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER");
|
||||
auto edge_type_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER");
|
||||
|
||||
auto edge_type_fine_grained_permissions = GetFineGrainedPermissionForPrivilegeForUserOrRole(
|
||||
user->GetUserFineGrainedAccessEdgeTypePermissions(), "EDGE_TYPE", "USER");
|
||||
auto role_edge_type_fine_grained_permissions = GetFineGrainedPermissionForPrivilegeForUserOrRole(
|
||||
user->GetRoleFineGrainedAccessEdgeTypePermissions(), "EDGE_TYPE", "ROLE");
|
||||
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(),
|
||||
std::make_move_iterator(edge_type_fine_grained_permissions.begin()),
|
||||
std::make_move_iterator(edge_type_fine_grained_permissions.end()));
|
||||
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(),
|
||||
std::make_move_iterator(role_edge_type_fine_grained_permissions.begin()),
|
||||
std::make_move_iterator(role_edge_type_fine_grained_permissions.end()));
|
||||
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(),
|
||||
edge_type_fine_grained_permissions.end());
|
||||
|
||||
return ConstructFineGrainedPrivilegesResult(all_fine_grained_permissions);
|
||||
}
|
||||
@@ -242,9 +233,9 @@ std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivile
|
||||
const auto &edge_type_permissions = role->GetFineGrainedAccessEdgeTypePermissions();
|
||||
|
||||
auto all_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "ROLE");
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER");
|
||||
auto edge_type_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "ROLE");
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER");
|
||||
|
||||
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(),
|
||||
edge_type_fine_grained_permissions.end());
|
||||
@@ -257,15 +248,31 @@ std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivile
|
||||
|
||||
namespace memgraph::glue {
|
||||
|
||||
AuthQueryHandler::AuthQueryHandler(memgraph::auth::SynchedAuth *auth) : auth_(auth) {}
|
||||
AuthQueryHandler::AuthQueryHandler(
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
std::string name_regex_string)
|
||||
: auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {}
|
||||
|
||||
bool AuthQueryHandler::CreateUser(const std::string &username, const std::optional<std::string> &password,
|
||||
system::Transaction *system_tx) {
|
||||
bool AuthQueryHandler::CreateUser(const std::string &username, const std::optional<std::string> &password) {
|
||||
if (name_regex_string_ != kDefaultUserRoleRegex) {
|
||||
if (const auto license_check_result =
|
||||
memgraph::license::global_license_checker.IsEnterpriseValid(memgraph::utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw memgraph::auth::AuthException(
|
||||
"Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
kDefaultUserRoleRegex,
|
||||
memgraph::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
|
||||
}
|
||||
}
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
const auto [first_user, user_added] = std::invoke([&, this] {
|
||||
auto locked_auth = auth_->Lock();
|
||||
const auto first_user = !locked_auth->HasUsers();
|
||||
const auto user_added = locked_auth->AddUser(username, password, system_tx).has_value();
|
||||
const auto user_added = locked_auth->AddUser(username, password).has_value();
|
||||
return std::make_pair(first_user, user_added);
|
||||
});
|
||||
|
||||
@@ -284,11 +291,10 @@ bool AuthQueryHandler::CreateUser(const std::string &username, const std::option
|
||||
}
|
||||
}
|
||||
#endif
|
||||
,
|
||||
system_tx);
|
||||
);
|
||||
#ifdef MG_ENTERPRISE
|
||||
GrantDatabaseToUser(auth::kAllDatabases, username, system_tx);
|
||||
SetMainDatabase(dbms::kDefaultDB, username, system_tx);
|
||||
GrantDatabaseToUser(auth::kAllDatabases, username);
|
||||
SetMainDatabase(dbms::kDefaultDB, username);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -298,61 +304,73 @@ bool AuthQueryHandler::CreateUser(const std::string &username, const std::option
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::DropUser(const std::string &username, system::Transaction *system_tx) {
|
||||
bool AuthQueryHandler::DropUser(const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) return false;
|
||||
return locked_auth->RemoveUser(username, system_tx);
|
||||
return locked_auth->RemoveUser(username);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::SetPassword(const std::string &username, const std::optional<std::string> &password,
|
||||
system::Transaction *system_tx) {
|
||||
void AuthQueryHandler::SetPassword(const std::string &username, const std::optional<std::string> &password) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username);
|
||||
}
|
||||
locked_auth->UpdatePassword(*user, password);
|
||||
locked_auth->SaveUser(*user, system_tx);
|
||||
user->UpdatePassword(password);
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::CreateRole(const std::string &rolename, system::Transaction *system_tx) {
|
||||
bool AuthQueryHandler::CreateRole(const std::string &rolename) {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
return locked_auth->AddRole(rolename, system_tx).has_value();
|
||||
return locked_auth->AddRole(rolename).has_value();
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
bool AuthQueryHandler::RevokeDatabaseFromUser(const std::string &db_name, const std::string &username,
|
||||
system::Transaction *system_tx) {
|
||||
bool AuthQueryHandler::RevokeDatabaseFromUser(const std::string &db, const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) return false;
|
||||
return locked_auth->RevokeDatabaseFromUser(db_name, username, system_tx);
|
||||
return locked_auth->RevokeDatabaseFromUser(db, username);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db_name, const std::string &username,
|
||||
system::Transaction *system_tx) {
|
||||
bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db, const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) return false;
|
||||
return locked_auth->GrantDatabaseToUser(db_name, username, system_tx);
|
||||
return locked_auth->GrantDatabaseToUser(db, username);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
@@ -360,6 +378,9 @@ bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db_name, const std
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatabasePrivileges(
|
||||
const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
@@ -372,28 +393,33 @@ std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatab
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::SetMainDatabase(std::string_view db_name, const std::string &username,
|
||||
system::Transaction *system_tx) {
|
||||
bool AuthQueryHandler::SetMainDatabase(std::string_view db, const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) return false;
|
||||
return locked_auth->SetMainDatabase(db_name, username, system_tx);
|
||||
return locked_auth->SetMainDatabase(db, username);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::DeleteDatabase(std::string_view db_name, system::Transaction *system_tx) {
|
||||
void AuthQueryHandler::DeleteDatabase(std::string_view db) {
|
||||
try {
|
||||
auth_->Lock()->DeleteDatabase(std::string(db_name), system_tx);
|
||||
auth_->Lock()->DeleteDatabase(std::string(db));
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AuthQueryHandler::DropRole(const std::string &rolename, system::Transaction *system_tx) {
|
||||
bool AuthQueryHandler::DropRole(const std::string &rolename) {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
@@ -402,7 +428,7 @@ bool AuthQueryHandler::DropRole(const std::string &rolename, system::Transaction
|
||||
return false;
|
||||
};
|
||||
|
||||
return locked_auth->RemoveRole(rolename, system_tx);
|
||||
return locked_auth->RemoveRole(rolename);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
@@ -439,6 +465,9 @@ std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetRolenames() {
|
||||
}
|
||||
|
||||
std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
@@ -456,6 +485,9 @@ std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::strin
|
||||
}
|
||||
|
||||
std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(const std::string &rolename) {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
@@ -474,8 +506,13 @@ std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(c
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::SetRole(const std::string &username, const std::string &rolename,
|
||||
system::Transaction *system_tx) {
|
||||
void AuthQueryHandler::SetRole(const std::string &username, const std::string &rolename) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
@@ -491,13 +528,16 @@ void AuthQueryHandler::SetRole(const std::string &username, const std::string &r
|
||||
current_role->rolename());
|
||||
}
|
||||
user->SetRole(*role);
|
||||
locked_auth->SaveUser(*user, system_tx);
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::ClearRole(const std::string &username, system::Transaction *system_tx) {
|
||||
void AuthQueryHandler::ClearRole(const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
@@ -505,13 +545,16 @@ void AuthQueryHandler::ClearRole(const std::string &username, system::Transactio
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
user->ClearRole();
|
||||
locked_auth->SaveUser(*user, system_tx);
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetPrivileges(const std::string &user_or_role) {
|
||||
if (!std::regex_match(user_or_role, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> grants;
|
||||
@@ -559,8 +602,7 @@ void AuthQueryHandler::GrantPrivilege(
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx) {
|
||||
) {
|
||||
EditPermissions(
|
||||
user_or_role, privileges,
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -583,13 +625,11 @@ void AuthQueryHandler::GrantPrivilege(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
,
|
||||
system_tx);
|
||||
);
|
||||
} // namespace memgraph::glue
|
||||
|
||||
void AuthQueryHandler::DenyPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
system::Transaction *system_tx) {
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
|
||||
EditPermissions(
|
||||
user_or_role, privileges,
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -605,8 +645,7 @@ void AuthQueryHandler::DenyPrivilege(const std::string &user_or_role,
|
||||
,
|
||||
[](auto &fine_grained_permissions, const auto &privilege_collection) {}
|
||||
#endif
|
||||
,
|
||||
system_tx);
|
||||
);
|
||||
}
|
||||
|
||||
void AuthQueryHandler::RevokePrivilege(
|
||||
@@ -618,8 +657,7 @@ void AuthQueryHandler::RevokePrivilege(
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx) {
|
||||
) {
|
||||
EditPermissions(
|
||||
user_or_role, privileges,
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -641,8 +679,7 @@ void AuthQueryHandler::RevokePrivilege(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
,
|
||||
system_tx);
|
||||
);
|
||||
} // namespace memgraph::glue
|
||||
|
||||
template <class TEditPermissionsFun
|
||||
@@ -666,8 +703,10 @@ void AuthQueryHandler::EditPermissions(
|
||||
,
|
||||
const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx) {
|
||||
) {
|
||||
if (!std::regex_match(user_or_role, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
std::vector<memgraph::auth::Permission> permissions;
|
||||
permissions.reserve(privileges.size());
|
||||
@@ -696,7 +735,7 @@ void AuthQueryHandler::EditPermissions(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
locked_auth->SaveUser(*user, system_tx);
|
||||
locked_auth->SaveUser(*user);
|
||||
} else {
|
||||
for (const auto &permission : permissions) {
|
||||
edit_permissions_fun(role->permissions(), permission);
|
||||
@@ -712,7 +751,7 @@ void AuthQueryHandler::EditPermissions(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
locked_auth->SaveRole(*role, system_tx);
|
||||
locked_auth->SaveRole(*role);
|
||||
}
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -23,36 +23,35 @@
|
||||
namespace memgraph::glue {
|
||||
|
||||
class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
memgraph::auth::SynchedAuth *auth_;
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
std::string name_regex_string_;
|
||||
std::regex name_regex_;
|
||||
|
||||
public:
|
||||
explicit AuthQueryHandler(memgraph::auth::SynchedAuth *auth);
|
||||
AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
std::string name_regex_string);
|
||||
|
||||
bool CreateUser(const std::string &username, const std::optional<std::string> &password,
|
||||
system::Transaction *system_tx) override;
|
||||
bool CreateUser(const std::string &username, const std::optional<std::string> &password) override;
|
||||
|
||||
bool DropUser(const std::string &username, system::Transaction *system_tx) override;
|
||||
bool DropUser(const std::string &username) override;
|
||||
|
||||
void SetPassword(const std::string &username, const std::optional<std::string> &password,
|
||||
system::Transaction *system_tx) override;
|
||||
void SetPassword(const std::string &username, const std::optional<std::string> &password) override;
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
bool RevokeDatabaseFromUser(const std::string &db_name, const std::string &username,
|
||||
system::Transaction *system_tx) override;
|
||||
bool RevokeDatabaseFromUser(const std::string &db, const std::string &username) override;
|
||||
|
||||
bool GrantDatabaseToUser(const std::string &db_name, const std::string &username,
|
||||
system::Transaction *system_tx) override;
|
||||
bool GrantDatabaseToUser(const std::string &db, const std::string &username) override;
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> GetDatabasePrivileges(const std::string &username) override;
|
||||
|
||||
bool SetMainDatabase(std::string_view db_name, const std::string &username, system::Transaction *system_tx) override;
|
||||
bool SetMainDatabase(std::string_view db, const std::string &username) override;
|
||||
|
||||
void DeleteDatabase(std::string_view db_name, system::Transaction *system_tx) override;
|
||||
void DeleteDatabase(std::string_view db) override;
|
||||
#endif
|
||||
|
||||
bool CreateRole(const std::string &rolename, system::Transaction *system_tx) override;
|
||||
bool CreateRole(const std::string &rolename) override;
|
||||
|
||||
bool DropRole(const std::string &rolename, system::Transaction *system_tx) override;
|
||||
bool DropRole(const std::string &rolename) override;
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetUsernames() override;
|
||||
|
||||
@@ -62,9 +61,9 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) override;
|
||||
|
||||
void SetRole(const std::string &username, const std::string &rolename, system::Transaction *system_tx) override;
|
||||
void SetRole(const std::string &username, const std::string &rolename) override;
|
||||
|
||||
void ClearRole(const std::string &username, system::Transaction *system_tx) override;
|
||||
void ClearRole(const std::string &username) override;
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) override;
|
||||
|
||||
@@ -78,12 +77,10 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx) override;
|
||||
) override;
|
||||
|
||||
void DenyPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
system::Transaction *system_tx) override;
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override;
|
||||
|
||||
void RevokePrivilege(
|
||||
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
|
||||
@@ -94,8 +91,7 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx) override;
|
||||
) override;
|
||||
|
||||
private:
|
||||
template <class TEditPermissionsFun
|
||||
@@ -119,7 +115,6 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
,
|
||||
const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx);
|
||||
);
|
||||
};
|
||||
} // namespace memgraph::glue
|
||||
|
||||
@@ -37,7 +37,6 @@ KVStore::KVStore(std::filesystem::path storage) : pimpl_(std::make_unique<impl>(
|
||||
}
|
||||
|
||||
KVStore::~KVStore() {
|
||||
if (pimpl_ == nullptr) return;
|
||||
spdlog::debug("Destroying KVStore at {}", pimpl_->storage.string());
|
||||
const auto sync = pimpl_->db->SyncWAL();
|
||||
if (!sync.ok()) spdlog::error("KVStore sync failed!");
|
||||
|
||||
101
src/memgraph.cpp
101
src/memgraph.cpp
@@ -11,12 +11,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include "audit/log.hpp"
|
||||
#include "auth/auth.hpp"
|
||||
#include "communication/websocket/auth.hpp"
|
||||
#include "communication/websocket/server.hpp"
|
||||
#include "coordination/coordinator_handlers.hpp"
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "dbms/inmemory/replication_handlers.hpp"
|
||||
#include "flags/all.hpp"
|
||||
#include "glue/MonitoringServerT.hpp"
|
||||
@@ -27,19 +24,14 @@
|
||||
#include "helpers.hpp"
|
||||
#include "license/license_sender.hpp"
|
||||
#include "memory/global_memory_control.hpp"
|
||||
#include "query/auth_query_handler.hpp"
|
||||
#include "query/config.hpp"
|
||||
#include "query/discard_value_stream.hpp"
|
||||
#include "query/interpreter.hpp"
|
||||
#include "query/interpreter_context.hpp"
|
||||
#include "query/procedure/callable_alias_mapper.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "query/procedure/py_module.hpp"
|
||||
#include "replication_handler/replication_handler.hpp"
|
||||
#include "replication_handler/system_replication.hpp"
|
||||
#include "requests/requests.hpp"
|
||||
#include "storage/v2/durability/durability.hpp"
|
||||
#include "system/system.hpp"
|
||||
#include "telemetry/telemetry.hpp"
|
||||
#include "utils/signals.hpp"
|
||||
#include "utils/sysinfo/memory.hpp"
|
||||
@@ -47,6 +39,10 @@
|
||||
#include "utils/terminate_handler.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "query/auth_query_handler.hpp"
|
||||
#include "query/interpreter_context.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr const char *kMgUser = "MEMGRAPH_USER";
|
||||
constexpr const char *kMgPassword = "MEMGRAPH_PASSWORD";
|
||||
@@ -360,75 +356,44 @@ int main(int argc, char **argv) {
|
||||
.stream_transaction_conflict_retries = FLAGS_stream_transaction_conflict_retries,
|
||||
.stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)};
|
||||
|
||||
auto auth_glue = [](memgraph::auth::SynchedAuth *auth, std::unique_ptr<memgraph::query::AuthQueryHandler> &ah,
|
||||
std::unique_ptr<memgraph::query::AuthChecker> &ac) {
|
||||
// Glue high level auth implementations to the query side
|
||||
ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth);
|
||||
ac = std::make_unique<memgraph::glue::AuthChecker>(auth);
|
||||
// Handle users passed via arguments
|
||||
auto *maybe_username = std::getenv(kMgUser);
|
||||
auto *maybe_password = std::getenv(kMgPassword);
|
||||
auto *maybe_pass_file = std::getenv(kMgPassfile);
|
||||
if (maybe_username && maybe_password) {
|
||||
ah->CreateUser(maybe_username, maybe_password, nullptr);
|
||||
} else if (maybe_pass_file) {
|
||||
const auto [username, password] = LoadUsernameAndPassword(maybe_pass_file);
|
||||
if (!username.empty() && !password.empty()) {
|
||||
ah->CreateUser(username, password, nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
auto auth_glue =
|
||||
[flag = FLAGS_auth_user_or_role_name_regex](
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
std::unique_ptr<memgraph::query::AuthQueryHandler> &ah, std::unique_ptr<memgraph::query::AuthChecker> &ac) {
|
||||
// Glue high level auth implementations to the query side
|
||||
ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth, flag);
|
||||
ac = std::make_unique<memgraph::glue::AuthChecker>(auth);
|
||||
// Handle users passed via arguments
|
||||
auto *maybe_username = std::getenv(kMgUser);
|
||||
auto *maybe_password = std::getenv(kMgPassword);
|
||||
auto *maybe_pass_file = std::getenv(kMgPassfile);
|
||||
if (maybe_username && maybe_password) {
|
||||
ah->CreateUser(maybe_username, maybe_password);
|
||||
} else if (maybe_pass_file) {
|
||||
const auto [username, password] = LoadUsernameAndPassword(maybe_pass_file);
|
||||
if (!username.empty() && !password.empty()) {
|
||||
ah->CreateUser(username, password);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
memgraph::auth::Auth::Config auth_config{FLAGS_auth_user_or_role_name_regex, FLAGS_auth_password_strength_regex,
|
||||
FLAGS_auth_password_permit_null};
|
||||
memgraph::auth::SynchedAuth auth_{data_directory / "auth", auth_config};
|
||||
// WIP
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{data_directory /
|
||||
"auth"};
|
||||
std::unique_ptr<memgraph::query::AuthQueryHandler> auth_handler;
|
||||
std::unique_ptr<memgraph::query::AuthChecker> auth_checker;
|
||||
auth_glue(&auth_, auth_handler, auth_checker);
|
||||
|
||||
auto system = memgraph::system::System{db_config.durability.storage_directory, FLAGS_data_recovery_on_startup};
|
||||
|
||||
// singleton replication state
|
||||
memgraph::replication::ReplicationState repl_state{ReplicationStateRootPath(db_config)};
|
||||
|
||||
// singleton coordinator state
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::coordination::CoordinatorState coordinator_state;
|
||||
#endif
|
||||
|
||||
memgraph::dbms::DbmsHandler dbms_handler(db_config, system, repl_state
|
||||
memgraph::dbms::DbmsHandler dbms_handler(db_config
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
auth_, FLAGS_data_recovery_on_startup
|
||||
&auth_, FLAGS_data_recovery_on_startup
|
||||
#endif
|
||||
);
|
||||
|
||||
// Note: Now that all system's subsystems are initialised (dbms & auth)
|
||||
// We can now initialise the recovery of replication (which will include those subsystems)
|
||||
// ReplicationHandler will handle the recovery
|
||||
auto replication_handler = memgraph::replication::ReplicationHandler{repl_state, dbms_handler
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
&system, auth_
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// MAIN or REPLICA instance
|
||||
if (FLAGS_coordinator_server_port) {
|
||||
memgraph::dbms::CoordinatorHandlers::Register(coordinator_state.GetCoordinatorServer(), replication_handler);
|
||||
MG_ASSERT(coordinator_state.GetCoordinatorServer().Start(), "Failed to start coordinator server!");
|
||||
}
|
||||
#endif
|
||||
|
||||
auto db_acc = dbms_handler.Get();
|
||||
|
||||
memgraph::query::InterpreterContext interpreter_context_(interp_config, &dbms_handler, &repl_state, system,
|
||||
#ifdef MG_ENTERPRISE
|
||||
&coordinator_state,
|
||||
#endif
|
||||
auth_handler.get(), auth_checker.get(),
|
||||
&replication_handler);
|
||||
memgraph::query::InterpreterContext interpreter_context_(
|
||||
interp_config, &dbms_handler, &dbms_handler.ReplicationState(), auth_handler.get(), auth_checker.get());
|
||||
MG_ASSERT(db_acc, "Failed to access the main database");
|
||||
|
||||
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(memgraph::flags::ParseQueryModulesDirectory(),
|
||||
@@ -495,9 +460,9 @@ int main(int argc, char **argv) {
|
||||
if (FLAGS_telemetry_enabled) {
|
||||
telemetry.emplace(telemetry_server, data_directory / "telemetry", memgraph::glue::run_id_, machine_id,
|
||||
service_name == "BoltS", FLAGS_data_directory, std::chrono::minutes(10));
|
||||
telemetry->AddStorageCollector(dbms_handler, auth_, repl_state);
|
||||
telemetry->AddStorageCollector(dbms_handler, auth_);
|
||||
#ifdef MG_ENTERPRISE
|
||||
telemetry->AddDatabaseCollector(dbms_handler, repl_state);
|
||||
telemetry->AddDatabaseCollector(dbms_handler);
|
||||
#else
|
||||
telemetry->AddDatabaseCollector();
|
||||
#endif
|
||||
|
||||
@@ -56,7 +56,6 @@ target_link_libraries(mg-query PUBLIC dl
|
||||
mg-kvstore
|
||||
mg-memory
|
||||
mg::csv
|
||||
mg::system
|
||||
mg-flags
|
||||
mg-dbms
|
||||
mg-events)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "query/frontend/ast/ast.hpp" // overkill
|
||||
#include "query/typed_value.hpp"
|
||||
#include "system/system.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
@@ -34,27 +33,23 @@ class AuthQueryHandler {
|
||||
|
||||
/// Return false if the user already exists.
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool CreateUser(const std::string &username, const std::optional<std::string> &password,
|
||||
system::Transaction *system_tx) = 0;
|
||||
virtual bool CreateUser(const std::string &username, const std::optional<std::string> &password) = 0;
|
||||
|
||||
/// Return false if the user does not exist.
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool DropUser(const std::string &username, system::Transaction *system_tx) = 0;
|
||||
virtual bool DropUser(const std::string &username) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void SetPassword(const std::string &username, const std::optional<std::string> &password,
|
||||
system::Transaction *system_tx) = 0;
|
||||
virtual void SetPassword(const std::string &username, const std::optional<std::string> &password) = 0;
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/// Return true if access revoked successfully
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool RevokeDatabaseFromUser(const std::string &db, const std::string &username,
|
||||
system::Transaction *system_tx) = 0;
|
||||
virtual bool RevokeDatabaseFromUser(const std::string &db, const std::string &username) = 0;
|
||||
|
||||
/// Return true if access granted successfully
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool GrantDatabaseToUser(const std::string &db, const std::string &username,
|
||||
system::Transaction *system_tx) = 0;
|
||||
virtual bool GrantDatabaseToUser(const std::string &db, const std::string &username) = 0;
|
||||
|
||||
/// Returns database access rights for the user
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
@@ -62,20 +57,20 @@ class AuthQueryHandler {
|
||||
|
||||
/// Return true if main database set successfully
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool SetMainDatabase(std::string_view db, const std::string &username, system::Transaction *system_tx) = 0;
|
||||
virtual bool SetMainDatabase(std::string_view db, const std::string &username) = 0;
|
||||
|
||||
/// Delete database from all users
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void DeleteDatabase(std::string_view db, system::Transaction *system_tx) = 0;
|
||||
virtual void DeleteDatabase(std::string_view db) = 0;
|
||||
#endif
|
||||
|
||||
/// Return false if the role already exists.
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool CreateRole(const std::string &rolename, system::Transaction *system_tx) = 0;
|
||||
virtual bool CreateRole(const std::string &rolename) = 0;
|
||||
|
||||
/// Return false if the role does not exist.
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool DropRole(const std::string &rolename, system::Transaction *system_tx) = 0;
|
||||
virtual bool DropRole(const std::string &rolename) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual std::vector<memgraph::query::TypedValue> GetUsernames() = 0;
|
||||
@@ -90,10 +85,10 @@ class AuthQueryHandler {
|
||||
virtual std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void SetRole(const std::string &username, const std::string &rolename, system::Transaction *system_tx) = 0;
|
||||
virtual void SetRole(const std::string &username, const std::string &rolename) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void ClearRole(const std::string &username, system::Transaction *system_tx) = 0;
|
||||
virtual void ClearRole(const std::string &username) = 0;
|
||||
|
||||
virtual std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
|
||||
|
||||
@@ -108,13 +103,11 @@ class AuthQueryHandler {
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx) = 0;
|
||||
) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void DenyPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
system::Transaction *system_tx) = 0;
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void RevokePrivilege(
|
||||
@@ -127,8 +120,7 @@ class AuthQueryHandler {
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
system::Transaction *system_tx) = 0;
|
||||
) = 0;
|
||||
};
|
||||
|
||||
} // namespace memgraph::query
|
||||
|
||||
@@ -34,9 +34,7 @@
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "csv/parsing.hpp"
|
||||
#include "dbms/coordinator_handler.hpp"
|
||||
#include "dbms/database.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "dbms/global.hpp"
|
||||
#include "dbms/inmemory/storage_helper.hpp"
|
||||
#include "flags/replication.hpp"
|
||||
@@ -45,7 +43,6 @@
|
||||
#include "license/license.hpp"
|
||||
#include "memory/global_memory_control.hpp"
|
||||
#include "memory/query_memory_control.hpp"
|
||||
#include "query/auth_query_handler.hpp"
|
||||
#include "query/config.hpp"
|
||||
#include "query/constants.hpp"
|
||||
#include "query/context.hpp"
|
||||
@@ -61,14 +58,12 @@
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/interpret/eval.hpp"
|
||||
#include "query/interpret/frame.hpp"
|
||||
#include "query/interpreter_context.hpp"
|
||||
#include "query/metadata.hpp"
|
||||
#include "query/plan/hint_provider.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/profile.hpp"
|
||||
#include "query/plan/vertex_count_cache.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "query/replication_query_handler.hpp"
|
||||
#include "query/stream.hpp"
|
||||
#include "query/stream/common.hpp"
|
||||
#include "query/stream/sources.hpp"
|
||||
@@ -76,7 +71,6 @@
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "replication/config.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "storage/v2/disk/storage.hpp"
|
||||
#include "storage/v2/edge.hpp"
|
||||
@@ -107,6 +101,13 @@
|
||||
#include "utils/typeinfo.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
#include "dbms/coordinator_handler.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "dbms/replication_handler.hpp"
|
||||
#include "query/auth_query_handler.hpp"
|
||||
#include "query/interpreter_context.hpp"
|
||||
#include "replication/state.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "coordination/constants.hpp"
|
||||
#endif
|
||||
@@ -305,18 +306,17 @@ class ReplQueryHandler {
|
||||
ReplicationQuery::ReplicaState state;
|
||||
};
|
||||
|
||||
explicit ReplQueryHandler(query::ReplicationQueryHandler &replication_query_handler)
|
||||
: handler_{&replication_query_handler} {}
|
||||
explicit ReplQueryHandler(dbms::DbmsHandler *dbms_handler) : handler_{*dbms_handler} {}
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) {
|
||||
auto ValidatePort = [](std::optional<int64_t> port) -> void {
|
||||
if (!port || *port < 0 || *port > std::numeric_limits<uint16_t>::max()) {
|
||||
if (*port < 0 || *port > std::numeric_limits<uint16_t>::max()) {
|
||||
throw QueryRuntimeException("Port number invalid!");
|
||||
}
|
||||
};
|
||||
if (replication_role == ReplicationQuery::ReplicationRole::MAIN) {
|
||||
if (!handler_->SetReplicationRoleMain()) {
|
||||
if (!handler_.SetReplicationRoleMain()) {
|
||||
throw QueryRuntimeException("Couldn't set replication role to main!");
|
||||
}
|
||||
} else {
|
||||
@@ -327,7 +327,7 @@ class ReplQueryHandler {
|
||||
.port = static_cast<uint16_t>(*port),
|
||||
};
|
||||
|
||||
if (!handler_->SetReplicationRoleReplica(config)) {
|
||||
if (!handler_.SetReplicationRoleReplica(config)) {
|
||||
throw QueryRuntimeException("Couldn't set role to replica!");
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ class ReplQueryHandler {
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
ReplicationQuery::ReplicationRole ShowReplicationRole() const {
|
||||
switch (handler_->GetRole()) {
|
||||
switch (handler_.GetRole()) {
|
||||
case memgraph::replication_coordination_glue::ReplicationRole::MAIN:
|
||||
return ReplicationQuery::ReplicationRole::MAIN;
|
||||
case memgraph::replication_coordination_glue::ReplicationRole::REPLICA:
|
||||
@@ -349,7 +349,7 @@ class ReplQueryHandler {
|
||||
const ReplicationQuery::SyncMode sync_mode, const std::chrono::seconds replica_check_frequency) {
|
||||
// Coordinator is main by default so this check is OK although it should actually be nothing (neither main nor
|
||||
// replica)
|
||||
if (handler_->IsReplica()) {
|
||||
if (handler_.IsReplica()) {
|
||||
// replica can't register another replica
|
||||
throw QueryRuntimeException("Replica can't register another replica!");
|
||||
}
|
||||
@@ -368,7 +368,7 @@ class ReplQueryHandler {
|
||||
.replica_check_frequency = replica_check_frequency,
|
||||
.ssl = std::nullopt};
|
||||
|
||||
const auto error = handler_->TryRegisterReplica(replication_config).HasError();
|
||||
const auto error = handler_.RegisterReplica(replication_config).HasError();
|
||||
|
||||
if (error) {
|
||||
throw QueryRuntimeException(fmt::format("Couldn't register replica '{}'!", name));
|
||||
@@ -381,9 +381,9 @@ class ReplQueryHandler {
|
||||
|
||||
/// @throw QueryRuntimeException if an error occurred.
|
||||
void DropReplica(std::string_view replica_name) {
|
||||
auto const result = handler_->UnregisterReplica(replica_name);
|
||||
auto const result = handler_.UnregisterReplica(replica_name);
|
||||
switch (result) {
|
||||
using enum memgraph::query::UnregisterReplicaResult;
|
||||
using enum memgraph::dbms::UnregisterReplicaResult;
|
||||
case NOT_MAIN:
|
||||
throw QueryRuntimeException("Replica can't unregister a replica!");
|
||||
case COULD_NOT_BE_PERSISTED:
|
||||
@@ -396,7 +396,7 @@ class ReplQueryHandler {
|
||||
}
|
||||
|
||||
std::vector<ReplicaInfo> ShowReplicas(const dbms::Database &db) const {
|
||||
if (handler_->IsReplica()) {
|
||||
if (handler_.IsReplica()) {
|
||||
// replica can't show registered replicas (it shouldn't have any)
|
||||
throw QueryRuntimeException("Replica can't show registered replicas (it shouldn't have any)!");
|
||||
}
|
||||
@@ -447,16 +447,19 @@ class ReplQueryHandler {
|
||||
}
|
||||
|
||||
private:
|
||||
query::ReplicationQueryHandler *handler_;
|
||||
dbms::ReplicationHandler handler_;
|
||||
};
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
class CoordQueryHandler final : public query::CoordinatorQueryHandler {
|
||||
public:
|
||||
explicit CoordQueryHandler(coordination::CoordinatorState &coordinator_state)
|
||||
|
||||
: coordinator_handler_(coordinator_state) {}
|
||||
explicit CoordQueryHandler(dbms::DbmsHandler *dbms_handler) : handler_ { *dbms_handler }
|
||||
#ifdef MG_ENTERPRISE
|
||||
, coordinator_handler_(*dbms_handler)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
void RegisterInstance(const std::string &coordinator_socket_address, const std::string &replication_socket_address,
|
||||
const std::chrono::seconds instance_check_frequency, const std::string &instance_name,
|
||||
@@ -494,15 +497,16 @@ class CoordQueryHandler final : public query::CoordinatorQueryHandler {
|
||||
using enum memgraph::coordination::RegisterInstanceCoordinatorStatus;
|
||||
case NAME_EXISTS:
|
||||
throw QueryRuntimeException("Couldn't register replica instance since instance with such name already exists!");
|
||||
case ENDPOINT_EXISTS:
|
||||
case END_POINT_EXISTS:
|
||||
throw QueryRuntimeException(
|
||||
"Couldn't register replica instance since instance with such endpoint already exists!");
|
||||
case NOT_COORDINATOR:
|
||||
throw QueryRuntimeException("REGISTER INSTANCE query can only be run on a coordinator!");
|
||||
throw QueryRuntimeException("Couldn't register replica instance since this instance is not a coordinator!");
|
||||
case RPC_FAILED:
|
||||
throw QueryRuntimeException(
|
||||
"Couldn't register replica instance because setting instance to replica failed! Check logs on replica to "
|
||||
"find out more info!");
|
||||
"Couldn't register instance because setting instance to replica failed! Check logs on replica to find out "
|
||||
"more "
|
||||
"info!");
|
||||
case SUCCESS:
|
||||
break;
|
||||
}
|
||||
@@ -515,29 +519,35 @@ class CoordQueryHandler final : public query::CoordinatorQueryHandler {
|
||||
case NO_INSTANCE_WITH_NAME:
|
||||
throw QueryRuntimeException("No instance with such name!");
|
||||
case NOT_COORDINATOR:
|
||||
throw QueryRuntimeException("SET INSTANCE TO MAIN query can only be run on a coordinator!");
|
||||
throw QueryRuntimeException("Couldn't set replica instance to main since this instance is not a coordinator!");
|
||||
case COULD_NOT_PROMOTE_TO_MAIN:
|
||||
throw QueryRuntimeException(
|
||||
"Couldn't set replica instance to main!. Check coordinator and replica for more logs");
|
||||
"Couldn't set replica instance to main. Check coordinator and replica for more logs");
|
||||
case SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::vector<coordination::CoordinatorInstanceStatus> ShowInstances() const override {
|
||||
return coordinator_handler_.ShowInstances();
|
||||
}
|
||||
|
||||
private:
|
||||
dbms::CoordinatorHandler coordinator_handler_;
|
||||
};
|
||||
#endif
|
||||
|
||||
private:
|
||||
dbms::ReplicationHandler handler_;
|
||||
#ifdef MG_ENTERPRISE
|
||||
dbms::CoordinatorHandler coordinator_handler_;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// returns false if the replication role can't be set
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
|
||||
Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_context, const Parameters ¶meters,
|
||||
Interpreter &interpreter) {
|
||||
Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_context, const Parameters ¶meters) {
|
||||
AuthQueryHandler *auth = interpreter_context->auth;
|
||||
#ifdef MG_ENTERPRISE
|
||||
auto *db_handler = interpreter_context->dbms_handler;
|
||||
@@ -586,111 +596,63 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
|
||||
license::LicenseCheckErrorToString(license_check_result.GetError(), "advanced authentication features"));
|
||||
}
|
||||
|
||||
const auto forbid_on_replica = [has_license = license_check_result.HasError(),
|
||||
is_replica = interpreter_context->repl_state->IsReplica()]() {
|
||||
if (is_replica) {
|
||||
#if MG_ENTERPRISE
|
||||
if (has_license) {
|
||||
throw QueryException(
|
||||
"Query forbidden on the replica! Update on MAIN, as it is the only source of truth for authentication "
|
||||
"data. MAIN will then replicate the update to connected REPLICAs");
|
||||
}
|
||||
throw QueryException(
|
||||
"Query forbidden on the replica! Switch role to MAIN and update user data, then switch back to REPLICA.");
|
||||
#else
|
||||
throw QueryException(
|
||||
"Query forbidden on the replica! Switch role to MAIN and update user data, then switch back to REPLICA.");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
switch (auth_query->action_) {
|
||||
case AuthQuery::Action::CREATE_USER:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, username, password, valid_enterprise_license = !license_check_result.HasError(),
|
||||
interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
callback.fn = [auth, username, password, valid_enterprise_license = !license_check_result.HasError()] {
|
||||
MG_ASSERT(password.IsString() || password.IsNull());
|
||||
if (!auth->CreateUser(
|
||||
username, password.IsString() ? std::make_optional(std::string(password.ValueString())) : std::nullopt,
|
||||
&*interpreter->system_transaction_)) {
|
||||
if (!auth->CreateUser(username, password.IsString() ? std::make_optional(std::string(password.ValueString()))
|
||||
: std::nullopt)) {
|
||||
throw UserAlreadyExistsException("User '{}' already exists.", username);
|
||||
}
|
||||
|
||||
// If the license is not valid we create users with admin access
|
||||
if (!valid_enterprise_license) {
|
||||
spdlog::warn("Granting all the privileges to {}.", username);
|
||||
auth->GrantPrivilege(
|
||||
username, kPrivilegesAll
|
||||
auth->GrantPrivilege(username, kPrivilegesAll
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
{{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {query::kAsterisk}}}},
|
||||
{
|
||||
{
|
||||
{
|
||||
AuthQuery::FineGrainedPrivilege::CREATE_DELETE, { query::kAsterisk }
|
||||
}
|
||||
}
|
||||
}
|
||||
,
|
||||
{{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {query::kAsterisk}}}},
|
||||
{
|
||||
{
|
||||
{
|
||||
AuthQuery::FineGrainedPrivilege::CREATE_DELETE, { query::kAsterisk }
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
,
|
||||
&*interpreter->system_transaction_);
|
||||
);
|
||||
}
|
||||
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::DROP_USER:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, username, interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
if (!auth->DropUser(username, &*interpreter->system_transaction_)) {
|
||||
callback.fn = [auth, username] {
|
||||
if (!auth->DropUser(username)) {
|
||||
throw QueryRuntimeException("User '{}' doesn't exist.", username);
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::SET_PASSWORD:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, username, password, interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
callback.fn = [auth, username, password] {
|
||||
MG_ASSERT(password.IsString() || password.IsNull());
|
||||
auth->SetPassword(username,
|
||||
password.IsString() ? std::make_optional(std::string(password.ValueString())) : std::nullopt,
|
||||
&*interpreter->system_transaction_);
|
||||
password.IsString() ? std::make_optional(std::string(password.ValueString())) : std::nullopt);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::CREATE_ROLE:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, rolename, interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
if (!auth->CreateRole(rolename, &*interpreter->system_transaction_)) {
|
||||
callback.fn = [auth, rolename] {
|
||||
if (!auth->CreateRole(rolename)) {
|
||||
throw QueryRuntimeException("Role '{}' already exists.", rolename);
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::DROP_ROLE:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, rolename, interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
if (!auth->DropRole(rolename, &*interpreter->system_transaction_)) {
|
||||
callback.fn = [auth, rolename] {
|
||||
if (!auth->DropRole(rolename)) {
|
||||
throw QueryRuntimeException("Role '{}' doesn't exist.", rolename);
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
@@ -721,79 +683,52 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::SET_ROLE:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, username, rolename, interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
auth->SetRole(username, rolename, &*interpreter->system_transaction_);
|
||||
callback.fn = [auth, username, rolename] {
|
||||
auth->SetRole(username, rolename);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::CLEAR_ROLE:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, username, interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
auth->ClearRole(username, &*interpreter->system_transaction_);
|
||||
callback.fn = [auth, username] {
|
||||
auth->ClearRole(username);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::GRANT_PRIVILEGE:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, user_or_role, privileges, interpreter = &interpreter
|
||||
callback.fn = [auth, user_or_role, privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
label_privileges, edge_type_privileges
|
||||
#endif
|
||||
] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
auth->GrantPrivilege(user_or_role, privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
label_privileges, edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
&*interpreter->system_transaction_);
|
||||
);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::DENY_PRIVILEGE:
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, user_or_role, privileges, interpreter = &interpreter] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
auth->DenyPrivilege(user_or_role, privileges, &*interpreter->system_transaction_);
|
||||
callback.fn = [auth, user_or_role, privileges] {
|
||||
auth->DenyPrivilege(user_or_role, privileges);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::REVOKE_PRIVILEGE: {
|
||||
forbid_on_replica();
|
||||
callback.fn = [auth, user_or_role, privileges, interpreter = &interpreter
|
||||
callback.fn = [auth, user_or_role, privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
label_privileges, edge_type_privileges
|
||||
#endif
|
||||
] {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
auth->RevokePrivilege(user_or_role, privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
label_privileges, edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
&*interpreter->system_transaction_);
|
||||
);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
@@ -823,20 +758,15 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::GRANT_DATABASE_TO_USER:
|
||||
forbid_on_replica();
|
||||
#ifdef MG_ENTERPRISE
|
||||
callback.fn = [auth, database, username, db_handler, interpreter = &interpreter] { // NOLINT
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
callback.fn = [auth, database, username, db_handler] { // NOLINT
|
||||
try {
|
||||
std::optional<memgraph::dbms::DatabaseAccess> db =
|
||||
std::nullopt; // Hold pointer to database to protect it until query is done
|
||||
if (database != memgraph::auth::kAllDatabases) {
|
||||
db = db_handler->Get(database); // Will throw if databases doesn't exist and protect it during pull
|
||||
}
|
||||
if (!auth->GrantDatabaseToUser(database, username, &*interpreter->system_transaction_)) {
|
||||
if (!auth->GrantDatabaseToUser(database, username)) {
|
||||
throw QueryRuntimeException("Failed to grant database {} to user {}.", database, username);
|
||||
}
|
||||
} catch (memgraph::dbms::UnknownDatabaseException &e) {
|
||||
@@ -849,20 +779,15 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::REVOKE_DATABASE_FROM_USER:
|
||||
forbid_on_replica();
|
||||
#ifdef MG_ENTERPRISE
|
||||
callback.fn = [auth, database, username, db_handler, interpreter = &interpreter] { // NOLINT
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
callback.fn = [auth, database, username, db_handler] { // NOLINT
|
||||
try {
|
||||
std::optional<memgraph::dbms::DatabaseAccess> db =
|
||||
std::nullopt; // Hold pointer to database to protect it until query is done
|
||||
if (database != memgraph::auth::kAllDatabases) {
|
||||
db = db_handler->Get(database); // Will throw if databases doesn't exist and protect it during pull
|
||||
}
|
||||
if (!auth->RevokeDatabaseFromUser(database, username, &*interpreter->system_transaction_)) {
|
||||
if (!auth->RevokeDatabaseFromUser(database, username)) {
|
||||
throw QueryRuntimeException("Failed to revoke database {} from user {}.", database, username);
|
||||
}
|
||||
} catch (memgraph::dbms::UnknownDatabaseException &e) {
|
||||
@@ -887,17 +812,12 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
|
||||
#endif
|
||||
return callback;
|
||||
case AuthQuery::Action::SET_MAIN_DATABASE:
|
||||
forbid_on_replica();
|
||||
#ifdef MG_ENTERPRISE
|
||||
callback.fn = [auth, database, username, db_handler, interpreter = &interpreter] { // NOLINT
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
callback.fn = [auth, database, username, db_handler] { // NOLINT
|
||||
try {
|
||||
const auto db =
|
||||
db_handler->Get(database); // Will throw if databases doesn't exist and protect it during pull
|
||||
if (!auth->SetMainDatabase(database, username, &*interpreter->system_transaction_)) {
|
||||
if (!auth->SetMainDatabase(database, username)) {
|
||||
throw QueryRuntimeException("Failed to set main database {} for user {}.", database, username);
|
||||
}
|
||||
} catch (memgraph::dbms::UnknownDatabaseException &e) {
|
||||
@@ -915,7 +835,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
|
||||
} // namespace
|
||||
|
||||
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters ¶meters,
|
||||
ReplicationQueryHandler &replication_query_handler, CurrentDB ¤t_db,
|
||||
dbms::DbmsHandler *dbms_handler, CurrentDB ¤t_db,
|
||||
const query::InterpreterConfig &config, std::vector<Notification> *notifications) {
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
@@ -945,8 +865,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
notifications->emplace_back(SeverityLevel::WARNING, NotificationCode::REPLICA_PORT_WARNING,
|
||||
"Be careful the replication port must be different from the memgraph port!");
|
||||
}
|
||||
callback.fn = [handler = ReplQueryHandler{replication_query_handler}, role = repl_query->role_,
|
||||
maybe_port]() mutable {
|
||||
callback.fn = [handler = ReplQueryHandler{dbms_handler}, role = repl_query->role_, maybe_port]() mutable {
|
||||
handler.SetReplicationRole(role, maybe_port);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
@@ -964,7 +883,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
#endif
|
||||
|
||||
callback.header = {"replication role"};
|
||||
callback.fn = [handler = ReplQueryHandler{replication_query_handler}] {
|
||||
callback.fn = [handler = ReplQueryHandler{dbms_handler}] {
|
||||
auto mode = handler.ShowReplicationRole();
|
||||
switch (mode) {
|
||||
case ReplicationQuery::ReplicationRole::MAIN: {
|
||||
@@ -988,7 +907,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
auto socket_address = repl_query->socket_address_->Accept(evaluator);
|
||||
const auto replica_check_frequency = config.replication_replica_check_frequency;
|
||||
|
||||
callback.fn = [handler = ReplQueryHandler{replication_query_handler}, name, socket_address, sync_mode,
|
||||
callback.fn = [handler = ReplQueryHandler{dbms_handler}, name, socket_address, sync_mode,
|
||||
replica_check_frequency]() mutable {
|
||||
handler.RegisterReplica(name, std::string(socket_address.ValueString()), sync_mode, replica_check_frequency);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
@@ -1005,7 +924,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
}
|
||||
#endif
|
||||
const auto &name = repl_query->instance_name_;
|
||||
callback.fn = [handler = ReplQueryHandler{replication_query_handler}, name]() mutable {
|
||||
callback.fn = [handler = ReplQueryHandler{dbms_handler}, name]() mutable {
|
||||
handler.DropReplica(name);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
@@ -1023,7 +942,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
callback.header = {
|
||||
"name", "socket_address", "sync_mode", "current_timestamp_of_replica", "number_of_timestamp_behind_master",
|
||||
"state"};
|
||||
callback.fn = [handler = ReplQueryHandler{replication_query_handler}, replica_nfields = callback.header.size(),
|
||||
callback.fn = [handler = ReplQueryHandler{dbms_handler}, replica_nfields = callback.header.size(),
|
||||
db_acc = current_db.db_acc_] {
|
||||
const auto &replicas = handler.ShowReplicas(*db_acc->get());
|
||||
auto typed_replicas = std::vector<std::vector<TypedValue>>{};
|
||||
@@ -1071,17 +990,16 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Parameters ¶meters,
|
||||
coordination::CoordinatorState *coordinator_state,
|
||||
const query::InterpreterConfig &config, std::vector<Notification> *notifications) {
|
||||
dbms::DbmsHandler *dbms_handler, const query::InterpreterConfig &config,
|
||||
std::vector<Notification> *notifications) {
|
||||
Callback callback;
|
||||
switch (coordinator_query->action_) {
|
||||
case CoordinatorQuery::Action::REGISTER_INSTANCE: {
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
throw QueryException("Trying to use enterprise feature without a valid license.");
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
if constexpr (!coordination::allow_ha) {
|
||||
throw QueryRuntimeException(
|
||||
"High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to "
|
||||
@@ -1097,7 +1015,7 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
|
||||
auto coordinator_socket_address_tv = coordinator_query->coordinator_socket_address_->Accept(evaluator);
|
||||
auto replication_socket_address_tv = coordinator_query->replication_socket_address_->Accept(evaluator);
|
||||
callback.fn = [handler = CoordQueryHandler{*coordinator_state}, coordinator_socket_address_tv,
|
||||
callback.fn = [handler = CoordQueryHandler{dbms_handler}, coordinator_socket_address_tv,
|
||||
replication_socket_address_tv, main_check_frequency = config.replication_replica_check_frequency,
|
||||
instance_name = coordinator_query->instance_name_,
|
||||
sync_mode = coordinator_query->sync_mode_]() mutable {
|
||||
@@ -1112,11 +1030,13 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
fmt::format("Coordinator has registered coordinator server on {} for instance {}.",
|
||||
coordinator_socket_address_tv.ValueString(), coordinator_query->instance_name_));
|
||||
return callback;
|
||||
#endif
|
||||
}
|
||||
case CoordinatorQuery::Action::SET_INSTANCE_TO_MAIN: {
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
throw QueryException("Trying to use enterprise feature without a valid license.");
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if constexpr (!coordination::allow_ha) {
|
||||
throw QueryRuntimeException(
|
||||
"High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to "
|
||||
@@ -1130,18 +1050,20 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
EvaluationContext evaluation_context{.timestamp = QueryTimestamp(), .parameters = parameters};
|
||||
auto evaluator = PrimitiveLiteralExpressionEvaluator{evaluation_context};
|
||||
|
||||
callback.fn = [handler = CoordQueryHandler{*coordinator_state},
|
||||
callback.fn = [handler = CoordQueryHandler{dbms_handler},
|
||||
instance_name = coordinator_query->instance_name_]() mutable {
|
||||
handler.SetInstanceToMain(instance_name);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
|
||||
return callback;
|
||||
#endif
|
||||
}
|
||||
case CoordinatorQuery::Action::SHOW_REPLICATION_CLUSTER: {
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
throw QueryException("Trying to use enterprise feature without a valid license.");
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if constexpr (!coordination::allow_ha) {
|
||||
throw QueryRuntimeException(
|
||||
"High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to "
|
||||
@@ -1152,8 +1074,7 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
}
|
||||
|
||||
callback.header = {"name", "socket_address", "alive", "role"};
|
||||
callback.fn = [handler = CoordQueryHandler{*coordinator_state},
|
||||
replica_nfields = callback.header.size()]() mutable {
|
||||
callback.fn = [handler = CoordQueryHandler{dbms_handler}, replica_nfields = callback.header.size()]() mutable {
|
||||
auto const instances = handler.ShowInstances();
|
||||
std::vector<std::vector<TypedValue>> result{};
|
||||
result.reserve(result.size());
|
||||
@@ -1167,11 +1088,11 @@ Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Param
|
||||
return result;
|
||||
};
|
||||
return callback;
|
||||
#endif
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
stream::CommonStreamInfo GetCommonStreamInfo(StreamQuery *stream_query, ExpressionVisitor<TypedValue> &evaluator) {
|
||||
return {
|
||||
@@ -2573,14 +2494,14 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
|
||||
}
|
||||
|
||||
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, Interpreter &interpreter) {
|
||||
InterpreterContext *interpreter_context) {
|
||||
if (in_explicit_transaction) {
|
||||
throw UserModificationInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *auth_query = utils::Downcast<AuthQuery>(parsed_query.query);
|
||||
|
||||
auto callback = HandleAuthQuery(auth_query, interpreter_context, parsed_query.parameters, interpreter);
|
||||
auto callback = HandleAuthQuery(auth_query, interpreter_context, parsed_query.parameters);
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[handler = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>(nullptr),
|
||||
@@ -2605,16 +2526,15 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
}
|
||||
|
||||
PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::vector<Notification> *notifications,
|
||||
ReplicationQueryHandler &replication_query_handler, CurrentDB ¤t_db,
|
||||
const InterpreterConfig &config) {
|
||||
std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler,
|
||||
CurrentDB ¤t_db, const InterpreterConfig &config) {
|
||||
if (in_explicit_transaction) {
|
||||
throw ReplicationModificationInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query);
|
||||
auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, replication_query_handler,
|
||||
current_db, config, notifications);
|
||||
auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, &dbms_handler, current_db, config,
|
||||
notifications);
|
||||
|
||||
return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
@@ -2633,10 +2553,8 @@ PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
PreparedQuery PrepareCoordinatorQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::vector<Notification> *notifications,
|
||||
coordination::CoordinatorState &coordinator_state,
|
||||
std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler,
|
||||
const InterpreterConfig &config) {
|
||||
if (in_explicit_transaction) {
|
||||
throw CoordinatorModificationInMulticommandTxException();
|
||||
@@ -2644,7 +2562,7 @@ PreparedQuery PrepareCoordinatorQuery(ParsedQuery parsed_query, bool in_explicit
|
||||
|
||||
auto *coordinator_query = utils::Downcast<CoordinatorQuery>(parsed_query.query);
|
||||
auto callback =
|
||||
HandleCoordinatorQuery(coordinator_query, parsed_query.parameters, &coordinator_state, config, notifications);
|
||||
HandleCoordinatorQuery(coordinator_query, parsed_query.parameters, &dbms_handler, config, notifications);
|
||||
|
||||
return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
@@ -2662,7 +2580,6 @@ PreparedQuery PrepareCoordinatorQuery(ParsedQuery parsed_query, bool in_explicit
|
||||
// False positive report for the std::make_shared above
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
#endif
|
||||
|
||||
PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_transaction, CurrentDB ¤t_db) {
|
||||
if (in_explicit_transaction) {
|
||||
@@ -3765,8 +3682,7 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
|
||||
PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB ¤t_db,
|
||||
InterpreterContext *interpreter_context,
|
||||
std::optional<std::function<void(std::string_view)>> on_change_cb,
|
||||
Interpreter &interpreter) {
|
||||
std::optional<std::function<void(std::string_view)>> on_change_cb) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
throw QueryException("Trying to use enterprise feature without a valid license.");
|
||||
@@ -3785,16 +3701,12 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
|
||||
return PreparedQuery{
|
||||
{"STATUS"},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[db_name = query->db_name_, db_handler, interpreter = &interpreter](
|
||||
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
[db_name = query->db_name_, db_handler](AnyStream *stream,
|
||||
std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
std::vector<std::vector<TypedValue>> status;
|
||||
std::string res;
|
||||
|
||||
const auto success = db_handler->New(db_name, &*interpreter->system_transaction_);
|
||||
const auto success = db_handler->New(db_name);
|
||||
if (success.HasError()) {
|
||||
switch (success.GetError()) {
|
||||
case dbms::NewError::EXISTS:
|
||||
@@ -3869,20 +3781,16 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
|
||||
return PreparedQuery{
|
||||
{"STATUS"},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[db_name = query->db_name_, db_handler, auth = interpreter_context->auth, interpreter = &interpreter](
|
||||
[db_name = query->db_name_, db_handler, auth = interpreter_context->auth](
|
||||
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
if (!interpreter->system_transaction_) {
|
||||
throw QueryException("Expected to be in a system transaction");
|
||||
}
|
||||
|
||||
std::vector<std::vector<TypedValue>> status;
|
||||
|
||||
try {
|
||||
// Remove database
|
||||
auto success = db_handler->TryDelete(db_name, &*interpreter->system_transaction_);
|
||||
auto success = db_handler->TryDelete(db_name);
|
||||
if (!success.HasError()) {
|
||||
// Remove from auth
|
||||
if (auth) auth->DeleteDatabase(db_name, &*interpreter->system_transaction_);
|
||||
if (auth) auth->DeleteDatabase(db_name);
|
||||
} else {
|
||||
switch (success.GetError()) {
|
||||
case dbms::DeleteError::DEFAULT_DB:
|
||||
@@ -4133,15 +4041,18 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
utils::Downcast<ReplicationQuery>(parsed_query.query);
|
||||
|
||||
// TODO Split SHOW REPLICAS (which needs the db) and other replication queries
|
||||
auto system_transaction = std::invoke([&]() -> std::optional<memgraph::system::Transaction> {
|
||||
if (!system_queries) return std::nullopt;
|
||||
|
||||
// TODO: Ordering between system and data queries
|
||||
auto system_txn = interpreter_context_->system_->TryCreateTransaction(std::chrono::milliseconds(kSystemTxTryMS));
|
||||
if (!system_txn) {
|
||||
throw ConcurrentSystemQueriesException("Multiple concurrent system queries are not supported.");
|
||||
auto system_transaction_guard = std::invoke([&]() -> std::optional<SystemTransactionGuard> {
|
||||
if (system_queries) {
|
||||
// TODO: Ordering between system and data queries
|
||||
// Start a system transaction
|
||||
auto system_unique = std::unique_lock{interpreter_context_->dbms_handler->system_lock_, std::defer_lock};
|
||||
if (!system_unique.try_lock_for(std::chrono::milliseconds(kSystemTxTryMS))) {
|
||||
throw ConcurrentSystemQueriesException("Multiple concurrent system queries are not supported.");
|
||||
}
|
||||
return std::optional<SystemTransactionGuard>{std::in_place, std::move(system_unique),
|
||||
*interpreter_context_->dbms_handler};
|
||||
}
|
||||
return system_txn;
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
// Some queries do not require a database to be executed (current_db_ won't be passed on to the Prepare*; special
|
||||
@@ -4207,7 +4118,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
prepared_query = PrepareAnalyzeGraphQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
|
||||
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
|
||||
/// SYSTEM (Replication) PURE
|
||||
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_, *this);
|
||||
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_);
|
||||
} else if (utils::Downcast<DatabaseInfoQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareDatabaseInfoQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
|
||||
} else if (utils::Downcast<SystemInfoQuery>(parsed_query.query)) {
|
||||
@@ -4218,18 +4129,13 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
&query_execution->notifications, current_db_);
|
||||
} else if (utils::Downcast<ReplicationQuery>(parsed_query.query)) {
|
||||
/// TODO: make replication DB agnostic
|
||||
prepared_query = PrepareReplicationQuery(
|
||||
std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
|
||||
*interpreter_context_->replication_handler_, current_db_, interpreter_context_->config);
|
||||
|
||||
prepared_query =
|
||||
PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
|
||||
*interpreter_context_->dbms_handler, current_db_, interpreter_context_->config);
|
||||
} else if (utils::Downcast<CoordinatorQuery>(parsed_query.query)) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
prepared_query =
|
||||
PrepareCoordinatorQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
|
||||
*interpreter_context_->coordinator_state_, interpreter_context_->config);
|
||||
#else
|
||||
throw QueryRuntimeException("Coordinator queries are not part of community edition");
|
||||
#endif
|
||||
*interpreter_context_->dbms_handler, interpreter_context_->config);
|
||||
} else if (utils::Downcast<LockPathQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
|
||||
} else if (utils::Downcast<FreeMemoryQuery>(parsed_query.query)) {
|
||||
@@ -4272,8 +4178,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
}
|
||||
/// SYSTEM (Replication) + INTERPRETER
|
||||
// DMG_ASSERT(system_guard);
|
||||
prepared_query =
|
||||
PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_, *this);
|
||||
prepared_query = PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_
|
||||
/*, *system_guard*/);
|
||||
} else if (utils::Downcast<ShowDatabasesQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareShowDatabasesQuery(std::move(parsed_query), interpreter_context_, username_);
|
||||
} else if (utils::Downcast<EdgeImportModeQuery>(parsed_query.query)) {
|
||||
@@ -4305,7 +4211,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
query_execution->summary["db"] = *query_execution->prepared_query->db;
|
||||
|
||||
// prepare is done, move system txn guard to be owned by interpreter
|
||||
system_transaction_ = std::move(system_transaction);
|
||||
system_transaction_guard_ = std::move(system_transaction_guard);
|
||||
return {query_execution->prepared_query->header, query_execution->prepared_query->privileges, qid,
|
||||
query_execution->prepared_query->db};
|
||||
} catch (const utils::BasicException &) {
|
||||
@@ -4455,13 +4361,13 @@ void Interpreter::Commit() {
|
||||
current_transaction_.reset();
|
||||
if (!current_db_.db_transactional_accessor_ || !current_db_.db_acc_) {
|
||||
// No database nor db transaction; check for system transaction
|
||||
if (!system_transaction_) return;
|
||||
if (!system_transaction_guard_) return;
|
||||
|
||||
// TODO Distinguish between data and system transaction state
|
||||
// Think about updating the status to a struct with bitfield
|
||||
// Clean transaction status on exit
|
||||
utils::OnScopeExit clean_status([this]() {
|
||||
system_transaction_.reset();
|
||||
system_transaction_guard_.reset();
|
||||
// System transactions are not terminable
|
||||
// Durability has happened at time of PULL
|
||||
// Commit is doing replication and timestamp update
|
||||
@@ -4479,23 +4385,7 @@ void Interpreter::Commit() {
|
||||
}
|
||||
});
|
||||
|
||||
auto const main_commit = [&](replication::RoleMainData &mainData) {
|
||||
// Only enterprise can do system replication
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return system_transaction_->Commit(memgraph::system::DoReplication{mainData});
|
||||
}
|
||||
#endif
|
||||
return system_transaction_->Commit(memgraph::system::DoNothing{});
|
||||
};
|
||||
|
||||
auto const replica_commit = [&](replication::RoleReplicaData &) {
|
||||
return system_transaction_->Commit(memgraph::system::DoNothing{});
|
||||
};
|
||||
|
||||
auto const commit_method = utils::Overloaded{main_commit, replica_commit};
|
||||
[[maybe_unused]] auto sync_result = std::visit(commit_method, interpreter_context_->repl_state->ReplicationData());
|
||||
// TODO: something with sync_result
|
||||
system_transaction_guard_->Commit();
|
||||
return;
|
||||
}
|
||||
auto *db = current_db_.db_acc_->get();
|
||||
|
||||
@@ -72,7 +72,6 @@ inline constexpr size_t kExecutionPoolMaxBlockSize = 1024UL; // 2 ^ 10
|
||||
|
||||
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
class CoordinatorQueryHandler {
|
||||
public:
|
||||
CoordinatorQueryHandler() = default;
|
||||
@@ -94,6 +93,7 @@ class CoordinatorQueryHandler {
|
||||
ReplicationQuery::ReplicaState state;
|
||||
};
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
struct MainReplicaStatus {
|
||||
std::string_view name;
|
||||
std::string_view socket_address;
|
||||
@@ -103,7 +103,9 @@ class CoordinatorQueryHandler {
|
||||
MainReplicaStatus(std::string_view name, std::string_view socket_address, bool alive, bool is_main)
|
||||
: name{name}, socket_address{socket_address}, alive{alive}, is_main{is_main} {}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void RegisterInstance(const std::string &coordinator_socket_address,
|
||||
const std::string &replication_socket_address,
|
||||
@@ -115,8 +117,9 @@ class CoordinatorQueryHandler {
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual std::vector<coordination::CoordinatorInstanceStatus> ShowInstances() const = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
class AnalyzeGraphQueryHandler {
|
||||
public:
|
||||
@@ -293,12 +296,32 @@ class Interpreter final {
|
||||
|
||||
void SetUser(std::string_view username);
|
||||
|
||||
std::optional<memgraph::system::Transaction> system_transaction_{};
|
||||
struct SystemTransactionGuard {
|
||||
explicit SystemTransactionGuard(std::unique_lock<utils::ResourceLock> guard, dbms::DbmsHandler &dbms_handler)
|
||||
: system_guard_(std::move(guard)), dbms_handler_{&dbms_handler} {
|
||||
dbms_handler_->NewSystemTransaction();
|
||||
}
|
||||
SystemTransactionGuard &operator=(SystemTransactionGuard &&) = default;
|
||||
SystemTransactionGuard(SystemTransactionGuard &&) = default;
|
||||
|
||||
~SystemTransactionGuard() {
|
||||
if (system_guard_.owns_lock()) dbms_handler_->ResetSystemTransaction();
|
||||
}
|
||||
|
||||
dbms::AllSyncReplicaStatus Commit() { return dbms_handler_->Commit(); }
|
||||
|
||||
private:
|
||||
std::unique_lock<utils::ResourceLock> system_guard_;
|
||||
dbms::DbmsHandler *dbms_handler_;
|
||||
};
|
||||
|
||||
std::optional<SystemTransactionGuard> system_transaction_guard_{};
|
||||
|
||||
private:
|
||||
void ResetInterpreter() {
|
||||
query_executions_.clear();
|
||||
system_transaction_.reset();
|
||||
system_guard.reset();
|
||||
system_transaction_guard_.reset();
|
||||
transaction_queries_->clear();
|
||||
if (current_db_.db_acc_ && current_db_.db_acc_->is_deleting()) {
|
||||
current_db_.db_acc_.reset();
|
||||
@@ -363,6 +386,8 @@ class Interpreter final {
|
||||
// TODO Figure out how this would work for multi-database
|
||||
// Exists only during a single transaction (for now should be okay as is)
|
||||
std::vector<std::unique_ptr<QueryExecution>> query_executions_;
|
||||
// TODO: our upgradable lock guard for system
|
||||
std::optional<utils::ResourceLockGuard> system_guard;
|
||||
|
||||
// all queries that are run as part of the current transaction
|
||||
utils::Synchronized<std::vector<std::string>, utils::SpinLock> transaction_queries_;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -12,27 +12,12 @@
|
||||
#include "query/interpreter_context.hpp"
|
||||
|
||||
#include "query/interpreter.hpp"
|
||||
#include "system/include/system/system.hpp"
|
||||
namespace memgraph::query {
|
||||
|
||||
InterpreterContext::InterpreterContext(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
|
||||
replication::ReplicationState *rs, memgraph::system::System &system,
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::coordination::CoordinatorState *coordinator_state,
|
||||
#endif
|
||||
AuthQueryHandler *ah, AuthChecker *ac,
|
||||
ReplicationQueryHandler *replication_handler)
|
||||
: dbms_handler(dbms_handler),
|
||||
config(interpreter_config),
|
||||
repl_state(rs),
|
||||
#ifdef MG_ENTERPRISE
|
||||
coordinator_state_{coordinator_state},
|
||||
#endif
|
||||
auth(ah),
|
||||
auth_checker(ac),
|
||||
replication_handler_{replication_handler},
|
||||
system_{&system} {
|
||||
}
|
||||
replication::ReplicationState *rs, query::AuthQueryHandler *ah,
|
||||
query::AuthChecker *ac)
|
||||
: dbms_handler(dbms_handler), config(interpreter_config), repl_state(rs), auth(ah), auth_checker(ac) {}
|
||||
|
||||
std::vector<std::vector<TypedValue>> InterpreterContext::TerminateTransactions(
|
||||
std::vector<std::string> maybe_kill_transaction_ids, const std::optional<std::string> &username,
|
||||
|
||||
@@ -20,20 +20,14 @@
|
||||
|
||||
#include "query/config.hpp"
|
||||
#include "query/cypher_query_interpreter.hpp"
|
||||
#include "query/replication_query_handler.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "system/state.hpp"
|
||||
#include "system/system.hpp"
|
||||
#include "utils/gatekeeper.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "coordination/coordinator_state.hpp"
|
||||
#endif
|
||||
|
||||
namespace memgraph::dbms {
|
||||
class DbmsHandler;
|
||||
@@ -54,12 +48,7 @@ class Interpreter;
|
||||
*/
|
||||
struct InterpreterContext {
|
||||
InterpreterContext(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
|
||||
replication::ReplicationState *rs, memgraph::system::System &system,
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::coordination::CoordinatorState *coordinator_state,
|
||||
#endif
|
||||
AuthQueryHandler *ah = nullptr, AuthChecker *ac = nullptr,
|
||||
ReplicationQueryHandler *replication_handler = nullptr);
|
||||
replication::ReplicationState *rs, AuthQueryHandler *ah = nullptr, AuthChecker *ac = nullptr);
|
||||
|
||||
memgraph::dbms::DbmsHandler *dbms_handler;
|
||||
|
||||
@@ -70,14 +59,9 @@ struct InterpreterContext {
|
||||
|
||||
// GLOBAL
|
||||
memgraph::replication::ReplicationState *repl_state;
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::coordination::CoordinatorState *coordinator_state_;
|
||||
#endif
|
||||
|
||||
AuthQueryHandler *auth;
|
||||
AuthChecker *auth_checker;
|
||||
ReplicationQueryHandler *replication_handler_;
|
||||
system::System *system_;
|
||||
|
||||
// Used to check active transactions
|
||||
// TODO: Have a way to read the current database
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "replication_coordination_glue/role.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
// BEGIN fwd declares
|
||||
namespace memgraph::replication {
|
||||
struct ReplicationState;
|
||||
struct ReplicationServerConfig;
|
||||
struct ReplicationClientConfig;
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, ENDPOINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
|
||||
enum class UnregisterReplicaResult : uint8_t {
|
||||
NOT_MAIN,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
CAN_NOT_UNREGISTER,
|
||||
SUCCESS,
|
||||
};
|
||||
|
||||
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
|
||||
struct ReplicationQueryHandler {
|
||||
virtual ~ReplicationQueryHandler() = default;
|
||||
|
||||
// as REPLICA, become MAIN
|
||||
virtual bool SetReplicationRoleMain() = 0;
|
||||
|
||||
// as MAIN, become REPLICA
|
||||
virtual bool SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config) = 0;
|
||||
|
||||
// as MAIN, define and connect to REPLICAs
|
||||
virtual auto TryRegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> utils::BasicResult<RegisterReplicaError> = 0;
|
||||
|
||||
virtual auto RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> utils::BasicResult<RegisterReplicaError> = 0;
|
||||
|
||||
// as MAIN, remove a REPLICA connection
|
||||
virtual auto UnregisterReplica(std::string_view name) -> UnregisterReplicaResult = 0;
|
||||
|
||||
// Helper pass-through (TODO: remove)
|
||||
virtual auto GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole = 0;
|
||||
virtual bool IsMain() const = 0;
|
||||
virtual bool IsReplica() const = 0;
|
||||
};
|
||||
|
||||
} // namespace memgraph::query
|
||||
@@ -6,6 +6,7 @@ target_sources(mg-replication
|
||||
include/replication/epoch.hpp
|
||||
include/replication/config.hpp
|
||||
include/replication/status.hpp
|
||||
include/replication/messages.hpp
|
||||
include/replication/replication_client.hpp
|
||||
include/replication/replication_server.hpp
|
||||
|
||||
@@ -14,6 +15,7 @@ target_sources(mg-replication
|
||||
epoch.cpp
|
||||
config.cpp
|
||||
status.cpp
|
||||
messages.cpp
|
||||
replication_client.cpp
|
||||
replication_server.cpp
|
||||
)
|
||||
|
||||
47
src/replication/include/replication/messages.hpp
Normal file
47
src/replication/include/replication/messages.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rpc/messages.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
struct SystemHeartbeatReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder);
|
||||
SystemHeartbeatReq() = default;
|
||||
};
|
||||
|
||||
struct SystemHeartbeatRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
|
||||
SystemHeartbeatRes() = default;
|
||||
explicit SystemHeartbeatRes(uint64_t system_timestamp) : system_timestamp(system_timestamp) {}
|
||||
|
||||
uint64_t system_timestamp;
|
||||
};
|
||||
|
||||
using SystemHeartbeatRpc = rpc::RequestResponse<SystemHeartbeatReq, SystemHeartbeatRes>;
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::slk {
|
||||
void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
|
||||
void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/);
|
||||
void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/);
|
||||
} // namespace memgraph::slk
|
||||
@@ -41,67 +41,26 @@ struct ReplicationClient {
|
||||
void StartFrequentCheck(F &&callback) {
|
||||
// Help the user to get the most accurate replica state possible.
|
||||
if (replica_check_frequency_ > std::chrono::seconds(0)) {
|
||||
replica_checker_.Run(
|
||||
"Replica Checker", replica_check_frequency_,
|
||||
[this, cb = std::forward<F>(callback), reconnect = false]() mutable {
|
||||
try {
|
||||
{
|
||||
auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||
stream.AwaitResponse();
|
||||
}
|
||||
cb(reconnect, *this);
|
||||
reconnect = false;
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
// Nothing to do...wait for a reconnect
|
||||
// NOTE: Here we are communicating with the instance connection.
|
||||
// We don't have access to the undelying client; so the only thing we can do it
|
||||
// tell the callback that this is a reconnection and to check the state
|
||||
reconnect = true;
|
||||
}
|
||||
});
|
||||
replica_checker_.Run("Replica Checker", replica_check_frequency_,
|
||||
[this, cb = std::forward<F>(callback), reconnect = false]() mutable {
|
||||
try {
|
||||
{
|
||||
auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||
stream.AwaitResponse();
|
||||
}
|
||||
cb(reconnect, *this);
|
||||
reconnect = false;
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
// Nothing to do...wait for a reconnect
|
||||
// NOTE: Here we are communicating with the instance connection.
|
||||
// We don't have access to the undelying client; so the only thing we can do it
|
||||
// tell the callback that this is a reconnection and to check the state
|
||||
reconnect = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//! \tparam RPC An rpc::RequestResponse
|
||||
//! \tparam Args the args type
|
||||
//! \param client the client to use for rpc communication
|
||||
//! \param check predicate to check response is ok
|
||||
//! \param args arguments to forward to the rpc request
|
||||
//! \return If replica stream is completed or enqueued
|
||||
template <typename RPC, typename... Args>
|
||||
bool SteamAndFinalizeDelta(auto &&check, Args &&...args) {
|
||||
try {
|
||||
auto stream = rpc_client_.template Stream<RPC>(std::forward<Args>(args)...);
|
||||
auto task = [this, check = std::forward<decltype(check)>(check), stream = std::move(stream)]() mutable {
|
||||
if (stream.IsDefunct()) {
|
||||
state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (check(stream.AwaitResponse())) {
|
||||
return true;
|
||||
}
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
// swallow error, fallthrough to error handling
|
||||
}
|
||||
// This replica needs SYSTEM recovery
|
||||
state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
};
|
||||
|
||||
if (mode_ == memgraph::replication_coordination_glue::ReplicationMode::ASYNC) {
|
||||
thread_pool_.AddTask([task = utils::CopyMovableFunctionWrapper{std::move(task)}]() mutable { task(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
return task();
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
// This replica needs SYSTEM recovery
|
||||
state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
std::string name_;
|
||||
communication::ClientContext rpc_context_;
|
||||
rpc::Client rpc_client_;
|
||||
|
||||
52
src/replication/messages.cpp
Normal file
52
src/replication/messages.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
#include "replication/messages.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
constexpr utils::TypeInfo SystemHeartbeatReq::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_REQ, "SystemHeartbeatReq",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo SystemHeartbeatRes::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_RES, "SystemHeartbeatRes",
|
||||
nullptr};
|
||||
|
||||
void SystemHeartbeatReq::Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemHeartbeatReq::Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void SystemHeartbeatRes::Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemHeartbeatRes::Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::slk {
|
||||
// Serialize code for SystemHeartbeatRes
|
||||
void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.system_timestamp, builder);
|
||||
}
|
||||
void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->system_timestamp, reader);
|
||||
}
|
||||
|
||||
// Serialize code for SystemHeartbeatReq
|
||||
void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
} // namespace memgraph::slk
|
||||
@@ -1,17 +0,0 @@
|
||||
add_library(mg-replication_handler STATIC)
|
||||
add_library(mg::replication_handler ALIAS mg-replication_handler)
|
||||
target_sources(mg-replication_handler
|
||||
PUBLIC
|
||||
include/replication_handler/replication_handler.hpp
|
||||
include/replication_handler/system_replication.hpp
|
||||
include/replication_handler/system_rpc.hpp
|
||||
|
||||
PRIVATE
|
||||
replication_handler.cpp
|
||||
system_replication.cpp
|
||||
system_rpc.cpp
|
||||
)
|
||||
target_include_directories(mg-replication_handler PUBLIC include)
|
||||
|
||||
target_link_libraries(mg-replication_handler
|
||||
PUBLIC mg-auth mg-dbms mg-replication)
|
||||
@@ -1,220 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
#pragma once
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "replication/include/replication/state.hpp"
|
||||
#include "replication_handler/system_rpc.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
inline std::optional<query::RegisterReplicaError> HandleRegisterReplicaStatus(
|
||||
utils::BasicResult<replication::RegisterReplicaError, replication::ReplicationClient *> &instance_client);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
void StartReplicaClient(replication::ReplicationClient &client, system::System *system, dbms::DbmsHandler &dbms_handler,
|
||||
auth::SynchedAuth &auth);
|
||||
#else
|
||||
void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandler &dbms_handler);
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// TODO: Split into 2 functions: dbms and auth
|
||||
// When being called by interpreter no need to gain lock, it should already be under a system transaction
|
||||
// But concurrently the FrequentCheck is running and will need to lock before reading last_committed_system_timestamp_
|
||||
template <bool REQUIRE_LOCK = false>
|
||||
void SystemRestore(replication::ReplicationClient &client, system::System *system, dbms::DbmsHandler &dbms_handler,
|
||||
auth::SynchedAuth &auth) {
|
||||
// Check if system is up to date
|
||||
if (client.state_.WithLock(
|
||||
[](auto &state) { return state == memgraph::replication::ReplicationClient::State::READY; }))
|
||||
return;
|
||||
|
||||
// Try to recover...
|
||||
{
|
||||
struct DbInfo {
|
||||
std::vector<storage::SalientConfig> configs;
|
||||
uint64_t last_committed_timestamp;
|
||||
};
|
||||
DbInfo db_info = std::invoke([&] {
|
||||
auto guard = std::invoke([&]() -> std::optional<memgraph::system::TransactionGuard> {
|
||||
if constexpr (REQUIRE_LOCK) {
|
||||
return system->GenTransactionGuard();
|
||||
}
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
if (license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
auto configs = std::vector<storage::SalientConfig>{};
|
||||
dbms_handler.ForEach([&configs](dbms::DatabaseAccess acc) { configs.emplace_back(acc->config().salient); });
|
||||
// TODO: This is `SystemRestore` maybe DbInfo is incorrect as it will need Auth also
|
||||
return DbInfo{configs, system->LastCommittedSystemTimestamp()};
|
||||
}
|
||||
|
||||
// No license -> send only default config
|
||||
return DbInfo{{dbms_handler.Get()->config().salient}, system->LastCommittedSystemTimestamp()};
|
||||
});
|
||||
try {
|
||||
auto stream = std::invoke([&]() {
|
||||
// Handle only default database is no license
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return client.rpc_client_.Stream<replication::SystemRecoveryRpc>(
|
||||
db_info.last_committed_timestamp, std::move(db_info.configs), auth::Auth::Config{},
|
||||
std::vector<auth::User>{}, std::vector<auth::Role>{});
|
||||
}
|
||||
return auth.WithLock([&](auto &locked_auth) {
|
||||
return client.rpc_client_.Stream<replication::SystemRecoveryRpc>(
|
||||
db_info.last_committed_timestamp, std::move(db_info.configs), locked_auth.GetConfig(),
|
||||
locked_auth.AllUsers(), locked_auth.AllRoles());
|
||||
});
|
||||
});
|
||||
const auto response = stream.AwaitResponse();
|
||||
if (response.result == replication::SystemRecoveryRes::Result::FAILURE) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return;
|
||||
}
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully recovered
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::READY; });
|
||||
}
|
||||
#endif
|
||||
|
||||
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
|
||||
struct ReplicationHandler : public memgraph::query::ReplicationQueryHandler {
|
||||
#ifdef MG_ENTERPRISE
|
||||
explicit ReplicationHandler(memgraph::replication::ReplicationState &repl_state,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::system::System *system,
|
||||
memgraph::auth::SynchedAuth &auth);
|
||||
#else
|
||||
explicit ReplicationHandler(memgraph::replication::ReplicationState &repl_state,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler);
|
||||
#endif
|
||||
|
||||
// as REPLICA, become MAIN
|
||||
bool SetReplicationRoleMain() override;
|
||||
|
||||
// as MAIN, become REPLICA
|
||||
bool SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config) override;
|
||||
|
||||
// as MAIN, define and connect to REPLICAs
|
||||
auto TryRegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> memgraph::utils::BasicResult<memgraph::query::RegisterReplicaError> override;
|
||||
|
||||
auto RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> memgraph::utils::BasicResult<memgraph::query::RegisterReplicaError> override;
|
||||
|
||||
// as MAIN, remove a REPLICA connection
|
||||
auto UnregisterReplica(std::string_view name) -> memgraph::query::UnregisterReplicaResult override;
|
||||
|
||||
bool DoReplicaToMainPromotion();
|
||||
|
||||
// Helper pass-through (TODO: remove)
|
||||
auto GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole override;
|
||||
bool IsMain() const override;
|
||||
bool IsReplica() const override;
|
||||
|
||||
private:
|
||||
template <bool HandleFailure>
|
||||
auto RegisterReplica_(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> memgraph::utils::BasicResult<memgraph::query::RegisterReplicaError> {
|
||||
MG_ASSERT(repl_state_.IsMain(), "Only main instance can register a replica!");
|
||||
|
||||
auto maybe_client = repl_state_.RegisterReplica(config);
|
||||
if (maybe_client.HasError()) {
|
||||
switch (maybe_client.GetError()) {
|
||||
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
|
||||
MG_ASSERT(false, "Only main instance can register a replica!");
|
||||
return {};
|
||||
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
|
||||
return memgraph::query::RegisterReplicaError::NAME_EXISTS;
|
||||
case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
return memgraph::query::RegisterReplicaError::ENDPOINT_EXISTS;
|
||||
case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return memgraph::query::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case memgraph::replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!memgraph::dbms::allow_mt_repl && dbms_handler_.All().size() > 1) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Update system before enabling individual storage <-> replica clients
|
||||
SystemRestore(*maybe_client.GetValue(), system_, dbms_handler_, auth_);
|
||||
#endif
|
||||
|
||||
const auto dbms_error = HandleRegisterReplicaStatus(maybe_client);
|
||||
if (dbms_error.has_value()) {
|
||||
return *dbms_error;
|
||||
}
|
||||
auto &instance_client_ptr = maybe_client.GetValue();
|
||||
|
||||
bool all_clients_good = true;
|
||||
// Add database specific clients (NOTE Currently all databases are connected to each replica)
|
||||
dbms_handler_.ForEach([&](dbms::DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
if (!dbms::allow_mt_repl && storage->name() != dbms::kDefaultDB) {
|
||||
return;
|
||||
}
|
||||
// TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes
|
||||
if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return;
|
||||
|
||||
all_clients_good &= storage->repl_storage_state_.replication_clients_.WithLock(
|
||||
[storage, &instance_client_ptr, db_acc = std::move(db_acc)](auto &storage_clients) mutable { // NOLINT
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(*instance_client_ptr);
|
||||
// All good, start replica client
|
||||
client->Start(storage, std::move(db_acc));
|
||||
// After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
|
||||
// MAYBE_BEHIND isn't a statement of the current state, this is the default value
|
||||
// Failed to start due an error like branching of MAIN and REPLICA
|
||||
const bool success = client->State() != storage::replication::ReplicaState::MAYBE_BEHIND;
|
||||
if (HandleFailure || success) {
|
||||
storage_clients.push_back(std::move(client));
|
||||
}
|
||||
return success;
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE Currently if any databases fails, we revert back
|
||||
if (!HandleFailure && !all_clients_good) {
|
||||
spdlog::error("Failed to register all databases on the REPLICA \"{}\"", config.name);
|
||||
UnregisterReplica(config.name);
|
||||
return memgraph::query::RegisterReplicaError::CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
// No client error, start instance level client
|
||||
#ifdef MG_ENTERPRISE
|
||||
StartReplicaClient(*instance_client_ptr, system_, dbms_handler_, auth_);
|
||||
#else
|
||||
StartReplicaClient(*instance_client_ptr, dbms_handler_);
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
memgraph::replication::ReplicationState &repl_state_;
|
||||
memgraph::dbms::DbmsHandler &dbms_handler_;
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::system::System *system_;
|
||||
memgraph::auth::SynchedAuth &auth_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace memgraph::replication
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
#include "system/state.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
#ifdef MG_ENTERPRISE
|
||||
void SystemHeartbeatHandler(uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void SystemRecoveryHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access,
|
||||
dbms::DbmsHandler &dbms_handler, auth::SynchedAuth &auth, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler, auth::SynchedAuth &auth);
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, const replication::RoleReplicaData &data, auth::SynchedAuth &auth);
|
||||
#else
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, const replication::RoleReplicaData &data);
|
||||
#endif
|
||||
|
||||
} // namespace memgraph::replication
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "rpc/messages.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
struct SystemHeartbeatReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder);
|
||||
SystemHeartbeatReq() = default;
|
||||
};
|
||||
|
||||
struct SystemHeartbeatRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
|
||||
SystemHeartbeatRes() = default;
|
||||
explicit SystemHeartbeatRes(uint64_t system_timestamp) : system_timestamp(system_timestamp) {}
|
||||
|
||||
uint64_t system_timestamp;
|
||||
};
|
||||
|
||||
using SystemHeartbeatRpc = rpc::RequestResponse<SystemHeartbeatReq, SystemHeartbeatRes>;
|
||||
|
||||
struct SystemRecoveryReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder);
|
||||
SystemRecoveryReq() = default;
|
||||
SystemRecoveryReq(uint64_t forced_group_timestamp, std::vector<storage::SalientConfig> database_configs,
|
||||
auth::Auth::Config auth_config, std::vector<auth::User> users, std::vector<auth::Role> roles)
|
||||
: forced_group_timestamp{forced_group_timestamp},
|
||||
database_configs(std::move(database_configs)),
|
||||
auth_config(std::move(auth_config)),
|
||||
users{std::move(users)},
|
||||
roles{std::move(roles)} {}
|
||||
|
||||
uint64_t forced_group_timestamp;
|
||||
std::vector<storage::SalientConfig> database_configs;
|
||||
auth::Auth::Config auth_config;
|
||||
std::vector<auth::User> users;
|
||||
std::vector<auth::Role> roles;
|
||||
};
|
||||
|
||||
struct SystemRecoveryRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
|
||||
|
||||
static void Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder);
|
||||
SystemRecoveryRes() = default;
|
||||
explicit SystemRecoveryRes(Result res) : result(res) {}
|
||||
|
||||
Result result;
|
||||
};
|
||||
|
||||
using SystemRecoveryRpc = rpc::RequestResponse<SystemRecoveryReq, SystemRecoveryRes>;
|
||||
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::slk {
|
||||
void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
|
||||
void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/);
|
||||
void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/);
|
||||
void Save(const memgraph::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader);
|
||||
void Save(const memgraph::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader);
|
||||
} // namespace memgraph::slk
|
||||
@@ -1,291 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "replication_handler/replication_handler.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "replication_handler/system_replication.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
namespace {
|
||||
#ifdef MG_ENTERPRISE
|
||||
void RecoverReplication(memgraph::replication::ReplicationState &repl_state, memgraph::system::System *system,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::auth::SynchedAuth &auth) {
|
||||
/*
|
||||
* REPLICATION RECOVERY AND STARTUP
|
||||
*/
|
||||
|
||||
// Startup replication state (if recovered at startup)
|
||||
auto replica = [&dbms_handler, &auth](memgraph::replication::RoleReplicaData const &data) {
|
||||
return memgraph::replication::StartRpcServer(dbms_handler, data, auth);
|
||||
};
|
||||
|
||||
// Replication recovery and frequent check start
|
||||
auto main = [system, &dbms_handler, &auth](memgraph::replication::RoleMainData &mainData) {
|
||||
for (auto &client : mainData.registered_replicas_) {
|
||||
memgraph::replication::SystemRestore(client, system, dbms_handler, auth);
|
||||
}
|
||||
// DBMS here
|
||||
dbms_handler.ForEach([&mainData](memgraph::dbms::DatabaseAccess db_acc) {
|
||||
dbms::DbmsHandler::RecoverStorageReplication(std::move(db_acc), mainData);
|
||||
});
|
||||
|
||||
for (auto &client : mainData.registered_replicas_) {
|
||||
memgraph::replication::StartReplicaClient(client, system, dbms_handler, auth);
|
||||
}
|
||||
|
||||
// Warning
|
||||
if (dbms_handler.default_config().durability.snapshot_wal_mode ==
|
||||
memgraph::storage::Config::Durability::SnapshotWalMode::DISABLED) {
|
||||
spdlog::warn(
|
||||
"The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please "
|
||||
"consider "
|
||||
"enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
|
||||
"without write-ahead logs this instance is not replicating any data.");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto result = std::visit(memgraph::utils::Overloaded{replica, main}, repl_state.ReplicationData());
|
||||
MG_ASSERT(result, "Replica recovery failure!");
|
||||
}
|
||||
#else
|
||||
void RecoverReplication(memgraph::replication::ReplicationState &repl_state,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler) {
|
||||
// Startup replication state (if recovered at startup)
|
||||
auto replica = [&dbms_handler](memgraph::replication::RoleReplicaData const &data) {
|
||||
return memgraph::replication::StartRpcServer(dbms_handler, data);
|
||||
};
|
||||
|
||||
// Replication recovery and frequent check start
|
||||
auto main = [&dbms_handler](memgraph::replication::RoleMainData &mainData) {
|
||||
dbms::DbmsHandler::RecoverStorageReplication(dbms_handler.Get(), mainData);
|
||||
|
||||
for (auto &client : mainData.registered_replicas_) {
|
||||
memgraph::replication::StartReplicaClient(client, dbms_handler);
|
||||
}
|
||||
|
||||
// Warning
|
||||
if (dbms_handler.default_config().durability.snapshot_wal_mode ==
|
||||
memgraph::storage::Config::Durability::SnapshotWalMode::DISABLED) {
|
||||
spdlog::warn(
|
||||
"The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please "
|
||||
"consider "
|
||||
"enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
|
||||
"without write-ahead logs this instance is not replicating any data.");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto result = std::visit(memgraph::utils::Overloaded{replica, main}, repl_state.ReplicationData());
|
||||
MG_ASSERT(result, "Replica recovery failure!");
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
inline std::optional<query::RegisterReplicaError> HandleRegisterReplicaStatus(
|
||||
utils::BasicResult<replication::RegisterReplicaError, replication::ReplicationClient *> &instance_client) {
|
||||
if (instance_client.HasError()) switch (instance_client.GetError()) {
|
||||
case replication::RegisterReplicaError::NOT_MAIN:
|
||||
MG_ASSERT(false, "Only main instance can register a replica!");
|
||||
return {};
|
||||
case replication::RegisterReplicaError::NAME_EXISTS:
|
||||
return query::RegisterReplicaError::NAME_EXISTS;
|
||||
case replication::RegisterReplicaError::ENDPOINT_EXISTS:
|
||||
return query::RegisterReplicaError::ENDPOINT_EXISTS;
|
||||
case replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return query::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
void StartReplicaClient(replication::ReplicationClient &client, system::System *system, dbms::DbmsHandler &dbms_handler,
|
||||
auth::SynchedAuth &auth) {
|
||||
#else
|
||||
void StartReplicaClient(replication::ReplicationClient &client, dbms::DbmsHandler &dbms_handler) {
|
||||
#endif
|
||||
// No client error, start instance level client
|
||||
auto const &endpoint = client.rpc_client_.Endpoint();
|
||||
spdlog::trace("Replication client started at: {}:{}", endpoint.address, endpoint.port);
|
||||
client.StartFrequentCheck([&,
|
||||
#ifdef MG_ENTERPRISE
|
||||
system = system,
|
||||
#endif
|
||||
license = license::global_license_checker.IsEnterpriseValidFast()](
|
||||
bool reconnect, replication::ReplicationClient &client) mutable {
|
||||
// Working connection
|
||||
// Check if system needs restoration
|
||||
if (reconnect) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
}
|
||||
// Check if license has changed
|
||||
const auto new_license = license::global_license_checker.IsEnterpriseValidFast();
|
||||
if (new_license != license) {
|
||||
license = new_license;
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
SystemRestore<true>(client, system, dbms_handler, auth);
|
||||
#endif
|
||||
// Check if any database has been left behind
|
||||
dbms_handler.ForEach([&name = client.name_, reconnect](dbms::DatabaseAccess db_acc) {
|
||||
// Specific database <-> replica client
|
||||
db_acc->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) {
|
||||
if (reconnect || client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
// Database <-> replica might be behind, check and recover
|
||||
client->TryCheckReplicaStateAsync(db_acc->storage(), db_acc);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
ReplicationHandler::ReplicationHandler(memgraph::replication::ReplicationState &repl_state,
|
||||
memgraph::dbms::DbmsHandler &dbms_handler, memgraph::system::System *system,
|
||||
memgraph::auth::SynchedAuth &auth)
|
||||
: repl_state_{repl_state}, dbms_handler_{dbms_handler}, system_{system}, auth_{auth} {
|
||||
RecoverReplication(repl_state_, system_, dbms_handler_, auth_);
|
||||
}
|
||||
#else
|
||||
ReplicationHandler::ReplicationHandler(replication::ReplicationState &repl_state, dbms::DbmsHandler &dbms_handler)
|
||||
: repl_state_{repl_state}, dbms_handler_{dbms_handler} {
|
||||
RecoverReplication(repl_state_, dbms_handler_);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ReplicationHandler::SetReplicationRoleMain() {
|
||||
auto const main_handler = [](memgraph::replication::RoleMainData &) {
|
||||
// If we are already MAIN, we don't want to change anything
|
||||
return false;
|
||||
};
|
||||
|
||||
auto const replica_handler = [this](memgraph::replication::RoleReplicaData const &) {
|
||||
return DoReplicaToMainPromotion();
|
||||
};
|
||||
|
||||
// TODO: under lock
|
||||
return std::visit(memgraph::utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
|
||||
}
|
||||
|
||||
bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config) {
|
||||
// We don't want to restart the server if we're already a REPLICA
|
||||
if (repl_state_.IsReplica()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are
|
||||
// deleting the replica.
|
||||
// Remove database specific clients
|
||||
dbms_handler_.ForEach([&](memgraph::dbms::DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
|
||||
});
|
||||
// Remove instance level clients
|
||||
std::get<memgraph::replication::RoleMainData>(repl_state_.ReplicationData()).registered_replicas_.clear();
|
||||
|
||||
// Creates the server
|
||||
repl_state_.SetReplicationRoleReplica(config);
|
||||
|
||||
// Start
|
||||
const auto success =
|
||||
std::visit(memgraph::utils::Overloaded{[](memgraph::replication::RoleMainData const &) {
|
||||
// ASSERT
|
||||
return false;
|
||||
},
|
||||
[this](memgraph::replication::RoleReplicaData const &data) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
return StartRpcServer(dbms_handler_, data, auth_);
|
||||
#else
|
||||
return StartRpcServer(dbms_handler_, data);
|
||||
#endif
|
||||
}},
|
||||
repl_state_.ReplicationData());
|
||||
// TODO Handle error (restore to main?)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ReplicationHandler::DoReplicaToMainPromotion() {
|
||||
// STEP 1) bring down all REPLICA servers
|
||||
dbms_handler_.ForEach([](dbms::DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
// Remember old epoch + storage timestamp association
|
||||
storage->PrepareForNewEpoch();
|
||||
});
|
||||
|
||||
// STEP 2) Change to MAIN
|
||||
// TODO: restore replication servers if false?
|
||||
if (!repl_state_.SetReplicationRoleMain()) {
|
||||
// TODO: Handle recovery on failure???
|
||||
return false;
|
||||
}
|
||||
|
||||
// STEP 3) We are now MAIN, update storage local epoch
|
||||
const auto &epoch = std::get<replication::RoleMainData>(std::as_const(repl_state_).ReplicationData()).epoch_;
|
||||
dbms_handler_.ForEach([&](dbms::DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
storage->repl_storage_state_.epoch_ = epoch;
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// as MAIN, define and connect to REPLICAs
|
||||
auto ReplicationHandler::TryRegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> memgraph::utils::BasicResult<memgraph::query::RegisterReplicaError> {
|
||||
return RegisterReplica_<false>(config);
|
||||
}
|
||||
|
||||
auto ReplicationHandler::RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
|
||||
-> memgraph::utils::BasicResult<memgraph::query::RegisterReplicaError> {
|
||||
return RegisterReplica_<true>(config);
|
||||
}
|
||||
|
||||
auto ReplicationHandler::UnregisterReplica(std::string_view name) -> memgraph::query::UnregisterReplicaResult {
|
||||
auto const replica_handler =
|
||||
[](memgraph::replication::RoleReplicaData const &) -> memgraph::query::UnregisterReplicaResult {
|
||||
return memgraph::query::UnregisterReplicaResult::NOT_MAIN;
|
||||
};
|
||||
auto const main_handler =
|
||||
[this, name](memgraph::replication::RoleMainData &mainData) -> memgraph::query::UnregisterReplicaResult {
|
||||
if (!repl_state_.TryPersistUnregisterReplica(name)) {
|
||||
return memgraph::query::UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
|
||||
}
|
||||
// Remove database specific clients
|
||||
dbms_handler_.ForEach([name](memgraph::dbms::DatabaseAccess db_acc) {
|
||||
db_acc->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) {
|
||||
std::erase_if(clients, [name](const auto &client) { return client->Name() == name; });
|
||||
});
|
||||
});
|
||||
// Remove instance level clients
|
||||
auto const n_unregistered =
|
||||
std::erase_if(mainData.registered_replicas_, [name](auto const &client) { return client.name_ == name; });
|
||||
return n_unregistered != 0 ? memgraph::query::UnregisterReplicaResult::SUCCESS
|
||||
: memgraph::query::UnregisterReplicaResult::CAN_NOT_UNREGISTER;
|
||||
};
|
||||
|
||||
return std::visit(memgraph::utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
|
||||
}
|
||||
|
||||
auto ReplicationHandler::GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole {
|
||||
return repl_state_.GetRole();
|
||||
}
|
||||
|
||||
bool ReplicationHandler::IsMain() const { return repl_state_.IsMain(); }
|
||||
|
||||
bool ReplicationHandler::IsReplica() const { return repl_state_.IsReplica(); }
|
||||
|
||||
} // namespace memgraph::replication
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "replication_handler/system_replication.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "auth/replication_handlers.hpp"
|
||||
#include "dbms/replication_handlers.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "replication_handler/system_rpc.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
void SystemHeartbeatHandler(const uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::SystemHeartbeatRes res{0};
|
||||
|
||||
// Ignore if no license
|
||||
if (!license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
spdlog::error("Handling SystemHeartbeat, an enterprise RPC message, without license.");
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
replication::SystemHeartbeatReq req;
|
||||
replication::SystemHeartbeatReq::Load(&req, req_reader);
|
||||
|
||||
res = replication::SystemHeartbeatRes{ts};
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void SystemRecoveryHandler(memgraph::system::ReplicaHandlerAccessToState &system_state_access,
|
||||
dbms::DbmsHandler &dbms_handler, auth::SynchedAuth &auth, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder) {
|
||||
using memgraph::replication::SystemRecoveryRes;
|
||||
SystemRecoveryRes res(SystemRecoveryRes::Result::FAILURE);
|
||||
|
||||
utils::OnScopeExit send_on_exit([&]() { memgraph::slk::Save(res, res_builder); });
|
||||
|
||||
memgraph::replication::SystemRecoveryReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
/*
|
||||
* DBMS
|
||||
*/
|
||||
if (!dbms::SystemRecoveryHandler(dbms_handler, req.database_configs)) return; // Failure sent on exit
|
||||
|
||||
/*
|
||||
* AUTH
|
||||
*/
|
||||
if (!auth::SystemRecoveryHandler(auth, req.auth_config, req.users, req.roles)) return; // Failure sent on exit
|
||||
|
||||
/*
|
||||
* SUCCESSFUL RECOVERY
|
||||
*/
|
||||
system_state_access.SetLastCommitedTS(req.forced_group_timestamp);
|
||||
spdlog::debug("SystemRecoveryHandler: SUCCESS updated LCTS to {}", req.forced_group_timestamp);
|
||||
res = SystemRecoveryRes(SystemRecoveryRes::Result::SUCCESS);
|
||||
}
|
||||
|
||||
void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler, auth::SynchedAuth &auth) {
|
||||
// NOTE: Register even without license as the user could add a license at run-time
|
||||
// TODO: fix Register when system is removed from DbmsHandler
|
||||
|
||||
auto system_state_access = dbms_handler.system_->CreateSystemStateAccess();
|
||||
|
||||
// System
|
||||
data.server->rpc_server_.Register<replication::SystemHeartbeatRpc>(
|
||||
[system_state_access](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SystemHeartbeatRpc");
|
||||
SystemHeartbeatHandler(system_state_access.LastCommitedTS(), req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<replication::SystemRecoveryRpc>(
|
||||
[system_state_access, &dbms_handler, &auth](auto *req_reader, auto *res_builder) mutable {
|
||||
spdlog::debug("Received SystemRecoveryRpc");
|
||||
SystemRecoveryHandler(system_state_access, dbms_handler, auth, req_reader, res_builder);
|
||||
});
|
||||
|
||||
// DBMS
|
||||
dbms::Register(data, system_state_access, dbms_handler);
|
||||
|
||||
// Auth
|
||||
auth::Register(data, system_state_access, auth);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, const replication::RoleReplicaData &data,
|
||||
auth::SynchedAuth &auth) {
|
||||
#else
|
||||
bool StartRpcServer(dbms::DbmsHandler &dbms_handler, const replication::RoleReplicaData &data) {
|
||||
#endif
|
||||
// Register storage handlers
|
||||
dbms::InMemoryReplicationHandlers::Register(&dbms_handler, *data.server);
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Register system handlers
|
||||
Register(data, dbms_handler, auth);
|
||||
#endif
|
||||
// Start server
|
||||
if (!data.server->Start()) {
|
||||
spdlog::error("Unable to start the replication server.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace memgraph::replication
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "replication_handler/system_rpc.hpp"
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "auth/rpc.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
#include "storage/v2/replication/rpc.hpp"
|
||||
#include "utils/enum.hpp"
|
||||
|
||||
namespace memgraph::slk {
|
||||
// Serialize code for SystemHeartbeatRes
|
||||
void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.system_timestamp, builder);
|
||||
}
|
||||
void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->system_timestamp, reader);
|
||||
}
|
||||
|
||||
// Serialize code for SystemHeartbeatReq
|
||||
void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
|
||||
// Serialize code for SystemRecoveryReq
|
||||
void Save(const memgraph::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.forced_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.database_configs, builder);
|
||||
memgraph::slk::Save(self.auth_config, builder);
|
||||
memgraph::slk::Save(self.users, builder);
|
||||
memgraph::slk::Save(self.roles, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->forced_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->database_configs, reader);
|
||||
memgraph::slk::Load(&self->auth_config, reader);
|
||||
memgraph::slk::Load(&self->users, reader);
|
||||
memgraph::slk::Load(&self->roles, reader);
|
||||
}
|
||||
|
||||
// Serialize code for SystemRecoveryRes
|
||||
void Save(const memgraph::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader) {
|
||||
uint8_t res = 0;
|
||||
memgraph::slk::Load(&res, reader);
|
||||
if (!utils::NumToEnum(res, self->result)) {
|
||||
throw SlkReaderException("Unexpected result line:{}!", __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
constexpr utils::TypeInfo SystemHeartbeatReq::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_REQ, "SystemHeartbeatReq",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo SystemHeartbeatRes::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_RES, "SystemHeartbeatRes",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo SystemRecoveryReq::kType{utils::TypeId::REP_SYSTEM_RECOVERY_REQ, "SystemRecoveryReq",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo SystemRecoveryRes::kType{utils::TypeId::REP_SYSTEM_RECOVERY_RES, "SystemRecoveryRes",
|
||||
nullptr};
|
||||
|
||||
void SystemHeartbeatReq::Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemHeartbeatReq::Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void SystemHeartbeatRes::Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemHeartbeatRes::Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void SystemRecoveryReq::Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemRecoveryReq::Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void SystemRecoveryRes::Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemRecoveryRes::Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
} // namespace memgraph::replication
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
#include "flags/replication.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/storage_mode.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
@@ -129,15 +128,10 @@ struct Config {
|
||||
};
|
||||
|
||||
inline auto ReplicationStateRootPath(memgraph::storage::Config const &config) -> std::optional<std::filesystem::path> {
|
||||
if (!config.durability.restore_replication_state_on_startup
|
||||
#ifdef MG_ENTERPRISE
|
||||
&& !FLAGS_coordinator_server_port
|
||||
#endif
|
||||
) {
|
||||
if (!config.durability.restore_replication_state_on_startup) {
|
||||
spdlog::warn(
|
||||
"Replication configuration will NOT be stored. When the server restarts, replication state will be "
|
||||
"forgotten.");
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
return {config.durability.storage_directory};
|
||||
|
||||
@@ -35,7 +35,6 @@ void ReplicationStorageClient::UpdateReplicaState(Storage *storage, DatabaseAcce
|
||||
|
||||
auto hb_stream{client_.rpc_client_.Stream<replication::HeartbeatRpc>(
|
||||
storage->uuid(), replStorageState.last_commit_timestamp_, std::string{replStorageState.epoch_.id()})};
|
||||
|
||||
const auto replica = hb_stream.AwaitResponse();
|
||||
|
||||
#ifdef MG_ENTERPRISE // Multi-tenancy is only supported in enterprise
|
||||
@@ -68,6 +67,7 @@ void ReplicationStorageClient::UpdateReplicaState(Storage *storage, DatabaseAcce
|
||||
"now hold unique data. Please resolve data conflicts and start the "
|
||||
"replication on a clean instance.",
|
||||
client_.name_, client_.name_, client_.name_);
|
||||
// TODO: (andi) Talk about renaming MAYBE_BEHIND to branching
|
||||
// State not updated, hence in MAYBE_BEHIND state
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,39 @@ void TimestampRes::Save(const TimestampRes &self, memgraph::slk::Builder *builde
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void TimestampRes::Load(TimestampRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
void CreateDatabaseReq::Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void CreateDatabaseReq::Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void CreateDatabaseRes::Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void CreateDatabaseRes::Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void DropDatabaseReq::Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void DropDatabaseReq::Load(DropDatabaseReq *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
void DropDatabaseRes::Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void DropDatabaseRes::Load(DropDatabaseRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
|
||||
void SystemRecoveryReq::Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemRecoveryReq::Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void SystemRecoveryRes::Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemRecoveryRes::Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
} // namespace storage::replication
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::AppendDeltasReq::kType{utils::TypeId::REP_APPEND_DELTAS_REQ,
|
||||
@@ -97,6 +130,24 @@ constexpr utils::TypeInfo storage::replication::TimestampReq::kType{utils::TypeI
|
||||
constexpr utils::TypeInfo storage::replication::TimestampRes::kType{utils::TypeId::REP_TIMESTAMP_RES, "TimestampRes",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::CreateDatabaseReq::kType{utils::TypeId::REP_CREATE_DATABASE_REQ,
|
||||
"CreateDatabaseReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::CreateDatabaseRes::kType{utils::TypeId::REP_CREATE_DATABASE_RES,
|
||||
"CreateDatabaseRes", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::DropDatabaseReq::kType{utils::TypeId::REP_DROP_DATABASE_REQ,
|
||||
"DropDatabaseReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::DropDatabaseRes::kType{utils::TypeId::REP_DROP_DATABASE_RES,
|
||||
"DropDatabaseRes", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::SystemRecoveryReq::kType{utils::TypeId::REP_SYSTEM_RECOVERY_REQ,
|
||||
"SystemRecoveryReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo storage::replication::SystemRecoveryRes::kType{utils::TypeId::REP_SYSTEM_RECOVERY_RES,
|
||||
"SystemRecoveryRes", nullptr};
|
||||
|
||||
// Autogenerated SLK serialization code
|
||||
namespace slk {
|
||||
// Serialize code for TimestampRes
|
||||
@@ -265,5 +316,91 @@ void Load(memgraph::storage::SalientConfig *self, memgraph::slk::Reader *reader)
|
||||
memgraph::slk::Load(&self->items.enable_schema_metadata, reader);
|
||||
}
|
||||
|
||||
// Serialize code for CreateDatabaseReq
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.config, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->config, reader);
|
||||
}
|
||||
|
||||
// Serialize code for CreateDatabaseRes
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader) {
|
||||
uint8_t res = 0;
|
||||
memgraph::slk::Load(&res, reader);
|
||||
if (!utils::NumToEnum(res, self->result)) {
|
||||
throw SlkReaderException("Unexpected result line:{}!", __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize code for DropDatabaseReq
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.epoch_id, builder);
|
||||
memgraph::slk::Save(self.expected_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.new_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.uuid, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->epoch_id, reader);
|
||||
memgraph::slk::Load(&self->expected_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->new_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->uuid, reader);
|
||||
}
|
||||
|
||||
// Serialize code for DropDatabaseRes
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader) {
|
||||
uint8_t res = 0;
|
||||
memgraph::slk::Load(&res, reader);
|
||||
if (!utils::NumToEnum(res, self->result)) {
|
||||
throw SlkReaderException("Unexpected result line:{}!", __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize code for SystemRecoveryReq
|
||||
|
||||
void Save(const memgraph::storage::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.forced_group_timestamp, builder);
|
||||
memgraph::slk::Save(self.database_configs, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->forced_group_timestamp, reader);
|
||||
memgraph::slk::Load(&self->database_configs, reader);
|
||||
}
|
||||
|
||||
// Serialize code for SystemRecoveryRes
|
||||
|
||||
void Save(const memgraph::storage::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::storage::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader) {
|
||||
uint8_t res = 0;
|
||||
memgraph::slk::Load(&res, reader);
|
||||
if (!utils::NumToEnum(res, self->result)) {
|
||||
throw SlkReaderException("Unexpected result line:{}!", __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace slk
|
||||
} // namespace memgraph
|
||||
|
||||
@@ -201,6 +201,108 @@ struct TimestampRes {
|
||||
|
||||
using TimestampRpc = rpc::RequestResponse<TimestampReq, TimestampRes>;
|
||||
|
||||
struct CreateDatabaseReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
CreateDatabaseReq() = default;
|
||||
CreateDatabaseReq(std::string epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp,
|
||||
storage::SalientConfig config)
|
||||
: epoch_id(std::move(epoch_id)),
|
||||
expected_group_timestamp{expected_group_timestamp},
|
||||
new_group_timestamp(new_group_timestamp),
|
||||
config(std::move(config)) {}
|
||||
|
||||
std::string epoch_id;
|
||||
uint64_t expected_group_timestamp;
|
||||
uint64_t new_group_timestamp;
|
||||
storage::SalientConfig config;
|
||||
};
|
||||
|
||||
struct CreateDatabaseRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
|
||||
|
||||
static void Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
CreateDatabaseRes() = default;
|
||||
explicit CreateDatabaseRes(Result res) : result(res) {}
|
||||
|
||||
Result result;
|
||||
};
|
||||
|
||||
using CreateDatabaseRpc = rpc::RequestResponse<CreateDatabaseReq, CreateDatabaseRes>;
|
||||
|
||||
struct DropDatabaseReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(DropDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
DropDatabaseReq() = default;
|
||||
DropDatabaseReq(std::string epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp,
|
||||
const utils::UUID &uuid)
|
||||
: epoch_id(std::move(epoch_id)),
|
||||
expected_group_timestamp{expected_group_timestamp},
|
||||
new_group_timestamp(new_group_timestamp),
|
||||
uuid(uuid) {}
|
||||
|
||||
std::string epoch_id;
|
||||
uint64_t expected_group_timestamp;
|
||||
uint64_t new_group_timestamp;
|
||||
utils::UUID uuid;
|
||||
};
|
||||
|
||||
struct DropDatabaseRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
|
||||
|
||||
static void Load(DropDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
DropDatabaseRes() = default;
|
||||
explicit DropDatabaseRes(Result res) : result(res) {}
|
||||
|
||||
Result result;
|
||||
};
|
||||
|
||||
using DropDatabaseRpc = rpc::RequestResponse<DropDatabaseReq, DropDatabaseRes>;
|
||||
|
||||
struct SystemRecoveryReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder);
|
||||
SystemRecoveryReq() = default;
|
||||
SystemRecoveryReq(uint64_t forced_group_timestamp, std::vector<storage::SalientConfig> database_configs)
|
||||
: forced_group_timestamp{forced_group_timestamp}, database_configs(std::move(database_configs)) {}
|
||||
|
||||
uint64_t forced_group_timestamp;
|
||||
std::vector<storage::SalientConfig> database_configs;
|
||||
};
|
||||
|
||||
struct SystemRecoveryRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N };
|
||||
|
||||
static void Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder);
|
||||
SystemRecoveryRes() = default;
|
||||
explicit SystemRecoveryRes(Result res) : result(res) {}
|
||||
|
||||
Result result;
|
||||
};
|
||||
|
||||
using SystemRecoveryRpc = rpc::RequestResponse<SystemRecoveryReq, SystemRecoveryRes>;
|
||||
|
||||
} // namespace memgraph::storage::replication
|
||||
|
||||
// SLK serialization declarations
|
||||
@@ -254,8 +356,28 @@ void Save(const memgraph::storage::replication::AppendDeltasReq &self, memgraph:
|
||||
|
||||
void Load(memgraph::storage::replication::AppendDeltasReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::SalientConfig &self, memgraph::slk::Builder *builder);
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::SalientConfig *self, memgraph::slk::Reader *reader);
|
||||
void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::storage::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::storage::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
add_library(mg-system STATIC)
|
||||
add_library(mg::system ALIAS mg-system)
|
||||
target_sources(mg-system
|
||||
PUBLIC
|
||||
include/system/action.hpp
|
||||
include/system/system.hpp
|
||||
include/system/transaction.hpp
|
||||
include/system/state.hpp
|
||||
|
||||
PRIVATE
|
||||
action.cpp
|
||||
system.cpp
|
||||
transaction.cpp
|
||||
state.cpp
|
||||
|
||||
)
|
||||
target_include_directories(mg-system PUBLIC include)
|
||||
|
||||
target_link_libraries(mg-system
|
||||
PUBLIC
|
||||
mg::replication
|
||||
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "replication/epoch.hpp"
|
||||
#include "replication/replication_client.hpp"
|
||||
#include "replication/state.hpp"
|
||||
|
||||
namespace memgraph::system {
|
||||
|
||||
struct Transaction;
|
||||
|
||||
/// The system action interface that subsystems will implement. This OO-style separation is needed so that one common
|
||||
/// mechanism can be used for all subsystem replication within a system transaction, without the need for System to
|
||||
/// know about all the subsystems.
|
||||
struct ISystemAction {
|
||||
/// Durability step which is defered until commit time
|
||||
virtual void DoDurability() = 0;
|
||||
|
||||
/// Prepare the RPC payload that will be sent to all replicas clients
|
||||
virtual bool DoReplication(memgraph::replication::ReplicationClient &client,
|
||||
memgraph::replication::ReplicationEpoch const &epoch,
|
||||
Transaction const &system_tx) const = 0;
|
||||
|
||||
virtual void PostReplication(memgraph::replication::RoleMainData &main_data) const = 0;
|
||||
|
||||
virtual ~ISystemAction() = default;
|
||||
};
|
||||
} // namespace memgraph::system
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "utils/file.hpp"
|
||||
|
||||
namespace memgraph::system {
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view kLastCommitedSystemTsKey = "last_committed_system_ts"; // Key for timestamp durability
|
||||
}
|
||||
|
||||
struct State {
|
||||
explicit State(std::optional<std::filesystem::path> storage, bool recovery_on_startup);
|
||||
|
||||
void FinalizeTransaction(std::uint64_t timestamp) {
|
||||
if (durability_) {
|
||||
durability_->Put(kLastCommitedSystemTsKey, std::to_string(timestamp));
|
||||
}
|
||||
last_committed_system_timestamp_.store(timestamp);
|
||||
}
|
||||
|
||||
auto LastCommittedSystemTimestamp() -> uint64_t { return last_committed_system_timestamp_.load(); }
|
||||
|
||||
private:
|
||||
friend struct ReplicaHandlerAccessToState;
|
||||
friend struct Transaction;
|
||||
|
||||
std::optional<kvstore::KVStore> durability_;
|
||||
std::atomic_uint64_t last_committed_system_timestamp_{};
|
||||
};
|
||||
|
||||
struct ReplicaHandlerAccessToState {
|
||||
explicit ReplicaHandlerAccessToState(memgraph::system::State &state) : state_{&state} {}
|
||||
|
||||
auto LastCommitedTS() const -> uint64_t { return state_->last_committed_system_timestamp_.load(); }
|
||||
|
||||
void SetLastCommitedTS(uint64_t new_timestamp) { state_->last_committed_system_timestamp_.store(new_timestamp); }
|
||||
|
||||
private:
|
||||
State *state_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::system
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "system/state.hpp"
|
||||
#include "system/transaction.hpp"
|
||||
|
||||
namespace memgraph::system {
|
||||
|
||||
struct TransactionGuard {
|
||||
explicit TransactionGuard(std::unique_lock<std::timed_mutex> guard) : guard_(std::move(guard)) {}
|
||||
|
||||
private:
|
||||
std::unique_lock<std::timed_mutex> guard_;
|
||||
};
|
||||
|
||||
struct System {
|
||||
// NOTE: default arguments to make testing easier.
|
||||
System(std::optional<std::filesystem::path> storage = std::nullopt, bool recovery_on_startup = false)
|
||||
: state_(std::move(storage), recovery_on_startup), timestamp_{state_.LastCommittedSystemTimestamp()} {}
|
||||
|
||||
auto TryCreateTransaction(std::chrono::microseconds try_time = std::chrono::milliseconds{100})
|
||||
-> std::optional<Transaction> {
|
||||
auto system_unique = std::unique_lock{mtx_, std::defer_lock};
|
||||
if (!system_unique.try_lock_for(try_time)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return Transaction{state_, std::move(system_unique), timestamp_++};
|
||||
}
|
||||
|
||||
// TODO: this and LastCommittedSystemTimestamp maybe not needed
|
||||
auto GenTransactionGuard() -> TransactionGuard { return TransactionGuard{std::unique_lock{mtx_}}; }
|
||||
auto LastCommittedSystemTimestamp() -> uint64_t { return state_.LastCommittedSystemTimestamp(); }
|
||||
|
||||
auto CreateSystemStateAccess() -> ReplicaHandlerAccessToState { return ReplicaHandlerAccessToState{state_}; }
|
||||
|
||||
private:
|
||||
State state_;
|
||||
std::timed_mutex mtx_{};
|
||||
std::uint64_t timestamp_{};
|
||||
};
|
||||
|
||||
} // namespace memgraph::system
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include "replication/state.hpp"
|
||||
#include "system/action.hpp"
|
||||
#include "system/state.hpp"
|
||||
|
||||
namespace memgraph::system {
|
||||
|
||||
enum class AllSyncReplicaStatus : std::uint8_t {
|
||||
AllCommitsConfirmed,
|
||||
SomeCommitsUnconfirmed,
|
||||
};
|
||||
|
||||
struct Transaction;
|
||||
|
||||
template <typename T>
|
||||
concept ReplicationPolicy = requires(T handler, ISystemAction const &action, Transaction const &txn) {
|
||||
{ handler.ApplyAction(action, txn) } -> std::same_as<AllSyncReplicaStatus>;
|
||||
};
|
||||
|
||||
struct System;
|
||||
|
||||
struct Transaction {
|
||||
template <std::derived_from<ISystemAction> TAction, typename... Args>
|
||||
requires std::constructible_from<TAction, Args...>
|
||||
void AddAction(Args &&...args) { actions_.emplace_back(std::make_unique<TAction>(std::forward<Args>(args)...)); }
|
||||
|
||||
template <ReplicationPolicy Handler>
|
||||
auto Commit(Handler handler) -> AllSyncReplicaStatus {
|
||||
if (!lock_.owns_lock() || actions_.empty()) {
|
||||
// If no actions, we do not increment the last commited ts, since there is no delta to send to the REPLICA
|
||||
Abort();
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed; // TODO: some kind of error
|
||||
}
|
||||
|
||||
auto sync_status = AllSyncReplicaStatus::AllCommitsConfirmed;
|
||||
|
||||
while (!actions_.empty()) {
|
||||
auto &action = actions_.front();
|
||||
|
||||
/// durability
|
||||
action->DoDurability();
|
||||
|
||||
/// replication prep
|
||||
auto action_sync_status = handler.ApplyAction(*action, *this);
|
||||
if (action_sync_status != AllSyncReplicaStatus::AllCommitsConfirmed) {
|
||||
sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
|
||||
}
|
||||
|
||||
actions_.pop_front();
|
||||
}
|
||||
|
||||
state_->FinalizeTransaction(timestamp_);
|
||||
lock_.unlock();
|
||||
|
||||
return sync_status;
|
||||
}
|
||||
|
||||
void Abort() {
|
||||
if (lock_.owns_lock()) {
|
||||
lock_.unlock();
|
||||
}
|
||||
actions_.clear();
|
||||
}
|
||||
|
||||
auto last_committed_system_timestamp() const -> uint64_t { return state_->last_committed_system_timestamp_.load(); }
|
||||
auto timestamp() const -> uint64_t { return timestamp_; }
|
||||
|
||||
private:
|
||||
friend struct System;
|
||||
Transaction(State &state, std::unique_lock<std::timed_mutex> lock, std::uint64_t timestamp)
|
||||
: state_{std::addressof(state)}, lock_(std::move(lock)), timestamp_{timestamp} {}
|
||||
|
||||
State *state_;
|
||||
std::unique_lock<std::timed_mutex> lock_;
|
||||
std::uint64_t timestamp_;
|
||||
std::list<std::unique_ptr<ISystemAction>> actions_;
|
||||
};
|
||||
|
||||
struct DoReplication {
|
||||
explicit DoReplication(replication::RoleMainData &main_data) : main_data_{main_data} {}
|
||||
auto ApplyAction(ISystemAction const &action, Transaction const &system_tx) -> AllSyncReplicaStatus {
|
||||
auto sync_status = AllSyncReplicaStatus::AllCommitsConfirmed;
|
||||
|
||||
for (auto &client : main_data_.registered_replicas_) {
|
||||
bool completed = action.DoReplication(client, main_data_.epoch_, system_tx);
|
||||
if (!completed && client.mode_ == replication_coordination_glue::ReplicationMode::SYNC) {
|
||||
sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
|
||||
}
|
||||
}
|
||||
|
||||
action.PostReplication(main_data_);
|
||||
return sync_status;
|
||||
}
|
||||
|
||||
private:
|
||||
replication::RoleMainData &main_data_;
|
||||
};
|
||||
static_assert(ReplicationPolicy<DoReplication>);
|
||||
|
||||
struct DoNothing {
|
||||
auto ApplyAction(ISystemAction const & /*action*/, Transaction const & /*system_tx*/) -> AllSyncReplicaStatus {
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed;
|
||||
}
|
||||
};
|
||||
static_assert(ReplicationPolicy<DoNothing>);
|
||||
|
||||
} // namespace memgraph::system
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "system/state.hpp"
|
||||
|
||||
namespace memgraph::system {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kSystemDir = ".system";
|
||||
constexpr std::string_view kVersion = "version"; // Key for version durability
|
||||
constexpr std::string_view kVersionV1 = "V1"; // Value for version 1
|
||||
|
||||
auto InitializeSystemDurability(std::optional<std::filesystem::path> storage, bool recovery_on_startup)
|
||||
-> std::optional<memgraph::kvstore::KVStore> {
|
||||
if (!storage) return std::nullopt;
|
||||
|
||||
auto const &path = *storage;
|
||||
memgraph::utils::EnsureDir(path);
|
||||
auto system_dir = path / kSystemDir;
|
||||
memgraph::utils::EnsureDir(system_dir);
|
||||
auto durability = memgraph::kvstore::KVStore{std::move(system_dir)};
|
||||
|
||||
auto version = durability.Get(kVersion);
|
||||
// TODO: migration schemes here in the future
|
||||
if (!version || *version != kVersionV1) {
|
||||
// ensure we start out with V1
|
||||
durability.Put(kVersion, kVersionV1);
|
||||
}
|
||||
|
||||
if (!recovery_on_startup) {
|
||||
// reset last_committed_system_ts
|
||||
durability.Delete(kLastCommitedSystemTsKey);
|
||||
}
|
||||
|
||||
return durability;
|
||||
}
|
||||
|
||||
auto LoadLastCommittedSystemTimestamp(std::optional<kvstore::KVStore> const &store) -> uint64_t {
|
||||
auto lcst = store ? store->Get(kLastCommitedSystemTsKey) : std::nullopt;
|
||||
return lcst ? std::stoul(*lcst) : 0U;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
State::State(std::optional<std::filesystem::path> storage, bool recovery_on_startup)
|
||||
: durability_{InitializeSystemDurability(std::move(storage), recovery_on_startup)},
|
||||
last_committed_system_timestamp_{LoadLastCommittedSystemTimestamp(durability_)} {}
|
||||
} // namespace memgraph::system
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
#include "system/include/system/transaction.hpp"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user