Compare commits
124 Commits
gc-proacti
...
ci-in-dock
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3086e8569a | ||
|
|
8e27ce9fc6 | ||
|
|
de81eb3d98 | ||
|
|
741cc7091a | ||
|
|
08021ecf01 | ||
|
|
8634ffd953 | ||
|
|
15ffc6422f | ||
|
|
a56a6116ed | ||
|
|
1997eddc8e | ||
|
|
4cdb536c3f | ||
|
|
cae54f5308 | ||
|
|
d89d53712c | ||
|
|
d5a06f7539 | ||
|
|
71c035b5a6 | ||
|
|
7179ced028 | ||
|
|
99d9675e08 | ||
|
|
c6839fdfa0 | ||
|
|
5af434d5df | ||
|
|
935fda33df | ||
|
|
15676f2557 | ||
|
|
efdd450b09 | ||
|
|
ce665d2c1b | ||
|
|
b443934b68 | ||
|
|
dd53f8d6b1 | ||
|
|
74fa6184b1 | ||
|
|
892f948866 | ||
|
|
6ab4235cc9 | ||
|
|
a9ef28c68e | ||
|
|
79361e9205 | ||
|
|
97b1e67d80 | ||
|
|
78a88737f8 | ||
|
|
ff44d68843 | ||
|
|
f1484240a0 | ||
|
|
1c95c3dc59 | ||
|
|
9f7118d893 | ||
|
|
38ade99652 | ||
|
|
6706ebfa2b | ||
|
|
e7f6a5f4f4 | ||
|
|
071df2f439 | ||
|
|
7f10636470 | ||
|
|
76589903a4 | ||
|
|
a8b625d861 | ||
|
|
9c89fce249 | ||
|
|
5e5f215be4 | ||
|
|
23dff58d22 | ||
|
|
0a7a7bc0d1 | ||
|
|
c772cab766 | ||
|
|
2e4d27c59a | ||
|
|
31f15b3651 | ||
|
|
b3d0c2ccc2 | ||
|
|
d4bcdb77ad | ||
|
|
1ba2f4e619 | ||
|
|
4bb9238679 | ||
|
|
bd11266f82 | ||
|
|
57e40a2b18 | ||
|
|
0fb8e4116f | ||
|
|
7128e1cea8 | ||
|
|
4788a633a6 | ||
|
|
ce2705d012 | ||
|
|
686fadf072 | ||
|
|
9e76021b94 | ||
|
|
cd37de481e | ||
|
|
f11b3c6d9d | ||
|
|
4ef86efb6f | ||
|
|
04fb92dce8 | ||
|
|
71e76cc980 | ||
|
|
cb4d4db813 | ||
|
|
39ee248d34 | ||
|
|
b35df12c1a | ||
|
|
21bbc196ae | ||
|
|
375c3c5ddd | ||
|
|
340057f959 | ||
|
|
e56e516f94 | ||
|
|
7a9c4f5ec4 | ||
|
|
eceed274d9 | ||
|
|
74fa6d21f6 | ||
|
|
d836b38a8b | ||
|
|
eeb9671bac | ||
|
|
e716c90031 | ||
|
|
9690682bc2 | ||
|
|
64e5428d94 | ||
|
|
66e86c060f | ||
|
|
953a8f5340 | ||
|
|
31efe28878 | ||
|
|
0fb3ae2d56 | ||
|
|
46bfeb0023 | ||
|
|
d58a464141 | ||
|
|
997779fe07 | ||
|
|
3ccd78ac71 | ||
|
|
14f92b4a0f | ||
|
|
9f555cf93d | ||
|
|
7fc9b89634 | ||
|
|
ed5bdb841f | ||
|
|
b74aee186e | ||
|
|
ac0c4193b0 | ||
|
|
9868dee73b | ||
|
|
bb2a7b8f21 | ||
|
|
6680f99e4d | ||
|
|
72d47fc3bf | ||
|
|
3cf13e2deb | ||
|
|
7f5a55f1b2 | ||
|
|
08acde3973 | ||
|
|
70db2fca56 | ||
|
|
8b9e1fa08b | ||
|
|
e4f94c15c6 | ||
|
|
0ce6dcf194 | ||
|
|
1d90b60f56 | ||
|
|
108d83cc8c | ||
|
|
eff857447a | ||
|
|
d03fafcef6 | ||
|
|
c31a7f9648 | ||
|
|
6053a91ef8 | ||
|
|
645568a75b | ||
|
|
d3f4c35362 | ||
|
|
c037cddb0e | ||
|
|
ced08fd7bc | ||
|
|
a370b09d12 | ||
|
|
1527bdf435 | ||
|
|
df3274d78f | ||
|
|
2946d74fdd | ||
|
|
eb4ebab438 | ||
|
|
260d60451d | ||
|
|
c4d9116c9c | ||
|
|
50c485fe40 |
2
.github/workflows/daily_benchmark.yaml
vendored
2
.github/workflows/daily_benchmark.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
|
|||||||
172
.github/workflows/diff.yaml
vendored
172
.github/workflows/diff.yaml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
# pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "**/*.md"
|
- "**/*.md"
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Python code analysis
|
- name: Python code analysis
|
||||||
run: |
|
run: |
|
||||||
CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only)
|
CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only --diff-filter=d)
|
||||||
for file in ${CHANGED_FILES}; do
|
for file in ${CHANGED_FILES}; do
|
||||||
echo ${file}
|
echo ${file}
|
||||||
if [[ ${file} == *.py ]]; then
|
if [[ ${file} == *.py ]]; then
|
||||||
@@ -137,9 +137,9 @@ jobs:
|
|||||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||||
|
|
||||||
- name: Save code coverage
|
- name: Save code coverage
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Code coverage"
|
name: "Code coverage(Code analysis)"
|
||||||
path: tools/github/generated/code_coverage.tar.gz
|
path: tools/github/generated/code_coverage.tar.gz
|
||||||
|
|
||||||
- name: Run clang-tidy
|
- name: Run clang-tidy
|
||||||
@@ -162,7 +162,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -208,9 +208,9 @@ jobs:
|
|||||||
./cppcheck_and_clang_format diff
|
./cppcheck_and_clang_format diff
|
||||||
|
|
||||||
- name: Save cppcheck and clang-format errors
|
- name: Save cppcheck and clang-format errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Code coverage"
|
name: "Code coverage(Debug build)"
|
||||||
path: tools/github/cppcheck_and_clang_format.txt
|
path: tools/github/cppcheck_and_clang_format.txt
|
||||||
|
|
||||||
release_build:
|
release_build:
|
||||||
@@ -223,16 +223,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Check e2e service dependencies
|
|
||||||
run: |
|
|
||||||
cd tests/e2e
|
|
||||||
./dependency_check.sh
|
|
||||||
|
|
||||||
- name: Build release binaries
|
- name: Build release binaries
|
||||||
run: |
|
run: |
|
||||||
@@ -255,7 +251,7 @@ jobs:
|
|||||||
./continuous_integration
|
./continuous_integration
|
||||||
|
|
||||||
- name: Save quality assurance status
|
- name: Save quality assurance status
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "GQL Behave Status"
|
name: "GQL Behave Status"
|
||||||
path: |
|
path: |
|
||||||
@@ -271,7 +267,16 @@ jobs:
|
|||||||
cd build
|
cd build
|
||||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
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
|
||||||
|
cd ../pulsar
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
- name: Run e2e tests
|
- name: Run e2e tests
|
||||||
|
if: false
|
||||||
run: |
|
run: |
|
||||||
cd tests
|
cd tests
|
||||||
./setup.sh /opt/toolchain-v4/activate
|
./setup.sh /opt/toolchain-v4/activate
|
||||||
@@ -279,6 +284,14 @@ jobs:
|
|||||||
cd e2e
|
cd e2e
|
||||||
./run.sh
|
./run.sh
|
||||||
|
|
||||||
|
- name: Ensure Kafka and Pulsar are down
|
||||||
|
if: false
|
||||||
|
run: |
|
||||||
|
cd tests/e2e/streams/kafka
|
||||||
|
docker-compose down
|
||||||
|
cd ../pulsar
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
- name: Run stress test (plain)
|
- name: Run stress test (plain)
|
||||||
run: |
|
run: |
|
||||||
cd tests/stress
|
cd tests/stress
|
||||||
@@ -312,16 +325,128 @@ jobs:
|
|||||||
cpack -G DEB --config ../CPackConfig.cmake
|
cpack -G DEB --config ../CPackConfig.cmake
|
||||||
|
|
||||||
- name: Save enterprise DEB package
|
- name: Save enterprise DEB package
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Enterprise DEB package"
|
name: "Enterprise DEB package"
|
||||||
path: build/output/memgraph*.deb
|
path: build/output/memgraph*.deb
|
||||||
|
|
||||||
- name: Save test data
|
- name: Save test data
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: "Test data"
|
name: "Test data(Release build)"
|
||||||
|
path: |
|
||||||
|
# multiple paths could be defined
|
||||||
|
build/logs
|
||||||
|
|
||||||
|
experimental_build_ha:
|
||||||
|
name: "High availability build"
|
||||||
|
runs-on: [self-hosted, Linux, X64, Diff]
|
||||||
|
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
|
||||||
|
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: |
|
||||||
|
source /opt/toolchain-v4/activate
|
||||||
|
./init
|
||||||
|
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
|
||||||
|
if: false
|
||||||
|
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@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: "Test data(High availability build)"
|
||||||
|
path: |
|
||||||
|
# multiple paths could be defined
|
||||||
|
build/logs
|
||||||
|
|
||||||
|
experimental_build_mt:
|
||||||
|
name: "MultiTenancy replication build"
|
||||||
|
runs-on: [self-hosted, Linux, X64, Diff]
|
||||||
|
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
|
||||||
|
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 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
|
||||||
|
if: false
|
||||||
|
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@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: "Test data(MultiTenancy replication build)"
|
||||||
path: |
|
path: |
|
||||||
# multiple paths could be defined
|
# multiple paths could be defined
|
||||||
build/logs
|
build/logs
|
||||||
@@ -337,7 +462,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -354,13 +479,18 @@ jobs:
|
|||||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
|
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
|
||||||
make -j$THREADS memgraph
|
make -j$THREADS memgraph
|
||||||
|
|
||||||
|
- name: Refresh Jepsen Cluster
|
||||||
|
run: |
|
||||||
|
cd tests/jepsen
|
||||||
|
./run.sh cluster-refresh
|
||||||
|
|
||||||
- name: Run Jepsen tests
|
- name: Run Jepsen tests
|
||||||
run: |
|
run: |
|
||||||
cd tests/jepsen
|
cd tests/jepsen
|
||||||
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
|
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||||
|
|
||||||
- name: Save Jepsen report
|
- name: Save Jepsen report
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: "Jepsen Report"
|
name: "Jepsen Report"
|
||||||
@@ -376,7 +506,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# 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:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
|
|||||||
263
.github/workflows/package_all.yaml
vendored
263
.github/workflows/package_all.yaml
vendored
@@ -1,263 +0,0 @@
|
|||||||
name: Package All
|
|
||||||
|
|
||||||
# TODO(gitbuda): Cleanup docker container if GHA job was canceled.
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
memgraph_version:
|
|
||||||
description: "Memgraph version to upload as. If empty upload is skipped. Format: 'X.Y.Z'"
|
|
||||||
required: false
|
|
||||||
build_type:
|
|
||||||
type: choice
|
|
||||||
description: "Memgraph Build type. Default value is Release."
|
|
||||||
default: 'Release'
|
|
||||||
options:
|
|
||||||
- Release
|
|
||||||
- RelWithDebInfo
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
centos-7:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: centos-7
|
|
||||||
path: build/output/centos-7/memgraph*.rpm
|
|
||||||
|
|
||||||
centos-9:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: centos-9
|
|
||||||
path: build/output/centos-9/memgraph*.rpm
|
|
||||||
|
|
||||||
debian-10:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: debian-10
|
|
||||||
path: build/output/debian-10/memgraph*.deb
|
|
||||||
|
|
||||||
debian-11:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: debian-11
|
|
||||||
path: build/output/debian-11/memgraph*.deb
|
|
||||||
|
|
||||||
docker:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
|
||||||
- name: "Build package"
|
|
||||||
run: |
|
|
||||||
cd release/package
|
|
||||||
./run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-docker
|
|
||||||
./run.sh docker
|
|
||||||
- name: "Upload package"
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: docker
|
|
||||||
path: build/output/docker/memgraph*.tar.gz
|
|
||||||
|
|
||||||
ubuntu-1804:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: ubuntu-18.04
|
|
||||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
|
||||||
|
|
||||||
ubuntu-2004:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: ubuntu-20.04
|
|
||||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
|
||||||
|
|
||||||
ubuntu-2204:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: ubuntu-22.04
|
|
||||||
path: build/output/ubuntu-22.04/memgraph*.deb
|
|
||||||
|
|
||||||
debian-11-platform:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: debian-11-platform
|
|
||||||
path: build/output/debian-11/memgraph*.deb
|
|
||||||
|
|
||||||
fedora-36:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: fedora-36
|
|
||||||
path: build/output/fedora-36/memgraph*.rpm
|
|
||||||
|
|
||||||
amzn-2:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: amzn-2
|
|
||||||
path: build/output/amzn-2/memgraph*.rpm
|
|
||||||
|
|
||||||
debian-11-arm:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
|
||||||
timeout-minutes: 120
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: debian-11-aarch64
|
|
||||||
path: build/output/debian-11-arm/memgraph*.deb
|
|
||||||
|
|
||||||
ubuntu-2204-arm:
|
|
||||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
|
||||||
timeout-minutes: 120
|
|
||||||
steps:
|
|
||||||
- name: "Set up repository"
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
name: ubuntu-22.04-aarch64
|
|
||||||
path: build/output/ubuntu-22.04-arm/memgraph*.deb
|
|
||||||
|
|
||||||
upload-to-s3:
|
|
||||||
# only run upload if we specified version. Allows for runs without upload
|
|
||||||
if: "${{ github.event.inputs.memgraph_version != '' }}"
|
|
||||||
needs: [centos-7, centos-9, debian-10, debian-11, docker, ubuntu-1804, ubuntu-2004, ubuntu-2204, debian-11-platform, fedora-36, amzn-2, debian-11-arm, ubuntu-2204-arm]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
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.
|
|
||||||
path: build/output/release
|
|
||||||
- name: Upload to S3
|
|
||||||
uses: jakejarvis/s3-sync-action@v0.5.1
|
|
||||||
env:
|
|
||||||
AWS_S3_BUCKET: "download.memgraph.com"
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.S3_AWS_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_REGION: "eu-west-1"
|
|
||||||
SOURCE_DIR: "build/output/release"
|
|
||||||
DEST_DIR: "memgraph/v${{ github.event.inputs.memgraph_version }}/"
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Package Specific
|
name: Package memgraph
|
||||||
|
|
||||||
# TODO(gitbuda): Cleanup docker container if GHA job was canceled.
|
# TODO(gitbuda): Cleanup docker container if GHA job was canceled.
|
||||||
|
|
||||||
@@ -10,16 +10,17 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
build_type:
|
build_type:
|
||||||
type: choice
|
type: choice
|
||||||
description: "Memgraph Build type. Default value is Release."
|
description: "Memgraph Build type. Default value is Release"
|
||||||
default: 'Release'
|
default: 'Release'
|
||||||
options:
|
options:
|
||||||
- Release
|
- Release
|
||||||
- RelWithDebInfo
|
- RelWithDebInfo
|
||||||
target_os:
|
target_os:
|
||||||
type: choice
|
type: choice
|
||||||
description: "Target OS for which memgraph will be packaged. Default is Ubuntu 22.04"
|
description: "Target OS for which memgraph will be packaged. Select 'all' if you want to package for every listed OS. Default is Ubuntu 22.04"
|
||||||
default: 'ubuntu-22_04'
|
default: 'ubuntu-22_04'
|
||||||
options:
|
options:
|
||||||
|
- all
|
||||||
- amzn-2
|
- amzn-2
|
||||||
- centos-7
|
- centos-7
|
||||||
- centos-9
|
- centos-9
|
||||||
@@ -36,138 +37,138 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
amzn-2:
|
amzn-2:
|
||||||
if: ${{ github.event.inputs.target_os == 'amzn-2' }}
|
if: ${{ github.event.inputs.target_os == 'amzn-2' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package amzn-2 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package amzn-2 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: amzn-2
|
name: amzn-2
|
||||||
path: build/output/amzn-2/memgraph*.rpm
|
path: build/output/amzn-2/memgraph*.rpm
|
||||||
|
|
||||||
centos-7:
|
centos-7:
|
||||||
if: ${{ github.event.inputs.target_os == 'centos-7' }}
|
if: ${{ github.event.inputs.target_os == 'centos-7' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package centos-7 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package centos-7 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: centos-7
|
name: centos-7
|
||||||
path: build/output/centos-7/memgraph*.rpm
|
path: build/output/centos-7/memgraph*.rpm
|
||||||
|
|
||||||
centos-9:
|
centos-9:
|
||||||
if: ${{ github.event.inputs.target_os == 'centos-9' }}
|
if: ${{ github.event.inputs.target_os == 'centos-9' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package centos-9 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package centos-9 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: centos-9
|
name: centos-9
|
||||||
path: build/output/centos-9/memgraph*.rpm
|
path: build/output/centos-9/memgraph*.rpm
|
||||||
|
|
||||||
debian-10:
|
debian-10:
|
||||||
if: ${{ github.event.inputs.target_os == 'debian-10' }}
|
if: ${{ github.event.inputs.target_os == 'debian-10' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package debian-10 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package debian-10 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debian-10
|
name: debian-10
|
||||||
path: build/output/debian-10/memgraph*.deb
|
path: build/output/debian-10/memgraph*.deb
|
||||||
|
|
||||||
debian-11:
|
debian-11:
|
||||||
if: ${{ github.event.inputs.target_os == 'debian-11' }}
|
if: ${{ github.event.inputs.target_os == 'debian-11' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debian-11
|
name: debian-11
|
||||||
path: build/output/debian-11/memgraph*.deb
|
path: build/output/debian-11/memgraph*.deb
|
||||||
|
|
||||||
debian-11-arm:
|
debian-11-arm:
|
||||||
if: ${{ github.event.inputs.target_os == 'debian-11-arm' }}
|
if: ${{ github.event.inputs.target_os == 'debian-11-arm' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package debian-11-arm ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package debian-11-arm ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debian-11-aarch64
|
name: debian-11-aarch64
|
||||||
path: build/output/debian-11-arm/memgraph*.deb
|
path: build/output/debian-11-arm/memgraph*.deb
|
||||||
|
|
||||||
debian-11-platform:
|
debian-11-platform:
|
||||||
if: ${{ github.event.inputs.target_os == 'debian-11-platform' }}
|
if: ${{ github.event.inputs.target_os == 'debian-11-platform' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-platform
|
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-platform
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: debian-11-platform
|
name: debian-11-platform
|
||||||
path: build/output/debian-11/memgraph*.deb
|
path: build/output/debian-11/memgraph*.deb
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
if: ${{ github.event.inputs.target_os == 'docker' }}
|
if: ${{ github.event.inputs.target_os == 'docker' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
@@ -176,97 +177,97 @@ jobs:
|
|||||||
./run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-docker
|
./run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-docker
|
||||||
./run.sh docker
|
./run.sh docker
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: docker
|
name: docker
|
||||||
path: build/output/docker/memgraph*.tar.gz
|
path: build/output/docker/memgraph*.tar.gz
|
||||||
|
|
||||||
fedora-36:
|
fedora-36:
|
||||||
if: ${{ github.event.inputs.target_os == 'fedora-36' }}
|
if: ${{ github.event.inputs.target_os == 'fedora-36' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package fedora-36 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package fedora-36 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: fedora-36
|
name: fedora-36
|
||||||
path: build/output/fedora-36/memgraph*.rpm
|
path: build/output/fedora-36/memgraph*.rpm
|
||||||
|
|
||||||
ubuntu-18_04:
|
ubuntu-18_04:
|
||||||
if: ${{ github.event.inputs.target_os == 'ubuntu-18_04' }}
|
if: ${{ github.event.inputs.target_os == 'ubuntu-18_04' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package ubuntu-18.04 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package ubuntu-18.04 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ubuntu-18.04
|
name: ubuntu-18.04
|
||||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
path: build/output/ubuntu-18.04/memgraph*.deb
|
||||||
|
|
||||||
ubuntu-20_04:
|
ubuntu-20_04:
|
||||||
if: ${{ github.event.inputs.target_os == 'ubuntu-20_04' }}
|
if: ${{ github.event.inputs.target_os == 'ubuntu-20_04' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package ubuntu-20.04 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package ubuntu-20.04 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ubuntu-20.04
|
name: ubuntu-20.04
|
||||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
path: build/output/ubuntu-20.04/memgraph*.deb
|
||||||
|
|
||||||
ubuntu-22_04:
|
ubuntu-22_04:
|
||||||
if: ${{ github.event.inputs.target_os == 'ubuntu-22_04' }}
|
if: ${{ github.event.inputs.target_os == 'ubuntu-22_04' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, X64]
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package ubuntu-22.04 ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package ubuntu-22.04 ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ubuntu-22.04
|
name: ubuntu-22.04
|
||||||
path: build/output/ubuntu-22.04/memgraph*.deb
|
path: build/output/ubuntu-22.04/memgraph*.deb
|
||||||
|
|
||||||
ubuntu-22_04-arm:
|
ubuntu-22_04-arm:
|
||||||
if: ${{ github.event.inputs.target_os == 'ubuntu-22_04-arm' }}
|
if: ${{ github.event.inputs.target_os == 'ubuntu-22_04-arm' || github.event.inputs.target_os == 'all' }}
|
||||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up repository"
|
- name: "Set up repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Required because of release/get_version.py
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
- name: "Build package"
|
- name: "Build package"
|
||||||
run: |
|
run: |
|
||||||
./release/package/run.sh package ubuntu-22.04-arm ${{ github.event.inputs.build_type }}
|
./release/package/run.sh package ubuntu-22.04-arm ${{ github.event.inputs.build_type }}
|
||||||
- name: "Upload package"
|
- name: "Upload package"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ubuntu-22.04-aarch64
|
name: ubuntu-22.04-aarch64
|
||||||
path: build/output/ubuntu-22.04-arm/memgraph*.deb
|
path: build/output/ubuntu-22.04-arm/memgraph*.deb
|
||||||
@@ -278,7 +279,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
# name: # if name input parameter is not provided, all artifacts are downloaded
|
# name: # if name input parameter is not provided, all artifacts are downloaded
|
||||||
# and put in directories named after each one.
|
# and put in directories named after each one.
|
||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
|
|||||||
318
.github/workflows/release_centos8.yaml
vendored
318
.github/workflows/release_centos8.yaml
vendored
@@ -1,318 +0,0 @@
|
|||||||
name: Release CentOS 8
|
|
||||||
|
|
||||||
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 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
community_build:
|
|
||||||
name: "Community build"
|
|
||||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
|
||||||
# branches and tags. (default: 1)
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Build community binaries
|
|
||||||
run: |
|
|
||||||
# Activate toolchain.
|
|
||||||
source /opt/toolchain-v4/activate
|
|
||||||
|
|
||||||
# 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 ..
|
|
||||||
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
|
|
||||||
|
|
||||||
coverage_build:
|
|
||||||
name: "Coverage build"
|
|
||||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
|
||||||
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: Build coverage binaries
|
|
||||||
run: |
|
|
||||||
# Activate toolchain.
|
|
||||||
source /opt/toolchain-v4/activate
|
|
||||||
|
|
||||||
# Initialize dependencies.
|
|
||||||
./init
|
|
||||||
|
|
||||||
# Build coverage binaries.
|
|
||||||
cd build
|
|
||||||
cmake -DTEST_COVERAGE=ON ..
|
|
||||||
make -j$THREADS memgraph__unit
|
|
||||||
|
|
||||||
- name: Run unit tests
|
|
||||||
run: |
|
|
||||||
# Activate toolchain.
|
|
||||||
source /opt/toolchain-v4/activate
|
|
||||||
|
|
||||||
# Run unit tests.
|
|
||||||
cd build
|
|
||||||
ctest -R memgraph__unit --output-on-failure
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
debug_build:
|
|
||||||
name: "Debug build"
|
|
||||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
|
||||||
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: 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 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, CentOS8]
|
|
||||||
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@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
|
|
||||||
|
|
||||||
# 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 ..
|
|
||||||
make -j$THREADS
|
|
||||||
|
|
||||||
- name: Create enterprise RPM 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 RPM package.
|
|
||||||
mkdir output && cd output
|
|
||||||
cpack -G RPM --config ../CPackConfig.cmake
|
|
||||||
rpmlint memgraph*.rpm
|
|
||||||
|
|
||||||
- name: Save enterprise RPM package
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: "Enterprise RPM package"
|
|
||||||
path: build/output/memgraph*.rpm
|
|
||||||
|
|
||||||
- name: Run micro benchmark tests
|
|
||||||
run: |
|
|
||||||
# Activate toolchain.
|
|
||||||
source /opt/toolchain-v4/activate
|
|
||||||
|
|
||||||
# Run micro benchmark tests.
|
|
||||||
cd build
|
|
||||||
# The `eval` benchmark needs a large stack limit.
|
|
||||||
ulimit -s 262144
|
|
||||||
ctest -R memgraph__benchmark -V
|
|
||||||
|
|
||||||
- name: Run macro benchmark tests
|
|
||||||
run: |
|
|
||||||
cd tests/macro_benchmark
|
|
||||||
./harness QuerySuite MemgraphRunner \
|
|
||||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
|
||||||
--no-strict
|
|
||||||
|
|
||||||
- name: Run parallel macro benchmark tests
|
|
||||||
run: |
|
|
||||||
cd tests/macro_benchmark
|
|
||||||
./harness QueryParallelSuite MemgraphRunner \
|
|
||||||
--groups aggregation_parallel create_parallel bfs_parallel \
|
|
||||||
--num-database-workers 9 --num-clients-workers 30 \
|
|
||||||
--no-strict
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- name: Run e2e tests
|
|
||||||
run: |
|
|
||||||
cd tests
|
|
||||||
./setup.sh /opt/toolchain-v4/activate
|
|
||||||
source ve3/bin/activate_e2e
|
|
||||||
cd e2e
|
|
||||||
./run.sh
|
|
||||||
|
|
||||||
- name: Run stress test (plain)
|
|
||||||
run: |
|
|
||||||
cd tests/stress
|
|
||||||
./continuous_integration
|
|
||||||
|
|
||||||
- name: Run stress test (SSL)
|
|
||||||
run: |
|
|
||||||
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
|
|
||||||
source ve3/bin/activate
|
|
||||||
python3 durability --num-steps 5
|
|
||||||
|
|
||||||
- name: Run durability test (large)
|
|
||||||
run: |
|
|
||||||
cd tests/stress
|
|
||||||
source ve3/bin/activate
|
|
||||||
python3 durability --num-steps 20
|
|
||||||
228
.github/workflows/release_debian10.yaml
vendored
228
.github/workflows/release_debian10.yaml
vendored
@@ -14,19 +14,21 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: "0 22 * * *"
|
- 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:
|
jobs:
|
||||||
community_build:
|
community_build:
|
||||||
name: "Community build"
|
name: "Community build"
|
||||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||||
env:
|
timeout-minutes: 60
|
||||||
THREADS: 24
|
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
|
||||||
timeout-minutes: 960
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -40,10 +42,6 @@ jobs:
|
|||||||
# Initialize dependencies.
|
# Initialize dependencies.
|
||||||
./init
|
./init
|
||||||
|
|
||||||
# Set default build_type to Release
|
|
||||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
|
||||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
|
||||||
|
|
||||||
# Build community binaries.
|
# Build community binaries.
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
|
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
|
||||||
@@ -65,10 +63,11 @@ jobs:
|
|||||||
THREADS: 24
|
THREADS: 24
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||||
|
timeout-minutes: 60
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -110,22 +109,19 @@ jobs:
|
|||||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||||
|
|
||||||
- name: Save code coverage
|
- name: Save code coverage
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Code coverage"
|
name: "Code coverage(Coverage build)"
|
||||||
path: tools/github/generated/code_coverage.tar.gz
|
path: tools/github/generated/code_coverage.tar.gz
|
||||||
|
|
||||||
debug_build:
|
debug_build:
|
||||||
name: "Debug build"
|
name: "Debug build"
|
||||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||||
env:
|
timeout-minutes: 60
|
||||||
THREADS: 24
|
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -157,10 +153,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./tests/drivers/run.sh
|
./tests/drivers/run.sh
|
||||||
|
|
||||||
- name: Run integration tests
|
|
||||||
run: |
|
|
||||||
tests/integration/run.sh
|
|
||||||
|
|
||||||
- name: Run cppcheck and clang-format
|
- name: Run cppcheck and clang-format
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
@@ -171,23 +163,49 @@ jobs:
|
|||||||
./cppcheck_and_clang_format diff
|
./cppcheck_and_clang_format diff
|
||||||
|
|
||||||
- name: Save cppcheck and clang-format errors
|
- name: Save cppcheck and clang-format errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Code coverage"
|
name: "Code coverage(Debug build)"
|
||||||
path: tools/github/cppcheck_and_clang_format.txt
|
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:
|
release_build:
|
||||||
name: "Release build"
|
name: "Release build"
|
||||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||||
env:
|
timeout-minutes: 60
|
||||||
THREADS: 24
|
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
|
||||||
timeout-minutes: 960
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -201,10 +219,6 @@ jobs:
|
|||||||
# Initialize dependencies.
|
# Initialize dependencies.
|
||||||
./init
|
./init
|
||||||
|
|
||||||
# Set default build_type to Release
|
|
||||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
|
||||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
|
||||||
|
|
||||||
# Build release binaries.
|
# Build release binaries.
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||||
@@ -226,11 +240,60 @@ jobs:
|
|||||||
cpack -G DEB --config ../CPackConfig.cmake
|
cpack -G DEB --config ../CPackConfig.cmake
|
||||||
|
|
||||||
- name: Save enterprise DEB package
|
- name: Save enterprise DEB package
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Enterprise DEB package"
|
name: "Enterprise DEB package"
|
||||||
path: build/output/memgraph*.deb
|
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
|
- name: Run micro benchmark tests
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
@@ -257,29 +320,38 @@ jobs:
|
|||||||
--num-database-workers 9 --num-clients-workers 30 \
|
--num-database-workers 9 --num-clients-workers 30 \
|
||||||
--no-strict
|
--no-strict
|
||||||
|
|
||||||
- name: Run GQL Behave tests
|
release_e2e_test:
|
||||||
run: |
|
if: false
|
||||||
cd tests
|
name: "Release End-to-end Test"
|
||||||
./setup.sh /opt/toolchain-v4/activate
|
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||||
cd gql_behave
|
timeout-minutes: 60
|
||||||
./continuous_integration
|
|
||||||
|
|
||||||
- name: Save quality assurance status
|
steps:
|
||||||
uses: actions/upload-artifact@v3
|
- name: Set up repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
name: "GQL Behave Status"
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
path: |
|
# branches and tags. (default: 1)
|
||||||
tests/gql_behave/gql_behave_status.csv
|
fetch-depth: 0
|
||||||
tests/gql_behave/gql_behave_status.html
|
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Build release binaries
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
source /opt/toolchain-v4/activate
|
source /opt/toolchain-v4/activate
|
||||||
|
# Initialize dependencies.
|
||||||
|
./init
|
||||||
|
|
||||||
# Run unit tests.
|
# Build release binaries
|
||||||
cd build
|
cd build
|
||||||
ctest -R memgraph__unit --output-on-failure
|
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||||
|
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
|
- name: Run e2e tests
|
||||||
run: |
|
run: |
|
||||||
@@ -289,6 +361,40 @@ jobs:
|
|||||||
cd e2e
|
cd e2e
|
||||||
./run.sh
|
./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
|
||||||
|
|
||||||
|
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)
|
- name: Run stress test (plain)
|
||||||
run: |
|
run: |
|
||||||
cd tests/stress
|
cd tests/stress
|
||||||
@@ -299,11 +405,6 @@ jobs:
|
|||||||
cd tests/stress
|
cd tests/stress
|
||||||
./continuous_integration --use-ssl
|
./continuous_integration --use-ssl
|
||||||
|
|
||||||
- name: Run stress test (large)
|
|
||||||
run: |
|
|
||||||
cd tests/stress
|
|
||||||
./continuous_integration --large-dataset
|
|
||||||
|
|
||||||
- name: Run durability test (plain)
|
- name: Run durability test (plain)
|
||||||
run: |
|
run: |
|
||||||
cd tests/stress
|
cd tests/stress
|
||||||
@@ -319,15 +420,11 @@ jobs:
|
|||||||
release_jepsen_test:
|
release_jepsen_test:
|
||||||
name: "Release Jepsen Test"
|
name: "Release Jepsen Test"
|
||||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
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
|
timeout-minutes: 60
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -340,22 +437,23 @@ jobs:
|
|||||||
# Initialize dependencies.
|
# Initialize dependencies.
|
||||||
./init
|
./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.
|
# Build only memgraph release binary.
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||||
make -j$THREADS memgraph
|
make -j$THREADS memgraph
|
||||||
|
|
||||||
|
- name: Refresh Jepsen Cluster
|
||||||
|
run: |
|
||||||
|
cd tests/jepsen
|
||||||
|
./run.sh cluster-refresh
|
||||||
|
|
||||||
- name: Run Jepsen tests
|
- name: Run Jepsen tests
|
||||||
run: |
|
run: |
|
||||||
cd tests/jepsen
|
cd tests/jepsen
|
||||||
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
|
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||||
|
|
||||||
- name: Save Jepsen report
|
- name: Save Jepsen report
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: "Jepsen Report"
|
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
|
DOCKER_REPOSITORY_NAME: memgraph
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|||||||
215
.github/workflows/release_ubuntu2004.yaml
vendored
215
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -14,19 +14,21 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: "0 22 * * *"
|
- 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:
|
jobs:
|
||||||
community_build:
|
community_build:
|
||||||
name: "Community build"
|
name: "Community build"
|
||||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||||
env:
|
timeout-minutes: 60
|
||||||
THREADS: 24
|
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
|
||||||
timeout-minutes: 960
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -40,10 +42,6 @@ jobs:
|
|||||||
# Initialize dependencies.
|
# Initialize dependencies.
|
||||||
./init
|
./init
|
||||||
|
|
||||||
# Set default build_type to Release
|
|
||||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
|
||||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
|
||||||
|
|
||||||
# Build community binaries.
|
# Build community binaries.
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
|
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF ..
|
||||||
@@ -61,14 +59,11 @@ jobs:
|
|||||||
coverage_build:
|
coverage_build:
|
||||||
name: "Coverage build"
|
name: "Coverage build"
|
||||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||||
env:
|
timeout-minutes: 60
|
||||||
THREADS: 24
|
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -110,22 +105,19 @@ jobs:
|
|||||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||||
|
|
||||||
- name: Save code coverage
|
- name: Save code coverage
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Code coverage"
|
name: "Code coverage(Coverage build)"
|
||||||
path: tools/github/generated/code_coverage.tar.gz
|
path: tools/github/generated/code_coverage.tar.gz
|
||||||
|
|
||||||
debug_build:
|
debug_build:
|
||||||
name: "Debug build"
|
name: "Debug build"
|
||||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||||
env:
|
timeout-minutes: 60
|
||||||
THREADS: 24
|
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -157,10 +149,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./tests/drivers/run.sh
|
./tests/drivers/run.sh
|
||||||
|
|
||||||
- name: Run integration tests
|
|
||||||
run: |
|
|
||||||
tests/integration/run.sh
|
|
||||||
|
|
||||||
- name: Run cppcheck and clang-format
|
- name: Run cppcheck and clang-format
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
@@ -171,23 +159,49 @@ jobs:
|
|||||||
./cppcheck_and_clang_format diff
|
./cppcheck_and_clang_format diff
|
||||||
|
|
||||||
- name: Save cppcheck and clang-format errors
|
- name: Save cppcheck and clang-format errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Code coverage"
|
name: "Code coverage(Debug build)"
|
||||||
path: tools/github/cppcheck_and_clang_format.txt
|
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:
|
release_build:
|
||||||
name: "Release build"
|
name: "Release build"
|
||||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||||
env:
|
timeout-minutes: 60
|
||||||
THREADS: 24
|
|
||||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
|
||||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
|
||||||
timeout-minutes: 960
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up repository
|
- name: Set up repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Number of commits to fetch. `0` indicates all history for all
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
# branches and tags. (default: 1)
|
# branches and tags. (default: 1)
|
||||||
@@ -201,10 +215,6 @@ jobs:
|
|||||||
# Initialize dependencies.
|
# Initialize dependencies.
|
||||||
./init
|
./init
|
||||||
|
|
||||||
# Set default build_type to Release
|
|
||||||
INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }}
|
|
||||||
BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"}
|
|
||||||
|
|
||||||
# Build release binaries.
|
# Build release binaries.
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||||
@@ -226,11 +236,60 @@ jobs:
|
|||||||
cpack -G DEB --config ../CPackConfig.cmake
|
cpack -G DEB --config ../CPackConfig.cmake
|
||||||
|
|
||||||
- name: Save enterprise DEB package
|
- name: Save enterprise DEB package
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: "Enterprise DEB package"
|
name: "Enterprise DEB package"
|
||||||
path: build/output/memgraph*.deb
|
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
|
- name: Run micro benchmark tests
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
@@ -257,29 +316,38 @@ jobs:
|
|||||||
--num-database-workers 9 --num-clients-workers 30 \
|
--num-database-workers 9 --num-clients-workers 30 \
|
||||||
--no-strict
|
--no-strict
|
||||||
|
|
||||||
- name: Run GQL Behave tests
|
release_e2e_test:
|
||||||
run: |
|
if: false
|
||||||
cd tests
|
name: "Release End-to-end Test"
|
||||||
./setup.sh /opt/toolchain-v4/activate
|
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||||
cd gql_behave
|
timeout-minutes: 60
|
||||||
./continuous_integration
|
|
||||||
|
|
||||||
- name: Save quality assurance status
|
steps:
|
||||||
uses: actions/upload-artifact@v3
|
- name: Set up repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
name: "GQL Behave Status"
|
# Number of commits to fetch. `0` indicates all history for all
|
||||||
path: |
|
# branches and tags. (default: 1)
|
||||||
tests/gql_behave/gql_behave_status.csv
|
fetch-depth: 0
|
||||||
tests/gql_behave/gql_behave_status.html
|
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Build release binaries
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
source /opt/toolchain-v4/activate
|
source /opt/toolchain-v4/activate
|
||||||
|
# Initialize dependencies.
|
||||||
|
./init
|
||||||
|
|
||||||
# Run unit tests.
|
# Build release binaries
|
||||||
cd build
|
cd build
|
||||||
ctest -R memgraph__unit --output-on-failure
|
cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
|
||||||
|
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
|
- name: Run e2e tests
|
||||||
run: |
|
run: |
|
||||||
@@ -289,6 +357,40 @@ jobs:
|
|||||||
cd e2e
|
cd e2e
|
||||||
./run.sh
|
./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
|
||||||
|
|
||||||
|
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)
|
- name: Run stress test (plain)
|
||||||
run: |
|
run: |
|
||||||
cd tests/stress
|
cd tests/stress
|
||||||
@@ -299,11 +401,6 @@ jobs:
|
|||||||
cd tests/stress
|
cd tests/stress
|
||||||
./continuous_integration --use-ssl
|
./continuous_integration --use-ssl
|
||||||
|
|
||||||
- name: Run stress test (large)
|
|
||||||
run: |
|
|
||||||
cd tests/stress
|
|
||||||
./continuous_integration --large-dataset
|
|
||||||
|
|
||||||
- name: Run durability test (plain)
|
- name: Run durability test (plain)
|
||||||
run: |
|
run: |
|
||||||
cd tests/stress
|
cd tests/stress
|
||||||
|
|||||||
61
.github/workflows/reusable_build.yaml
vendored
Normal file
61
.github/workflows/reusable_build.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Build Memgraph
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
build_name:
|
||||||
|
description: Which build type will be used (community, debug, release ...)?
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
threads:
|
||||||
|
description: How many threads will be used to build memgraph?
|
||||||
|
default: 24
|
||||||
|
type: number
|
||||||
|
os:
|
||||||
|
description: Base image with which os will be used?
|
||||||
|
default: debian-10
|
||||||
|
type: string
|
||||||
|
toolchain_version:
|
||||||
|
description: Which toolchain version will be used in the build?
|
||||||
|
default: 4
|
||||||
|
type: number
|
||||||
|
|
||||||
|
env:
|
||||||
|
TAG: "memgraph/mgbuilder:${{ github.sha }}-${{ inputs.build_name }}"
|
||||||
|
OS: ${{ inputs.os }}
|
||||||
|
TOOLCHAIN_VERSION: ${{ inputs.toolchain_version }}
|
||||||
|
THREADS: ${{ inputs.threads }}
|
||||||
|
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||||
|
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: "${{ github.sha }}-${{ inputs.build_name }}"
|
||||||
|
runs-on: [self-hosted, Linux, X64, Diff]
|
||||||
|
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: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build memgraph/mgbuilder:${{ github.sha }}-${{ inputs.build_name }}
|
||||||
|
run: ./docker/ci.bash build ${{ inputs.build_name }}
|
||||||
|
|
||||||
|
- name: Push mgbuilder image
|
||||||
|
run: docker push $TAG
|
||||||
|
|
||||||
|
- name: Clean up
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
docker container prune -f
|
||||||
|
docker image prune -f
|
||||||
|
docker builder prune -f
|
||||||
|
docker image rm $TAG
|
||||||
60
.github/workflows/reusable_test.yaml
vendored
Normal file
60
.github/workflows/reusable_test.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: Run a test
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
test_name:
|
||||||
|
description: Which test will be run?
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
mgbuilder:
|
||||||
|
description: Which mgbuilder image is needed to perform the test?
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
threads:
|
||||||
|
description: How many threads will be used to build memgraph?
|
||||||
|
default: 24
|
||||||
|
type: number
|
||||||
|
os:
|
||||||
|
description: Base image with which os will be used?
|
||||||
|
default: debian-10
|
||||||
|
type: string
|
||||||
|
toolchain_version:
|
||||||
|
description: Which toolchain version will be used in the build?
|
||||||
|
default: 4
|
||||||
|
type: number
|
||||||
|
|
||||||
|
env:
|
||||||
|
OS: ${{ inputs.os }}
|
||||||
|
TOOLCHAIN_VERSION: ${{ inputs.toolchain_version }}
|
||||||
|
THREADS: ${{ inputs.threads }}
|
||||||
|
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||||
|
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||||
|
TEST_CONTAINER: "${{ inputs.test_name }}-${{ github.run_id }}"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
RunTest:
|
||||||
|
name: "Run ${{ inputs.test_name }} test"
|
||||||
|
runs-on: [self-hosted, Linux, X64, Diff]
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Pull ${{ inputs.mgbuilder_tag }}
|
||||||
|
run: docker pull ${{ inputs.mgbuilder }}
|
||||||
|
|
||||||
|
- name: Run ${{ inputs.test_name }}
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
--name $TEST_CONTAINER \
|
||||||
|
--entrypoint /bin/bash \
|
||||||
|
${{ inputs.mgbuilder }} \
|
||||||
|
-c "./memgraph/docker/ci.bash test ${{ inputs.test_name }}"
|
||||||
|
|
||||||
|
- name: Clean up
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
docker image rm ${{ inputs.mgbuilder }}
|
||||||
62
.github/workflows/stress_test_large.yaml
vendored
Normal file
62
.github/workflows/stress_test_large.yaml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
126
.github/workflows/testflow.yaml
vendored
Normal file
126
.github/workflows/testflow.yaml
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
name: Test workflow
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.head_ref || github.sha }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
- "**/*.md"
|
||||||
|
- ".clang-format"
|
||||||
|
- "CODEOWNERS"
|
||||||
|
- "licenses/*"
|
||||||
|
|
||||||
|
env:
|
||||||
|
THREADS: 24
|
||||||
|
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||||
|
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||||
|
OS: "debian-10"
|
||||||
|
TOOLCHAIN_VERSION: "4"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
########################################################################################
|
||||||
|
######################################## BUILDS ########################################
|
||||||
|
########################################################################################
|
||||||
|
MgbuilderCommunity:
|
||||||
|
if: false
|
||||||
|
uses: ./.github/workflows/reusable_build.yaml
|
||||||
|
with:
|
||||||
|
build_name: community
|
||||||
|
threads: 24
|
||||||
|
os: debian-10
|
||||||
|
toolchain_version: 4
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
MgbuilderDebug:
|
||||||
|
if: false
|
||||||
|
uses: ./.github/workflows/reusable_build.yaml
|
||||||
|
with:
|
||||||
|
build_name: debug
|
||||||
|
threads: 24
|
||||||
|
os: debian-10
|
||||||
|
toolchain_version: 4
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
MgbuilderRelease:
|
||||||
|
# if: false
|
||||||
|
uses: ./.github/workflows/reusable_build.yaml
|
||||||
|
with:
|
||||||
|
build_name: release
|
||||||
|
threads: 24
|
||||||
|
os: debian-10
|
||||||
|
toolchain_version: 4
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
MgbuilderExperimentalHA:
|
||||||
|
if: false
|
||||||
|
uses: ./.github/workflows/reusable_build.yaml
|
||||||
|
with:
|
||||||
|
build_name: experimental_ha
|
||||||
|
threads: 24
|
||||||
|
os: debian-10
|
||||||
|
toolchain_version: 4
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
MgbuilderExperimentalMT:
|
||||||
|
if: false
|
||||||
|
uses: ./.github/workflows/reusable_build.yaml
|
||||||
|
with:
|
||||||
|
build_name: experimental_mt
|
||||||
|
threads: 24
|
||||||
|
os: debian-10
|
||||||
|
toolchain_version: 4
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
########################################################################################
|
||||||
|
######################################## TESTS #########################################
|
||||||
|
########################################################################################
|
||||||
|
StressTestPlain:
|
||||||
|
needs: [MgbuilderRelease]
|
||||||
|
uses: ./.github/workflows/reusable_test.yaml
|
||||||
|
with:
|
||||||
|
test_name: stress
|
||||||
|
mgbuilder: "memgraph/mgbuilder:${{ github.sha }}-release"
|
||||||
|
threads: 24
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
StressTestSSL:
|
||||||
|
needs: [MgbuilderRelease]
|
||||||
|
uses: ./.github/workflows/reusable_test.yaml
|
||||||
|
with:
|
||||||
|
test_name: stress_ssl
|
||||||
|
mgbuilder: "memgraph/mgbuilder:${{ github.sha }}-release"
|
||||||
|
threads: 24
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
DurabilityTest:
|
||||||
|
needs: [MgbuilderRelease]
|
||||||
|
uses: ./.github/workflows/reusable_test.yaml
|
||||||
|
with:
|
||||||
|
test_name: durability
|
||||||
|
mgbuilder: "memgraph/mgbuilder:${{ github.sha }}-release"
|
||||||
|
threads: 24
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
UnitTest:
|
||||||
|
needs: [MgbuilderRelease]
|
||||||
|
uses: ./.github/workflows/reusable_test.yaml
|
||||||
|
with:
|
||||||
|
test_name: unit
|
||||||
|
mgbuilder: "memgraph/mgbuilder:${{ github.sha }}-release"
|
||||||
|
threads: 24
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
GQLBehaveTest:
|
||||||
|
needs: [MgbuilderRelease]
|
||||||
|
uses: ./.github/workflows/reusable_test.yaml
|
||||||
|
with:
|
||||||
|
test_name: gql_behave
|
||||||
|
mgbuilder: "memgraph/mgbuilder:${{ github.sha }}-release"
|
||||||
|
threads: 24
|
||||||
|
secrets: inherit
|
||||||
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: dawidd6/action-download-artifact@v2
|
uses: dawidd6/action-download-artifact@v4
|
||||||
with:
|
with:
|
||||||
workflow: package_all.yaml
|
workflow: package_all.yaml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
|
|||||||
22
.sonarcloud.properties
Normal file
22
.sonarcloud.properties
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Path to sources
|
||||||
|
sonar.sources = .
|
||||||
|
# sonar.exclusions=
|
||||||
|
sonar.inclusions=src,include,query_modules
|
||||||
|
|
||||||
|
# Path to tests
|
||||||
|
sonar.tests = tests/
|
||||||
|
# sonar.test.exclusions=
|
||||||
|
# sonar.test.inclusions=
|
||||||
|
|
||||||
|
# Source encoding
|
||||||
|
# sonar.sourceEncoding=
|
||||||
|
|
||||||
|
# Exclusions for copy-paste detection
|
||||||
|
# sonar.cpd.exclusions=
|
||||||
|
|
||||||
|
# Python version (for python projects only)
|
||||||
|
# sonar.python.version=
|
||||||
|
|
||||||
|
# C++ standard version (for C++ projects only)
|
||||||
|
# If not specified, it defaults to the latest supported standard
|
||||||
|
# sonar.cfamily.reportingCppStandardOverride=c++98|c++11|c++14|c++17|c++20
|
||||||
32
ADRs/001_tantivy.md
Normal file
32
ADRs/001_tantivy.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Tantivy ADR
|
||||||
|
|
||||||
|
**Author**
|
||||||
|
Marko Budiselic (github.com/gitbuda)
|
||||||
|
|
||||||
|
**Status**
|
||||||
|
APPROVED
|
||||||
|
|
||||||
|
**Date**
|
||||||
|
January 5, 2024
|
||||||
|
|
||||||
|
**Problem**
|
||||||
|
|
||||||
|
For some of Memgraph workloads, text search is a required feature. We don't
|
||||||
|
want to build a new text search engine because that's not Memgraph's core
|
||||||
|
value.
|
||||||
|
|
||||||
|
**Criteria**
|
||||||
|
|
||||||
|
- easy integration with our C++ codebase
|
||||||
|
- ability to operate in-memory and on-disk
|
||||||
|
- sufficient features (regex, full-text search, fuzzy search, aggregations over
|
||||||
|
text data)
|
||||||
|
- production-ready
|
||||||
|
|
||||||
|
**Decision**
|
||||||
|
|
||||||
|
All known C++ libraries are not production-ready. Recent Rust libraries, in
|
||||||
|
particular [Tantivy](https://github.com/quickwit-oss/tantivy), seem to provide
|
||||||
|
much more features, it is production ready. The way how we'll integrate Tantivy
|
||||||
|
into the current Memgraph codebase is via
|
||||||
|
[cxx](https://github.com/dtolnay/cxx). **We select Tantivy.**
|
||||||
34
ADRs/002_nuraft.md
Normal file
34
ADRs/002_nuraft.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# NuRaft ADR
|
||||||
|
|
||||||
|
**Author**
|
||||||
|
Marko Budiselic (github.com/gitbuda)
|
||||||
|
|
||||||
|
**Status**
|
||||||
|
PROPOSED
|
||||||
|
|
||||||
|
**Date**
|
||||||
|
January 10, 2024
|
||||||
|
|
||||||
|
**Problem**
|
||||||
|
|
||||||
|
In order to enhance Memgraph to have High Availability features as requested by
|
||||||
|
customers, we want to have reliable coordinators backed by RAFT consensus algorithm. Implementing
|
||||||
|
RAFT to be correct and performant is a very challenging task. Skillful Memgraph
|
||||||
|
engineers already tried 3 times and failed to deliver in a reasonable timeframe
|
||||||
|
all three times (approximately 4 person-weeks of engineering work each time).
|
||||||
|
|
||||||
|
**Criteria**
|
||||||
|
|
||||||
|
- easy integration with our C++ codebase
|
||||||
|
- heavily tested in production environments
|
||||||
|
- implementation of performance optimizations on top of the canonical Raft
|
||||||
|
implementation
|
||||||
|
|
||||||
|
**Decision**
|
||||||
|
|
||||||
|
There are a few, robust C++ implementations of Raft but as a part of other
|
||||||
|
projects or bigger libraries. **We select
|
||||||
|
[NuRaft](https://github.com/eBay/NuRaft)** because it focuses on delivering
|
||||||
|
Raft without bloatware, and it's used by
|
||||||
|
[Clickhouse](https://github.com/ClickHouse/ClickHouse) (an comparable peer to
|
||||||
|
Memgraph, a very well-established product).
|
||||||
67
ADRs/README.md
Normal file
67
ADRs/README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Architecture Decision Records
|
||||||
|
|
||||||
|
Also known as ADRs. This practice has become widespread in many
|
||||||
|
high performing engineering teams. It is a technique for communicating
|
||||||
|
between software engineers. ADRs provide a clear and documented
|
||||||
|
history of architectural choices, ensuring that everyone on the
|
||||||
|
team is on the same page. This improves communication and reduces
|
||||||
|
misunderstandings. The act of recording decisions encourages
|
||||||
|
thoughtful consideration before making choices. This can lead to
|
||||||
|
more robust and better-informed architectural decisions.
|
||||||
|
|
||||||
|
Links must be created, pointing both to and from the Github Issues
|
||||||
|
and/or the Notion Program Management "Initiative" database.
|
||||||
|
|
||||||
|
ADRs are complimentary to any tech specs that get written while
|
||||||
|
designing a solution. ADRs are very short and to the point, while
|
||||||
|
tech specs will include diagrams and can be quite verbose.
|
||||||
|
|
||||||
|
## HOWTO
|
||||||
|
|
||||||
|
Each ADR will be assigned a monotonically increasing unique numeric
|
||||||
|
identifier, which will be zero-padded to 3 digits. Each ADR will
|
||||||
|
be in a single markdown file containing no more than one page of
|
||||||
|
text, and the filename will start with that unique identifier,
|
||||||
|
followed by a camel case phrase summarizing the problem. For
|
||||||
|
example: `001_architecture_decision_records.md` or
|
||||||
|
`002_big_integration_cap_theorem.md`.
|
||||||
|
|
||||||
|
We want to use an ADR when:
|
||||||
|
1. Significant Impact: This includes choices that affect scalability, performance, or fundamental design principles.
|
||||||
|
1. Long-Term Ramifications: When a decision is expected to have long-term ramifications or is difficult to reverse.
|
||||||
|
1. Architectural Principles: ADRs are suitable for documenting decisions related to architectural principles, frameworks, or patterns that shape the system's structure.
|
||||||
|
1. Controversial Choices: When a decision is likely to be controversial or may require justification in the future.
|
||||||
|
|
||||||
|
The most senior engineer on a project will evaluate and decide
|
||||||
|
whether or not an ADR is needed.
|
||||||
|
|
||||||
|
## Do
|
||||||
|
|
||||||
|
1. Keep them brief and concise.
|
||||||
|
1. Explain the trade-offs.
|
||||||
|
1. Each ADR should be about one AD, not multiple ADs
|
||||||
|
1. Don't alter existing information in an ADR. Instead, amend the ADR by adding new information, or supersede the ADR by creating a new ADR.
|
||||||
|
1. Explain your organization's situation and business priorities.
|
||||||
|
1. Include rationale and considerations based on social and skills makeups of your teams.
|
||||||
|
1. Include pros and cons that are relevant, and describe them in terms that align with your needs and goals.
|
||||||
|
1. Explain what follows from making the decision. This can include the effects, outcomes, outputs, follow ups, and more.
|
||||||
|
|
||||||
|
## Don't
|
||||||
|
|
||||||
|
1. Try to guess what the executive leader wants, and then attempt to please them. Be objective.
|
||||||
|
1. Try to solve everything all at once. A pretty good solution now is MUCH BETTER than a perfect solution later. Carpe diem!
|
||||||
|
1. Hide any doubts or unanswered questions.
|
||||||
|
1. Make it a sales pitch. Everything has upsides and downsides - be authentic and honest about them.
|
||||||
|
1. Perform merely a superficial investigation. If an ADR doesn't call for some deep thinking, then it probably shouldn't exist.
|
||||||
|
1. Ignore the long-term costs such as performance, tech debt or hardware and maintenance.
|
||||||
|
1. Get tunnel vision where creative or surprising approaches are not explored.
|
||||||
|
|
||||||
|
# Template - use the format below for each new ADR
|
||||||
|
|
||||||
|
1. **Author** - who has written the ADR
|
||||||
|
1. **Status** - one of: PROPOSED, ACCEPTED, REJECTED, SUPERSEDED-BY or DEPRECATED
|
||||||
|
1. **Date** - when the status was most recently updated
|
||||||
|
1. **Problem** - a concise paragraph explaining the context
|
||||||
|
1. **Criteria** - a list of the two or three metrics by which the solution was evaluated, and their relative weights (importance)
|
||||||
|
1. **Decision** - what was chosen as the way forward, and what the consequences are of the decision
|
||||||
|
|
||||||
@@ -271,6 +271,17 @@ endif()
|
|||||||
set(libs_dir ${CMAKE_SOURCE_DIR}/libs)
|
set(libs_dir ${CMAKE_SOURCE_DIR}/libs)
|
||||||
add_subdirectory(libs EXCLUDE_FROM_ALL)
|
add_subdirectory(libs EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
option(MG_EXPERIMENTAL_HIGH_AVAILABILITY "Feature flag for experimental high availability" OFF)
|
||||||
|
|
||||||
|
if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_HIGH_AVAILABILITY)
|
||||||
|
set(MG_EXPERIMENTAL_HIGH_AVAILABILITY OFF)
|
||||||
|
message(FATAL_ERROR "MG_EXPERIMENTAL_HIGH_AVAILABILITY must be used with enterpise version of the code.")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (MG_EXPERIMENTAL_HIGH_AVAILABILITY)
|
||||||
|
add_compile_definitions(MG_EXPERIMENTAL_HIGH_AVAILABILITY)
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Optional subproject configuration -------------------------------------------
|
# Optional subproject configuration -------------------------------------------
|
||||||
option(TEST_COVERAGE "Generate coverage reports from running memgraph" OFF)
|
option(TEST_COVERAGE "Generate coverage reports from running memgraph" OFF)
|
||||||
option(TOOLS "Build tools binaries" ON)
|
option(TOOLS "Build tools binaries" ON)
|
||||||
@@ -279,6 +290,18 @@ option(ASAN "Build with Address Sanitizer. To get a reasonable performance optio
|
|||||||
option(TSAN "Build with Thread Sanitizer. To get a reasonable performance option should be used only in Release or RelWithDebInfo build " OFF)
|
option(TSAN "Build with Thread Sanitizer. To get a reasonable performance option should be used only in Release or RelWithDebInfo build " OFF)
|
||||||
option(UBSAN "Build with Undefined Behaviour Sanitizer" OFF)
|
option(UBSAN "Build with Undefined Behaviour Sanitizer" OFF)
|
||||||
|
|
||||||
|
# Build feature flags
|
||||||
|
option(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY "Feature flag for experimental replicaition of multitenacy" OFF)
|
||||||
|
|
||||||
|
if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||||
|
set(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY OFF)
|
||||||
|
message(FATAL_ERROR "MG_EXPERIMENTAL_REPLICATION_MULTITENANCY with community edition build isn't possible")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||||
|
add_compile_definitions(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (TEST_COVERAGE)
|
if (TEST_COVERAGE)
|
||||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||||
if (NOT lower_build_type STREQUAL "debug")
|
if (NOT lower_build_type STREQUAL "debug")
|
||||||
@@ -292,7 +315,7 @@ if (MG_ENTERPRISE)
|
|||||||
add_definitions(-DMG_ENTERPRISE)
|
add_definitions(-DMG_ENTERPRISE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(ENABLE_JEMALLOC ON)
|
option(ENABLE_JEMALLOC "Use jemalloc" ON)
|
||||||
|
|
||||||
if (ASAN)
|
if (ASAN)
|
||||||
message(WARNING "Disabling jemalloc as it doesn't work well with ASAN")
|
message(WARNING "Disabling jemalloc as it doesn't work well with ASAN")
|
||||||
|
|||||||
@@ -111,6 +111,14 @@ modifications:
|
|||||||
value: "false"
|
value: "false"
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
- name: "storage_parallel_schema_recovery"
|
||||||
|
value: "false"
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: "storage_enable_schema_metadata"
|
||||||
|
value: "false"
|
||||||
|
override: true
|
||||||
|
|
||||||
- name: "query_callable_mappings_path"
|
- name: "query_callable_mappings_path"
|
||||||
value: "/etc/memgraph/apoc_compatibility_mappings.json"
|
value: "/etc/memgraph/apoc_compatibility_mappings.json"
|
||||||
override: true
|
override: true
|
||||||
|
|||||||
169
docker/ci.bash
Executable file
169
docker/ci.bash
Executable file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
ROOT_DIR=$DIR/..
|
||||||
|
BUILD_DIR=$ROOT_DIR/build
|
||||||
|
|
||||||
|
OS=${OS:-debian-10}
|
||||||
|
TOOLCHAIN_VERSION=${TOOLCHAIN_VERSION:-4}
|
||||||
|
THREADS=${THREADS:-$(nproc)}
|
||||||
|
TAG=${TAG:-memgraph/mgbuilder:12345}
|
||||||
|
DOCKERFILE_PATH=${DOCKERFILE_PATH:-$DIR/mgbuilder/Dockerfile}
|
||||||
|
DOCKER_CONTEXT_PATH=${DOCKER_CONTEXT_PATH:-$DIR/..}
|
||||||
|
TOOLCHAIN_ACTIVATE=${TOOLCHAIN_ACTIVATE:-"source /opt/toolchain-v${TOOLCHAIN_VERSION}/activate"}
|
||||||
|
|
||||||
|
print_help() {
|
||||||
|
echo -e "$0 build BUILD_TYPE | test TEST_NAME"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
case "$1" in
|
||||||
|
community)
|
||||||
|
cmake_cmd="cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMG_ENTERPRISE=OFF .."
|
||||||
|
make_cmd="make -j$THREADS"
|
||||||
|
;;
|
||||||
|
debug)
|
||||||
|
cmake_cmd="cmake -DCMAKE_BUILD_TYPE=Debug .."
|
||||||
|
make_cmd="make -j$THREADS"
|
||||||
|
;;
|
||||||
|
jepsen)
|
||||||
|
cmake_cmd="cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .."
|
||||||
|
make_cmd="make -j$THREADS memgraph"
|
||||||
|
;;
|
||||||
|
release)
|
||||||
|
cmake_cmd="cmake -DCMAKE_BUILD_TYPE=Release .."
|
||||||
|
make_cmd="make -j$THREADS"
|
||||||
|
;;
|
||||||
|
experimental_mt)
|
||||||
|
cmake_cmd="cmake -DCMAKE_BUILD_TYPE=Release -DMG_EXPERIMENTAL_REPLICATION_MULTITENANCY=ON .."
|
||||||
|
make_cmd="make -j$THREADS"
|
||||||
|
;;
|
||||||
|
experimental_ha)
|
||||||
|
cmake_cmd="cmake -DCMAKE_BUILD_TYPE=Release -DMG_EXPERIMENTAL_HIGH_AVAILABILITY=ON .."
|
||||||
|
make_cmd="make -j$THREADS"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_help build
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
docker buildx build \
|
||||||
|
-f $DOCKERFILE_PATH \
|
||||||
|
-t $TAG \
|
||||||
|
--build-arg CMAKE_CMD="$cmake_cmd" \
|
||||||
|
--build-arg MAKE_CMD="$make_cmd" \
|
||||||
|
--build-arg OS=$OS \
|
||||||
|
--build-arg THREADS=$THREADS \
|
||||||
|
--build-arg TOOLCHAIN_VERSION=$TOOLCHAIN_VERSION \
|
||||||
|
$DOCKER_CONTEXT_PATH
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
unit_test() {
|
||||||
|
$TOOLCHAIN_ACTIVATE
|
||||||
|
cd $BUILD_DIR
|
||||||
|
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||||
|
}
|
||||||
|
|
||||||
|
leftover_ctests() {
|
||||||
|
$TOOLCHAIN_ACTIVATE
|
||||||
|
cd $BUILD_DIR
|
||||||
|
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||||
|
}
|
||||||
|
|
||||||
|
drivers_test() {
|
||||||
|
cd $ROOT_DIR
|
||||||
|
./tests/drivers/run.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
integration_test() {
|
||||||
|
cd $ROOT_DIR
|
||||||
|
tests/integration/run.sh
|
||||||
|
}
|
||||||
|
|
||||||
|
cppcheck_and_clang_format() {
|
||||||
|
$TOOLCHAIN_ACTIVATE
|
||||||
|
cd $ROOT_DIR/tools/github
|
||||||
|
./cppcheck_and_clang_format diff
|
||||||
|
}
|
||||||
|
|
||||||
|
stress_test () {
|
||||||
|
cd $ROOT_DIR/tests/stress
|
||||||
|
source ve3/bin/activate
|
||||||
|
case "$1" in
|
||||||
|
plain)
|
||||||
|
./continuous_integration
|
||||||
|
;;
|
||||||
|
ssl)
|
||||||
|
./continuous_integration --use-ssl
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_help test
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
durability_test() {
|
||||||
|
cd $ROOT_DIR/tests/stress
|
||||||
|
source ve3/bin/activate
|
||||||
|
python3 durability --num-steps 5
|
||||||
|
}
|
||||||
|
|
||||||
|
gql_behave_test() {
|
||||||
|
$TOOLCHAIN_ACTIVATE
|
||||||
|
cd $ROOT_DIR/tests/gql_behave
|
||||||
|
./continuous_integration
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test() {
|
||||||
|
case "$1" in
|
||||||
|
unit)
|
||||||
|
unit_test
|
||||||
|
;;
|
||||||
|
leftover-CTest)
|
||||||
|
leftover_ctest
|
||||||
|
;;
|
||||||
|
drivers)
|
||||||
|
drivers_test
|
||||||
|
;;
|
||||||
|
integration)
|
||||||
|
integration_test
|
||||||
|
;;
|
||||||
|
cppcheck-and-clang-format)
|
||||||
|
cppcheck_and_clang_format
|
||||||
|
;;
|
||||||
|
stress)
|
||||||
|
stress_test plain
|
||||||
|
;;
|
||||||
|
stress_ssl)
|
||||||
|
stress_test ssl
|
||||||
|
;;
|
||||||
|
durability)
|
||||||
|
durability_test
|
||||||
|
;;
|
||||||
|
gql_behave)
|
||||||
|
gql_behave_test
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_help test
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$#" -eq 0 ]]; then
|
||||||
|
print_help
|
||||||
|
else
|
||||||
|
case "$1" in
|
||||||
|
build)
|
||||||
|
build $2
|
||||||
|
;;
|
||||||
|
test)
|
||||||
|
run_test $2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_help
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
32
docker/mgbuilder-base/debian-10/Dockerfile
Normal file
32
docker/mgbuilder-base/debian-10/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FROM debian:10
|
||||||
|
|
||||||
|
ARG TOOLCHAIN_VERSION=4
|
||||||
|
ENV TOOLCHAIN="toolchain-v${TOOLCHAIN_VERSION}"
|
||||||
|
ENV TOOLCHAIN_ACTIVATE="source /opt/$TOOLCHAIN/activate"
|
||||||
|
ENV OS=debian-10
|
||||||
|
ENV TARGETARCH=amd64
|
||||||
|
|
||||||
|
# Stops tzdata interactive configuration.
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt update && apt install -y \
|
||||||
|
ca-certificates wget git
|
||||||
|
# Do NOT be smart here and clean the cache because the container is used in the
|
||||||
|
# stateful context.
|
||||||
|
|
||||||
|
RUN wget -q https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/$TOOLCHAIN/$TOOLCHAIN-binaries-$OS-$TARGETARCH.tar.gz \
|
||||||
|
-O $TOOLCHAIN-binaries-$OS-$TARGETARCH.tar.gz \
|
||||||
|
&& tar xzvf $TOOLCHAIN-binaries-$OS-$TARGETARCH.tar.gz -C /opt \
|
||||||
|
&& rm $TOOLCHAIN-binaries-$OS-$TARGETARCH.tar.gz
|
||||||
|
|
||||||
|
COPY . /memgraph
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
|
RUN cd memgraph && \
|
||||||
|
git remote set-url origin https://github.com/memgraph/memgraph.git && \
|
||||||
|
./environment/os/$OS.sh install TOOLCHAIN_RUN_DEPS && \
|
||||||
|
./environment/os/$OS.sh install MEMGRAPH_BUILD_DEPS && \
|
||||||
|
cd / && rm -rf memgraph
|
||||||
|
|
||||||
|
ENTRYPOINT ["sleep", "infinity"]
|
||||||
26
docker/mgbuilder/Dockerfile
Normal file
26
docker/mgbuilder/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
ARG TOOLCHAIN_VERSION=4
|
||||||
|
ARG OS=debian-10
|
||||||
|
|
||||||
|
FROM memgraph/mgbuilder-base:toolchain-v${TOOLCHAIN_VERSION}_${OS}
|
||||||
|
|
||||||
|
ARG BUILD_TYPE=Release
|
||||||
|
ARG THREADS
|
||||||
|
ENV BUILD_TYPE=${BUILD_TYPE}
|
||||||
|
ENV THREADS=${THREADS}
|
||||||
|
ARG CMAKE_CMD="cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .."
|
||||||
|
ARG MAKE_CMD="make -j$THREADS"
|
||||||
|
|
||||||
|
COPY . /memgraph
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
|
# Initialize deps
|
||||||
|
RUN cd memgraph && \
|
||||||
|
git remote set-url origin https://github.com/memgraph/memgraph.git && \
|
||||||
|
$TOOLCHAIN_ACTIVATE && \
|
||||||
|
./init && \
|
||||||
|
cd build && \
|
||||||
|
$CMAKE_CMD && \
|
||||||
|
$MAKE_CMD
|
||||||
|
|
||||||
|
ENTRYPOINT ["sleep", "infinity"]
|
||||||
@@ -234,7 +234,57 @@ inline mgp_type *type_duration() { return MgInvoke<mgp_type *>(mgp_type_duration
|
|||||||
|
|
||||||
inline mgp_type *type_nullable(mgp_type *type) { return MgInvoke<mgp_type *>(mgp_type_nullable, type); }
|
inline mgp_type *type_nullable(mgp_type *type) { return MgInvoke<mgp_type *>(mgp_type_nullable, type); }
|
||||||
|
|
||||||
|
inline bool create_label_index(mgp_graph *graph, const char *label) {
|
||||||
|
return MgInvoke<int>(mgp_create_label_index, graph, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool drop_label_index(mgp_graph *graph, const char *label) {
|
||||||
|
return MgInvoke<int>(mgp_drop_label_index, graph, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mgp_list *list_all_label_indices(mgp_graph *graph, mgp_memory *memory) {
|
||||||
|
return MgInvoke<mgp_list *>(mgp_list_all_label_indices, graph, memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool create_label_property_index(mgp_graph *graph, const char *label, const char *property) {
|
||||||
|
return MgInvoke<int>(mgp_create_label_property_index, graph, label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool drop_label_property_index(mgp_graph *graph, const char *label, const char *property) {
|
||||||
|
return MgInvoke<int>(mgp_drop_label_property_index, graph, label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mgp_list *list_all_label_property_indices(mgp_graph *graph, mgp_memory *memory) {
|
||||||
|
return MgInvoke<mgp_list *>(mgp_list_all_label_property_indices, graph, memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool create_existence_constraint(mgp_graph *graph, const char *label, const char *property) {
|
||||||
|
return MgInvoke<int>(mgp_create_existence_constraint, graph, label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool drop_existence_constraint(mgp_graph *graph, const char *label, const char *property) {
|
||||||
|
return MgInvoke<int>(mgp_drop_existence_constraint, graph, label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mgp_list *list_all_existence_constraints(mgp_graph *graph, mgp_memory *memory) {
|
||||||
|
return MgInvoke<mgp_list *>(mgp_list_all_existence_constraints, graph, memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool create_unique_constraint(mgp_graph *memgraph_graph, const char *label, mgp_value *properties) {
|
||||||
|
return MgInvoke<int>(mgp_create_unique_constraint, memgraph_graph, label, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool drop_unique_constraint(mgp_graph *memgraph_graph, const char *label, mgp_value *properties) {
|
||||||
|
return MgInvoke<int>(mgp_drop_unique_constraint, memgraph_graph, label, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mgp_list *list_all_unique_constraints(mgp_graph *graph, mgp_memory *memory) {
|
||||||
|
return MgInvoke<mgp_list *>(mgp_list_all_unique_constraints, graph, memory);
|
||||||
|
}
|
||||||
|
|
||||||
// mgp_graph
|
// mgp_graph
|
||||||
|
|
||||||
|
inline bool graph_is_transactional(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_transactional, graph); }
|
||||||
|
|
||||||
inline bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
|
inline bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
|
||||||
|
|
||||||
@@ -328,6 +378,8 @@ inline mgp_list *list_copy(mgp_list *list, mgp_memory *memory) {
|
|||||||
|
|
||||||
inline void list_destroy(mgp_list *list) { mgp_list_destroy(list); }
|
inline void list_destroy(mgp_list *list) { mgp_list_destroy(list); }
|
||||||
|
|
||||||
|
inline bool list_contains_deleted(mgp_list *list) { return MgInvoke<int>(mgp_list_contains_deleted, list); }
|
||||||
|
|
||||||
inline void list_append(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append, list, val); }
|
inline void list_append(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append, list, val); }
|
||||||
|
|
||||||
inline void list_append_extend(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append_extend, list, val); }
|
inline void list_append_extend(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append_extend, list, val); }
|
||||||
@@ -346,6 +398,8 @@ inline mgp_map *map_copy(mgp_map *map, mgp_memory *memory) { return MgInvoke<mgp
|
|||||||
|
|
||||||
inline void map_destroy(mgp_map *map) { mgp_map_destroy(map); }
|
inline void map_destroy(mgp_map *map) { mgp_map_destroy(map); }
|
||||||
|
|
||||||
|
inline bool map_contains_deleted(mgp_map *map) { return MgInvoke<int>(mgp_map_contains_deleted, map); }
|
||||||
|
|
||||||
inline void map_insert(mgp_map *map, const char *key, mgp_value *value) {
|
inline void map_insert(mgp_map *map, const char *key, mgp_value *value) {
|
||||||
MgInvokeVoid(mgp_map_insert, map, key, value);
|
MgInvokeVoid(mgp_map_insert, map, key, value);
|
||||||
}
|
}
|
||||||
@@ -394,6 +448,8 @@ inline mgp_vertex *vertex_copy(mgp_vertex *v, mgp_memory *memory) {
|
|||||||
|
|
||||||
inline void vertex_destroy(mgp_vertex *v) { mgp_vertex_destroy(v); }
|
inline void vertex_destroy(mgp_vertex *v) { mgp_vertex_destroy(v); }
|
||||||
|
|
||||||
|
inline bool vertex_is_deleted(mgp_vertex *v) { return MgInvoke<int>(mgp_vertex_is_deleted, v); }
|
||||||
|
|
||||||
inline bool vertex_equal(mgp_vertex *v1, mgp_vertex *v2) { return MgInvoke<int>(mgp_vertex_equal, v1, v2); }
|
inline bool vertex_equal(mgp_vertex *v1, mgp_vertex *v2) { return MgInvoke<int>(mgp_vertex_equal, v1, v2); }
|
||||||
|
|
||||||
inline size_t vertex_labels_count(mgp_vertex *v) { return MgInvoke<size_t>(mgp_vertex_labels_count, v); }
|
inline size_t vertex_labels_count(mgp_vertex *v) { return MgInvoke<size_t>(mgp_vertex_labels_count, v); }
|
||||||
@@ -446,6 +502,8 @@ inline mgp_edge *edge_copy(mgp_edge *e, mgp_memory *memory) { return MgInvoke<mg
|
|||||||
|
|
||||||
inline void edge_destroy(mgp_edge *e) { mgp_edge_destroy(e); }
|
inline void edge_destroy(mgp_edge *e) { mgp_edge_destroy(e); }
|
||||||
|
|
||||||
|
inline bool edge_is_deleted(mgp_edge *e) { return MgInvoke<int>(mgp_edge_is_deleted, e); }
|
||||||
|
|
||||||
inline bool edge_equal(mgp_edge *e1, mgp_edge *e2) { return MgInvoke<int>(mgp_edge_equal, e1, e2); }
|
inline bool edge_equal(mgp_edge *e1, mgp_edge *e2) { return MgInvoke<int>(mgp_edge_equal, e1, e2); }
|
||||||
|
|
||||||
inline mgp_edge_type edge_get_type(mgp_edge *e) { return MgInvoke<mgp_edge_type>(mgp_edge_get_type, e); }
|
inline mgp_edge_type edge_get_type(mgp_edge *e) { return MgInvoke<mgp_edge_type>(mgp_edge_get_type, e); }
|
||||||
@@ -482,6 +540,8 @@ inline mgp_path *path_copy(mgp_path *path, mgp_memory *memory) {
|
|||||||
|
|
||||||
inline void path_destroy(mgp_path *path) { mgp_path_destroy(path); }
|
inline void path_destroy(mgp_path *path) { mgp_path_destroy(path); }
|
||||||
|
|
||||||
|
inline bool path_contains_deleted(mgp_path *path) { return MgInvoke<int>(mgp_path_contains_deleted, path); }
|
||||||
|
|
||||||
inline void path_expand(mgp_path *path, mgp_edge *edge) { MgInvokeVoid(mgp_path_expand, path, edge); }
|
inline void path_expand(mgp_path *path, mgp_edge *edge) { MgInvokeVoid(mgp_path_expand, path, edge); }
|
||||||
|
|
||||||
inline void path_pop(mgp_path *path) { MgInvokeVoid(mgp_path_pop, path); }
|
inline void path_pop(mgp_path *path) { MgInvokeVoid(mgp_path_pop, path); }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// 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,7 @@ namespace mg_exception {
|
|||||||
template <typename FirstArg, typename... Args>
|
template <typename FirstArg, typename... Args>
|
||||||
std::string StringSerialize(FirstArg &&firstArg, Args &&...args) {
|
std::string StringSerialize(FirstArg &&firstArg, Args &&...args) {
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
stream << firstArg;
|
stream << std::forward<FirstArg>(firstArg);
|
||||||
((stream << " " << args), ...);
|
((stream << " " << args), ...);
|
||||||
return stream.str();
|
return stream.str();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -429,6 +429,9 @@ enum mgp_error mgp_list_copy(struct mgp_list *list, struct mgp_memory *memory, s
|
|||||||
/// Free the memory used by the given mgp_list and contained elements.
|
/// Free the memory used by the given mgp_list and contained elements.
|
||||||
void mgp_list_destroy(struct mgp_list *list);
|
void mgp_list_destroy(struct mgp_list *list);
|
||||||
|
|
||||||
|
/// Return whether the given mgp_list contains any deleted values.
|
||||||
|
enum mgp_error mgp_list_contains_deleted(struct mgp_list *list, int *result);
|
||||||
|
|
||||||
/// Append a copy of mgp_value to mgp_list if capacity allows.
|
/// Append a copy of mgp_value to mgp_list if capacity allows.
|
||||||
/// The list copies the given value and therefore does not take ownership of the
|
/// The list copies the given value and therefore does not take ownership of the
|
||||||
/// original value. You still need to call mgp_value_destroy to free the
|
/// original value. You still need to call mgp_value_destroy to free the
|
||||||
@@ -469,6 +472,9 @@ enum mgp_error mgp_map_copy(struct mgp_map *map, struct mgp_memory *memory, stru
|
|||||||
/// Free the memory used by the given mgp_map and contained items.
|
/// Free the memory used by the given mgp_map and contained items.
|
||||||
void mgp_map_destroy(struct mgp_map *map);
|
void mgp_map_destroy(struct mgp_map *map);
|
||||||
|
|
||||||
|
/// Return whether the given mgp_map contains any deleted values.
|
||||||
|
enum mgp_error mgp_map_contains_deleted(struct mgp_map *map, int *result);
|
||||||
|
|
||||||
/// Insert a new mapping from a NULL terminated character string to a value.
|
/// Insert a new mapping from a NULL terminated character string to a value.
|
||||||
/// If a mapping with the same key already exists, it is *not* replaced.
|
/// If a mapping with the same key already exists, it is *not* replaced.
|
||||||
/// In case of insertion, both the string and the value are copied into the map.
|
/// In case of insertion, both the string and the value are copied into the map.
|
||||||
@@ -552,6 +558,9 @@ enum mgp_error mgp_path_copy(struct mgp_path *path, struct mgp_memory *memory, s
|
|||||||
/// Free the memory used by the given mgp_path and contained vertices and edges.
|
/// Free the memory used by the given mgp_path and contained vertices and edges.
|
||||||
void mgp_path_destroy(struct mgp_path *path);
|
void mgp_path_destroy(struct mgp_path *path);
|
||||||
|
|
||||||
|
/// Return whether the given mgp_path contains any deleted values.
|
||||||
|
enum mgp_error mgp_path_contains_deleted(struct mgp_path *path, int *result);
|
||||||
|
|
||||||
/// Append an edge continuing from the last vertex on the path.
|
/// Append an edge continuing from the last vertex on the path.
|
||||||
/// The edge is copied into the path. Therefore, the path does not take
|
/// The edge is copied into the path. Therefore, the path does not take
|
||||||
/// ownership of the original edge, so you still need to free the edge memory
|
/// ownership of the original edge, so you still need to free the edge memory
|
||||||
@@ -725,6 +734,9 @@ enum mgp_error mgp_vertex_copy(struct mgp_vertex *v, struct mgp_memory *memory,
|
|||||||
/// Free the memory used by a mgp_vertex.
|
/// Free the memory used by a mgp_vertex.
|
||||||
void mgp_vertex_destroy(struct mgp_vertex *v);
|
void mgp_vertex_destroy(struct mgp_vertex *v);
|
||||||
|
|
||||||
|
/// Return whether the given mgp_vertex is deleted.
|
||||||
|
enum mgp_error mgp_vertex_is_deleted(struct mgp_vertex *v, int *result);
|
||||||
|
|
||||||
/// Result is non-zero if given vertices are equal, otherwise 0.
|
/// Result is non-zero if given vertices are equal, otherwise 0.
|
||||||
enum mgp_error mgp_vertex_equal(struct mgp_vertex *v1, struct mgp_vertex *v2, int *result);
|
enum mgp_error mgp_vertex_equal(struct mgp_vertex *v1, struct mgp_vertex *v2, int *result);
|
||||||
|
|
||||||
@@ -819,6 +831,9 @@ enum mgp_error mgp_edge_copy(struct mgp_edge *e, struct mgp_memory *memory, stru
|
|||||||
/// Free the memory used by a mgp_edge.
|
/// Free the memory used by a mgp_edge.
|
||||||
void mgp_edge_destroy(struct mgp_edge *e);
|
void mgp_edge_destroy(struct mgp_edge *e);
|
||||||
|
|
||||||
|
/// Return whether the given mgp_edge is deleted.
|
||||||
|
enum mgp_error mgp_edge_is_deleted(struct mgp_edge *e, int *result);
|
||||||
|
|
||||||
/// Result is non-zero if given edges are equal, otherwise 0.
|
/// Result is non-zero if given edges are equal, otherwise 0.
|
||||||
enum mgp_error mgp_edge_equal(struct mgp_edge *e1, struct mgp_edge *e2, int *result);
|
enum mgp_error mgp_edge_equal(struct mgp_edge *e1, struct mgp_edge *e2, int *result);
|
||||||
|
|
||||||
@@ -876,12 +891,77 @@ enum mgp_error mgp_edge_iter_properties(struct mgp_edge *e, struct mgp_memory *m
|
|||||||
enum mgp_error mgp_graph_get_vertex_by_id(struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory,
|
enum mgp_error mgp_graph_get_vertex_by_id(struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory,
|
||||||
struct mgp_vertex **result);
|
struct mgp_vertex **result);
|
||||||
|
|
||||||
|
/// Creates label index for given label.
|
||||||
|
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
|
||||||
|
/// if label index already exists, result will be 0, otherwise 1.
|
||||||
|
enum mgp_error mgp_create_label_index(struct mgp_graph *graph, const char *label, int *result);
|
||||||
|
|
||||||
|
/// Drop label index.
|
||||||
|
enum mgp_error mgp_drop_label_index(struct mgp_graph *graph, const char *label, int *result);
|
||||||
|
|
||||||
|
/// List all label indices.
|
||||||
|
enum mgp_error mgp_list_all_label_indices(struct mgp_graph *graph, struct mgp_memory *memory, struct mgp_list **result);
|
||||||
|
|
||||||
|
/// Creates label-property index for given label and propery.
|
||||||
|
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
|
||||||
|
/// if label property index already exists, result will be 0, otherwise 1.
|
||||||
|
enum mgp_error mgp_create_label_property_index(struct mgp_graph *graph, const char *label, const char *property,
|
||||||
|
int *result);
|
||||||
|
|
||||||
|
/// Drops label-property index for given label and propery.
|
||||||
|
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
|
||||||
|
/// if dropping label property index failed, result will be 0, otherwise 1.
|
||||||
|
enum mgp_error mgp_drop_label_property_index(struct mgp_graph *graph, const char *label, const char *property,
|
||||||
|
int *result);
|
||||||
|
|
||||||
|
/// List all label+property indices.
|
||||||
|
enum mgp_error mgp_list_all_label_property_indices(struct mgp_graph *graph, struct mgp_memory *memory,
|
||||||
|
struct mgp_list **result);
|
||||||
|
|
||||||
|
/// Creates existence constraint for given label and property.
|
||||||
|
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
|
||||||
|
/// if creating existence constraint failed, result will be 0, otherwise 1.
|
||||||
|
enum mgp_error mgp_create_existence_constraint(struct mgp_graph *graph, const char *label, const char *property,
|
||||||
|
int *result);
|
||||||
|
|
||||||
|
/// Drops existence constraint for given label and property.
|
||||||
|
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
|
||||||
|
/// if dropping existence constraint failed, result will be 0, otherwise 1.
|
||||||
|
enum mgp_error mgp_drop_existence_constraint(struct mgp_graph *graph, const char *label, const char *property,
|
||||||
|
int *result);
|
||||||
|
|
||||||
|
/// List all existence constraints.
|
||||||
|
enum mgp_error mgp_list_all_existence_constraints(struct mgp_graph *graph, struct mgp_memory *memory,
|
||||||
|
struct mgp_list **result);
|
||||||
|
|
||||||
|
/// Creates unique constraint for given label and properties.
|
||||||
|
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
|
||||||
|
/// if creating unique constraint failed, result will be 0, otherwise 1.
|
||||||
|
enum mgp_error mgp_create_unique_constraint(struct mgp_graph *graph, const char *label, struct mgp_value *properties,
|
||||||
|
int *result);
|
||||||
|
|
||||||
|
/// Drops unique constraint for given label and properties.
|
||||||
|
/// mgp_error::MGP_ERROR_NO_ERROR is always returned.
|
||||||
|
/// if dropping unique constraint failed, result will be 0, otherwise 1.
|
||||||
|
enum mgp_error mgp_drop_unique_constraint(struct mgp_graph *graph, const char *label, struct mgp_value *properties,
|
||||||
|
int *result);
|
||||||
|
|
||||||
|
/// List all unique constraints
|
||||||
|
enum mgp_error mgp_list_all_unique_constraints(struct mgp_graph *graph, struct mgp_memory *memory,
|
||||||
|
struct mgp_list **result);
|
||||||
|
|
||||||
/// Result is non-zero if the graph can be modified.
|
/// Result is non-zero if the graph can be modified.
|
||||||
/// If a graph is immutable, then vertices cannot be created or deleted, and all of the returned vertices will be
|
/// If a graph is immutable, then vertices cannot be created or deleted, and all of the returned vertices will be
|
||||||
/// immutable also. The same applies for edges.
|
/// immutable also. The same applies for edges.
|
||||||
/// Current implementation always returns without errors.
|
/// Current implementation always returns without errors.
|
||||||
enum mgp_error mgp_graph_is_mutable(struct mgp_graph *graph, int *result);
|
enum mgp_error mgp_graph_is_mutable(struct mgp_graph *graph, int *result);
|
||||||
|
|
||||||
|
/// Result is non-zero if the graph is in transactional storage mode.
|
||||||
|
/// If a graph is not in transactional mode (i.e. analytical mode), then vertices and edges can be missing
|
||||||
|
/// because changes from other transactions are visible.
|
||||||
|
/// Current implementation always returns without errors.
|
||||||
|
enum mgp_error mgp_graph_is_transactional(struct mgp_graph *graph, int *result);
|
||||||
|
|
||||||
/// Add a new vertex to the graph.
|
/// Add a new vertex to the graph.
|
||||||
/// Resulting vertex must be freed using mgp_vertex_destroy.
|
/// Resulting vertex must be freed using mgp_vertex_destroy.
|
||||||
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
|
/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable.
|
||||||
|
|||||||
403
include/mgp.hpp
403
include/mgp.hpp
File diff suppressed because it is too large
Load Diff
@@ -294,3 +294,10 @@ set_path_external_library(jemalloc STATIC
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/jemalloc/include/)
|
${CMAKE_CURRENT_SOURCE_DIR}/jemalloc/include/)
|
||||||
|
|
||||||
import_header_library(rangev3 ${CMAKE_CURRENT_SOURCE_DIR}/rangev3/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,6 +126,7 @@ declare -A primary_urls=(
|
|||||||
["absl"]="https://$local_cache_host/git/abseil-cpp.git"
|
["absl"]="https://$local_cache_host/git/abseil-cpp.git"
|
||||||
["jemalloc"]="https://$local_cache_host/git/jemalloc.git"
|
["jemalloc"]="https://$local_cache_host/git/jemalloc.git"
|
||||||
["range-v3"]="https://$local_cache_host/git/ericniebler/range-v3.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
|
# The goal of secondary urls is to have links to the "source of truth" of
|
||||||
@@ -155,6 +156,7 @@ declare -A secondary_urls=(
|
|||||||
["absl"]="https://github.com/abseil/abseil-cpp.git"
|
["absl"]="https://github.com/abseil/abseil-cpp.git"
|
||||||
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
|
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
|
||||||
["range-v3"]="https://github.com/ericniebler/range-v3.git"
|
["range-v3"]="https://github.com/ericniebler/range-v3.git"
|
||||||
|
["nuraft"]="https://github.com/eBay/NuRaft.git"
|
||||||
)
|
)
|
||||||
|
|
||||||
# antlr
|
# antlr
|
||||||
@@ -259,7 +261,7 @@ repo_clone_try_double "${primary_urls[absl]}" "${secondary_urls[absl]}" "absl" "
|
|||||||
|
|
||||||
# jemalloc ea6b3e973b477b8061e0076bb257dbd7f3faa756
|
# jemalloc ea6b3e973b477b8061e0076bb257dbd7f3faa756
|
||||||
JEMALLOC_COMMIT_VERSION="5.2.1"
|
JEMALLOC_COMMIT_VERSION="5.2.1"
|
||||||
repo_clone_try_double "${secondary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION"
|
repo_clone_try_double "${primary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION"
|
||||||
|
|
||||||
# this is hack for cmake in libs to set path, and for FindJemalloc to use Jemalloc_INCLUDE_DIR
|
# this is hack for cmake in libs to set path, and for FindJemalloc to use Jemalloc_INCLUDE_DIR
|
||||||
pushd jemalloc
|
pushd jemalloc
|
||||||
@@ -277,3 +279,10 @@ popd
|
|||||||
#range-v3 release-0.12.0
|
#range-v3 release-0.12.0
|
||||||
range_v3_ref="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"
|
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
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ ADDITIONAL USE GRANT: You may use the Licensed Work in accordance with the
|
|||||||
3. using the Licensed Work to create a work or solution
|
3. using the Licensed Work to create a work or solution
|
||||||
which competes (or might reasonably be expected to
|
which competes (or might reasonably be expected to
|
||||||
compete) with the Licensed Work.
|
compete) with the Licensed Work.
|
||||||
CHANGE DATE: 2027-30-10
|
CHANGE DATE: 2028-21-01
|
||||||
CHANGE LICENSE: Apache License, Version 2.0
|
CHANGE LICENSE: Apache License, Version 2.0
|
||||||
|
|
||||||
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
||||||
|
|||||||
@@ -10,18 +10,33 @@
|
|||||||
// licenses/APL.txt.
|
// licenses/APL.txt.
|
||||||
|
|
||||||
#include <mgp.hpp>
|
#include <mgp.hpp>
|
||||||
|
#include "utils/string.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace Schema {
|
namespace Schema {
|
||||||
|
|
||||||
/*NodeTypeProperties and RelTypeProperties constants*/
|
constexpr std::string_view kStatusKept = "Kept";
|
||||||
|
constexpr std::string_view kStatusCreated = "Created";
|
||||||
|
constexpr std::string_view kStatusDropped = "Dropped";
|
||||||
constexpr std::string_view kReturnNodeType = "nodeType";
|
constexpr std::string_view kReturnNodeType = "nodeType";
|
||||||
constexpr std::string_view kProcedureNodeType = "node_type_properties";
|
constexpr std::string_view kProcedureNodeType = "node_type_properties";
|
||||||
constexpr std::string_view kProcedureRelType = "rel_type_properties";
|
constexpr std::string_view kProcedureRelType = "rel_type_properties";
|
||||||
|
constexpr std::string_view kProcedureAssert = "assert";
|
||||||
constexpr std::string_view kReturnLabels = "nodeLabels";
|
constexpr std::string_view kReturnLabels = "nodeLabels";
|
||||||
constexpr std::string_view kReturnRelType = "relType";
|
constexpr std::string_view kReturnRelType = "relType";
|
||||||
constexpr std::string_view kReturnPropertyName = "propertyName";
|
constexpr std::string_view kReturnPropertyName = "propertyName";
|
||||||
constexpr std::string_view kReturnPropertyType = "propertyTypes";
|
constexpr std::string_view kReturnPropertyType = "propertyTypes";
|
||||||
constexpr std::string_view kReturnMandatory = "mandatory";
|
constexpr std::string_view kReturnMandatory = "mandatory";
|
||||||
|
constexpr std::string_view kReturnLabel = "label";
|
||||||
|
constexpr std::string_view kReturnKey = "key";
|
||||||
|
constexpr std::string_view kReturnKeys = "keys";
|
||||||
|
constexpr std::string_view kReturnUnique = "unique";
|
||||||
|
constexpr std::string_view kReturnAction = "action";
|
||||||
|
constexpr std::string_view kParameterIndices = "indices";
|
||||||
|
constexpr std::string_view kParameterUniqueConstraints = "unique_constraints";
|
||||||
|
constexpr std::string_view kParameterExistenceConstraints = "existence_constraints";
|
||||||
|
constexpr std::string_view kParameterDropExisting = "drop_existing";
|
||||||
|
|
||||||
std::string TypeOf(const mgp::Type &type);
|
std::string TypeOf(const mgp::Type &type);
|
||||||
|
|
||||||
@@ -35,6 +50,7 @@ void ProcessPropertiesRel(mgp::Record &record, const std::string_view &type, con
|
|||||||
|
|
||||||
void NodeTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
|
void NodeTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
|
||||||
void RelTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
|
void RelTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
|
||||||
|
void Assert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory);
|
||||||
} // namespace Schema
|
} // namespace Schema
|
||||||
|
|
||||||
/*we have << operator for type in Cpp API, but in it we return somewhat different strings than I would like in this
|
/*we have << operator for type in Cpp API, but in it we return somewhat different strings than I would like in this
|
||||||
@@ -92,31 +108,83 @@ void Schema::ProcessPropertiesRel(mgp::Record &record, const std::string_view &t
|
|||||||
record.Insert(std::string(kReturnMandatory).c_str(), mandatory);
|
record.Insert(std::string(kReturnMandatory).c_str(), mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Schema::NodeTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
struct Property {
|
||||||
|
std::string name;
|
||||||
|
mgp::Value value;
|
||||||
|
|
||||||
|
Property(const std::string &name, mgp::Value &&value) : name(name), value(std::move(value)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LabelsHash {
|
||||||
|
std::size_t operator()(const std::set<std::string> &set) const {
|
||||||
|
std::size_t seed = set.size();
|
||||||
|
for (const auto &i : set) {
|
||||||
|
seed ^= std::hash<std::string>{}(i) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LabelsComparator {
|
||||||
|
bool operator()(const std::set<std::string> &lhs, const std::set<std::string> &rhs) const { return lhs == rhs; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PropertyComparator {
|
||||||
|
bool operator()(const Property &lhs, const Property &rhs) const { return lhs.name < rhs.name; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PropertyInfo {
|
||||||
|
std::set<Property, PropertyComparator> properties;
|
||||||
|
bool mandatory;
|
||||||
|
};
|
||||||
|
|
||||||
|
void Schema::NodeTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result,
|
||||||
|
mgp_memory *memory) {
|
||||||
mgp::MemoryDispatcherGuard guard{memory};
|
mgp::MemoryDispatcherGuard guard{memory};
|
||||||
;
|
|
||||||
const auto record_factory = mgp::RecordFactory(result);
|
const auto record_factory = mgp::RecordFactory(result);
|
||||||
try {
|
try {
|
||||||
const mgp::Graph graph = mgp::Graph(memgraph_graph);
|
std::unordered_map<std::set<std::string>, PropertyInfo, LabelsHash, LabelsComparator> node_types_properties;
|
||||||
for (auto node : graph.Nodes()) {
|
|
||||||
std::string type = "";
|
for (auto node : mgp::Graph(memgraph_graph).Nodes()) {
|
||||||
mgp::List labels = mgp::List();
|
std::set<std::string> labels_set = {};
|
||||||
for (auto label : node.Labels()) {
|
for (auto label : node.Labels()) {
|
||||||
labels.AppendExtend(mgp::Value(label));
|
labels_set.emplace(label);
|
||||||
type += ":`" + std::string(label) + "`";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.Properties().size() == 0) {
|
if (node_types_properties.find(labels_set) == node_types_properties.end()) {
|
||||||
auto record = record_factory.NewRecord();
|
node_types_properties[labels_set] = PropertyInfo{std::set<Property, PropertyComparator>(), true};
|
||||||
ProcessPropertiesNode<std::string>(record, type, labels, "", "", false);
|
}
|
||||||
|
|
||||||
|
if (node.Properties().empty()) {
|
||||||
|
node_types_properties[labels_set].mandatory = false; // if there is node with no property, it is not mandatory
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &property_info = node_types_properties.at(labels_set);
|
||||||
for (auto &[key, prop] : node.Properties()) {
|
for (auto &[key, prop] : node.Properties()) {
|
||||||
auto property_type = mgp::List();
|
property_info.properties.emplace(key, std::move(prop));
|
||||||
|
if (property_info.mandatory) {
|
||||||
|
property_info.mandatory =
|
||||||
|
property_info.properties.size() == 1; // if there is only one property, it is mandatory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &[labels, property_info] : node_types_properties) {
|
||||||
|
std::string label_type;
|
||||||
|
mgp::List labels_list = mgp::List();
|
||||||
|
for (auto const &label : labels) {
|
||||||
|
label_type += ":`" + std::string(label) + "`";
|
||||||
|
labels_list.AppendExtend(mgp::Value(label));
|
||||||
|
}
|
||||||
|
for (auto const &prop : property_info.properties) {
|
||||||
auto record = record_factory.NewRecord();
|
auto record = record_factory.NewRecord();
|
||||||
property_type.AppendExtend(mgp::Value(TypeOf(prop.Type())));
|
ProcessPropertiesNode(record, label_type, labels_list, prop.name, TypeOf(prop.value.Type()),
|
||||||
ProcessPropertiesNode<mgp::List>(record, type, labels, key, property_type, true);
|
property_info.mandatory);
|
||||||
|
}
|
||||||
|
if (property_info.properties.empty()) {
|
||||||
|
auto record = record_factory.NewRecord();
|
||||||
|
ProcessPropertiesNode<std::string>(record, label_type, labels_list, "", "", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,26 +194,43 @@ void Schema::NodeTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Schema::RelTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
void Schema::RelTypeProperties(mgp_list * /*args*/, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||||
mgp::MemoryDispatcherGuard guard{memory};
|
mgp::MemoryDispatcherGuard guard{memory};
|
||||||
;
|
|
||||||
|
std::unordered_map<std::string, PropertyInfo> rel_types_properties;
|
||||||
const auto record_factory = mgp::RecordFactory(result);
|
const auto record_factory = mgp::RecordFactory(result);
|
||||||
try {
|
try {
|
||||||
const mgp::Graph graph = mgp::Graph(memgraph_graph);
|
const mgp::Graph graph = mgp::Graph(memgraph_graph);
|
||||||
|
|
||||||
for (auto rel : graph.Relationships()) {
|
for (auto rel : graph.Relationships()) {
|
||||||
std::string type = ":`" + std::string(rel.Type()) + "`";
|
std::string rel_type = std::string(rel.Type());
|
||||||
if (rel.Properties().size() == 0) {
|
if (rel_types_properties.find(rel_type) == rel_types_properties.end()) {
|
||||||
auto record = record_factory.NewRecord();
|
rel_types_properties[rel_type] = PropertyInfo{std::set<Property, PropertyComparator>(), true};
|
||||||
ProcessPropertiesRel<std::string>(record, type, "", "", false);
|
}
|
||||||
|
|
||||||
|
if (rel.Properties().empty()) {
|
||||||
|
rel_types_properties[rel_type].mandatory = false; // if there is rel with no property, it is not mandatory
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &property_info = rel_types_properties.at(rel_type);
|
||||||
for (auto &[key, prop] : rel.Properties()) {
|
for (auto &[key, prop] : rel.Properties()) {
|
||||||
auto property_type = mgp::List();
|
property_info.properties.emplace(key, std::move(prop));
|
||||||
|
if (property_info.mandatory) {
|
||||||
|
property_info.mandatory =
|
||||||
|
property_info.properties.size() == 1; // if there is only one property, it is mandatory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &[type, property_info] : rel_types_properties) {
|
||||||
|
std::string type_str = ":`" + std::string(type) + "`";
|
||||||
|
for (auto const &prop : property_info.properties) {
|
||||||
auto record = record_factory.NewRecord();
|
auto record = record_factory.NewRecord();
|
||||||
property_type.AppendExtend(mgp::Value(TypeOf(prop.Type())));
|
ProcessPropertiesRel(record, type_str, prop.name, TypeOf(prop.value.Type()), property_info.mandatory);
|
||||||
ProcessPropertiesRel<mgp::List>(record, type, key, property_type, true);
|
}
|
||||||
|
if (property_info.properties.empty()) {
|
||||||
|
auto record = record_factory.NewRecord();
|
||||||
|
ProcessPropertiesRel<std::string>(record, type_str, "", "", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,29 +240,435 @@ void Schema::RelTypeProperties(mgp_list *args, mgp_graph *memgraph_graph, mgp_re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InsertRecordForLabelIndex(const auto &record_factory, const std::string_view label,
|
||||||
|
const std::string_view status) {
|
||||||
|
auto record = record_factory.NewRecord();
|
||||||
|
record.Insert(std::string(Schema::kReturnLabel).c_str(), label);
|
||||||
|
record.Insert(std::string(Schema::kReturnKey).c_str(), "");
|
||||||
|
record.Insert(std::string(Schema::kReturnKeys).c_str(), mgp::List());
|
||||||
|
record.Insert(std::string(Schema::kReturnUnique).c_str(), false);
|
||||||
|
record.Insert(std::string(Schema::kReturnAction).c_str(), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InsertRecordForUniqueConstraint(const auto &record_factory, const std::string_view label,
|
||||||
|
const mgp::List &properties, const std::string_view status) {
|
||||||
|
auto record = record_factory.NewRecord();
|
||||||
|
record.Insert(std::string(Schema::kReturnLabel).c_str(), label);
|
||||||
|
record.Insert(std::string(Schema::kReturnKey).c_str(), properties.ToString());
|
||||||
|
record.Insert(std::string(Schema::kReturnKeys).c_str(), properties);
|
||||||
|
record.Insert(std::string(Schema::kReturnUnique).c_str(), true);
|
||||||
|
record.Insert(std::string(Schema::kReturnAction).c_str(), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InsertRecordForLabelPropertyIndexAndExistenceConstraint(const auto &record_factory, const std::string_view label,
|
||||||
|
const std::string_view property,
|
||||||
|
const std::string_view status) {
|
||||||
|
auto record = record_factory.NewRecord();
|
||||||
|
record.Insert(std::string(Schema::kReturnLabel).c_str(), label);
|
||||||
|
record.Insert(std::string(Schema::kReturnKey).c_str(), property);
|
||||||
|
record.Insert(std::string(Schema::kReturnKeys).c_str(), mgp::List({mgp::Value(property)}));
|
||||||
|
record.Insert(std::string(Schema::kReturnUnique).c_str(), false);
|
||||||
|
record.Insert(std::string(Schema::kReturnAction).c_str(), status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessCreatingLabelIndex(const std::string_view label, const std::set<std::string_view> &existing_label_indices,
|
||||||
|
mgp_graph *memgraph_graph, const auto &record_factory) {
|
||||||
|
if (existing_label_indices.contains(label)) {
|
||||||
|
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusKept);
|
||||||
|
} else if (mgp::CreateLabelIndex(memgraph_graph, label)) {
|
||||||
|
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusCreated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TFunc>
|
||||||
|
void ProcessCreatingLabelPropertyIndexAndExistenceConstraint(const std::string_view label,
|
||||||
|
const std::string_view property,
|
||||||
|
const std::set<std::string_view> &existing_collection,
|
||||||
|
const TFunc &func_creation, mgp_graph *memgraph_graph,
|
||||||
|
const auto &record_factory) {
|
||||||
|
const auto label_property_search_key = std::string(label) + ":" + std::string(property);
|
||||||
|
if (existing_collection.contains(label_property_search_key)) {
|
||||||
|
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusKept);
|
||||||
|
} else if (func_creation(memgraph_graph, label, property)) {
|
||||||
|
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusCreated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We collect properties for which index was created.
|
||||||
|
using AssertedIndices = std::set<std::string, std::less<>>;
|
||||||
|
AssertedIndices CreateIndicesForLabel(const std::string_view label, const mgp::Value &properties_val,
|
||||||
|
mgp_graph *memgraph_graph, const auto &record_factory,
|
||||||
|
const std::set<std::string_view> &existing_label_indices,
|
||||||
|
const std::set<std::string_view> &existing_label_property_indices) {
|
||||||
|
AssertedIndices asserted_indices;
|
||||||
|
if (!properties_val.IsList()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (const auto properties = properties_val.ValueList();
|
||||||
|
properties.Empty() && mgp::CreateLabelIndex(memgraph_graph, label)) {
|
||||||
|
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusCreated);
|
||||||
|
asserted_indices.emplace("");
|
||||||
|
} else {
|
||||||
|
std::for_each(properties.begin(), properties.end(),
|
||||||
|
[&label, &existing_label_indices, &existing_label_property_indices, &memgraph_graph, &record_factory,
|
||||||
|
&asserted_indices](const mgp::Value &property) {
|
||||||
|
if (!property.IsString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto property_str = property.ValueString();
|
||||||
|
if (property_str.empty()) {
|
||||||
|
ProcessCreatingLabelIndex(label, existing_label_indices, memgraph_graph, record_factory);
|
||||||
|
asserted_indices.emplace("");
|
||||||
|
} else {
|
||||||
|
ProcessCreatingLabelPropertyIndexAndExistenceConstraint(
|
||||||
|
label, property_str, existing_label_property_indices, mgp::CreateLabelPropertyIndex,
|
||||||
|
memgraph_graph, record_factory);
|
||||||
|
asserted_indices.emplace(property_str);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return asserted_indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessIndices(const mgp::Map &indices_map, mgp_graph *memgraph_graph, const auto &record_factory,
|
||||||
|
bool drop_existing) {
|
||||||
|
auto mgp_existing_label_indices = mgp::ListAllLabelIndices(memgraph_graph);
|
||||||
|
auto mgp_existing_label_property_indices = mgp::ListAllLabelPropertyIndices(memgraph_graph);
|
||||||
|
|
||||||
|
std::set<std::string_view> existing_label_indices;
|
||||||
|
std::transform(mgp_existing_label_indices.begin(), mgp_existing_label_indices.end(),
|
||||||
|
std::inserter(existing_label_indices, existing_label_indices.begin()),
|
||||||
|
[](const mgp::Value &index) { return index.ValueString(); });
|
||||||
|
|
||||||
|
std::set<std::string_view> existing_label_property_indices;
|
||||||
|
std::transform(mgp_existing_label_property_indices.begin(), mgp_existing_label_property_indices.end(),
|
||||||
|
std::inserter(existing_label_property_indices, existing_label_property_indices.begin()),
|
||||||
|
[](const mgp::Value &index) { return index.ValueString(); });
|
||||||
|
|
||||||
|
std::set<std::string> asserted_label_indices;
|
||||||
|
std::set<std::string> asserted_label_property_indices;
|
||||||
|
|
||||||
|
auto merge_label_property = [](const std::string &label, const std::string &property) {
|
||||||
|
return label + ":" + property;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &index : indices_map) {
|
||||||
|
const std::string_view label = index.key;
|
||||||
|
const mgp::Value &properties_val = index.value;
|
||||||
|
|
||||||
|
AssertedIndices asserted_indices_new = CreateIndicesForLabel(
|
||||||
|
label, properties_val, memgraph_graph, record_factory, existing_label_indices, existing_label_property_indices);
|
||||||
|
|
||||||
|
if (!drop_existing) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::ranges::for_each(asserted_indices_new, [&asserted_label_indices, &asserted_label_property_indices, label,
|
||||||
|
&merge_label_property](const std::string &property) {
|
||||||
|
if (property.empty()) {
|
||||||
|
asserted_label_indices.emplace(label);
|
||||||
|
} else {
|
||||||
|
asserted_label_property_indices.emplace(merge_label_property(std::string(label), property));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!drop_existing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string_view> label_indices_to_drop;
|
||||||
|
std::ranges::set_difference(existing_label_indices, asserted_label_indices,
|
||||||
|
std::inserter(label_indices_to_drop, label_indices_to_drop.begin()));
|
||||||
|
|
||||||
|
std::ranges::for_each(label_indices_to_drop, [memgraph_graph, &record_factory](const std::string_view label) {
|
||||||
|
if (mgp::DropLabelIndex(memgraph_graph, label)) {
|
||||||
|
InsertRecordForLabelIndex(record_factory, label, Schema::kStatusDropped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::set<std::string_view> label_property_indices_to_drop;
|
||||||
|
std::ranges::set_difference(existing_label_property_indices, asserted_label_property_indices,
|
||||||
|
std::inserter(label_property_indices_to_drop, label_property_indices_to_drop.begin()));
|
||||||
|
|
||||||
|
auto decouple_label_property = [](const std::string_view label_property) {
|
||||||
|
const auto label_size = label_property.find(':');
|
||||||
|
const auto label = std::string(label_property.substr(0, label_size));
|
||||||
|
const auto property = std::string(label_property.substr(label_size + 1));
|
||||||
|
return std::make_pair(label, property);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ranges::for_each(label_property_indices_to_drop, [memgraph_graph, &record_factory, decouple_label_property](
|
||||||
|
const std::string_view label_property) {
|
||||||
|
const auto [label, property] = decouple_label_property(label_property);
|
||||||
|
if (mgp::DropLabelPropertyIndex(memgraph_graph, label, property)) {
|
||||||
|
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusDropped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
using ExistenceConstraintsStorage = std::set<std::string_view>;
|
||||||
|
|
||||||
|
ExistenceConstraintsStorage CreateExistenceConstraintsForLabel(
|
||||||
|
const std::string_view label, const mgp::Value &properties_val, mgp_graph *memgraph_graph,
|
||||||
|
const auto &record_factory, const std::set<std::string_view> &existing_existence_constraints) {
|
||||||
|
ExistenceConstraintsStorage asserted_existence_constraints;
|
||||||
|
if (!properties_val.IsList()) {
|
||||||
|
return asserted_existence_constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto validate_property = [](const mgp::Value &property) -> bool {
|
||||||
|
return property.IsString() && !property.ValueString().empty();
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto &properties = properties_val.ValueList();
|
||||||
|
std::for_each(properties.begin(), properties.end(),
|
||||||
|
[&label, &existing_existence_constraints, &asserted_existence_constraints, &memgraph_graph,
|
||||||
|
&record_factory, &validate_property](const mgp::Value &property) {
|
||||||
|
if (!validate_property(property)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string_view property_str = property.ValueString();
|
||||||
|
asserted_existence_constraints.emplace(property_str);
|
||||||
|
ProcessCreatingLabelPropertyIndexAndExistenceConstraint(
|
||||||
|
label, property_str, existing_existence_constraints, mgp::CreateExistenceConstraint,
|
||||||
|
memgraph_graph, record_factory);
|
||||||
|
});
|
||||||
|
return asserted_existence_constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessExistenceConstraints(const mgp::Map &existence_constraints_map, mgp_graph *memgraph_graph,
|
||||||
|
const auto &record_factory, bool drop_existing) {
|
||||||
|
auto mgp_existing_existence_constraints = mgp::ListAllExistenceConstraints(memgraph_graph);
|
||||||
|
std::set<std::string_view> existing_existence_constraints;
|
||||||
|
std::transform(mgp_existing_existence_constraints.begin(), mgp_existing_existence_constraints.end(),
|
||||||
|
std::inserter(existing_existence_constraints, existing_existence_constraints.begin()),
|
||||||
|
[](const mgp::Value &constraint) { return constraint.ValueString(); });
|
||||||
|
|
||||||
|
auto merge_label_property = [](const std::string_view label, const std::string_view property) {
|
||||||
|
auto str = std::string(label) + ":";
|
||||||
|
str += property;
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
ExistenceConstraintsStorage asserted_existence_constraints;
|
||||||
|
|
||||||
|
for (const auto &existing_constraint : existence_constraints_map) {
|
||||||
|
const std::string_view label = existing_constraint.key;
|
||||||
|
const mgp::Value &properties_val = existing_constraint.value;
|
||||||
|
auto asserted_existence_constraints_new = CreateExistenceConstraintsForLabel(
|
||||||
|
label, properties_val, memgraph_graph, record_factory, existing_existence_constraints);
|
||||||
|
if (!drop_existing) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ranges::for_each(asserted_existence_constraints_new, [&asserted_existence_constraints, &merge_label_property,
|
||||||
|
label](const std::string_view property) {
|
||||||
|
asserted_existence_constraints.emplace(merge_label_property(label, property));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!drop_existing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string_view> existence_constraints_to_drop;
|
||||||
|
std::ranges::set_difference(existing_existence_constraints, asserted_existence_constraints,
|
||||||
|
std::inserter(existence_constraints_to_drop, existence_constraints_to_drop.begin()));
|
||||||
|
|
||||||
|
auto decouple_label_property = [](const std::string_view label_property) {
|
||||||
|
const auto label_size = label_property.find(':');
|
||||||
|
const auto label = std::string(label_property.substr(0, label_size));
|
||||||
|
const auto property = std::string(label_property.substr(label_size + 1));
|
||||||
|
return std::make_pair(label, property);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ranges::for_each(existence_constraints_to_drop, [&](const std::string_view label_property) {
|
||||||
|
const auto [label, property] = decouple_label_property(label_property);
|
||||||
|
if (mgp::DropExistenceConstraint(memgraph_graph, label, property)) {
|
||||||
|
InsertRecordForLabelPropertyIndexAndExistenceConstraint(record_factory, label, property, Schema::kStatusDropped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
using AssertedUniqueConstraintsStorage = std::set<std::set<std::string_view>>;
|
||||||
|
AssertedUniqueConstraintsStorage CreateUniqueConstraintsForLabel(
|
||||||
|
const std::string_view label, const mgp::Value &unique_props_nested,
|
||||||
|
const std::map<std::string_view, AssertedUniqueConstraintsStorage> &existing_unique_constraints,
|
||||||
|
mgp_graph *memgraph_graph, const auto &record_factory) {
|
||||||
|
AssertedUniqueConstraintsStorage asserted_unique_constraints;
|
||||||
|
if (!unique_props_nested.IsList()) {
|
||||||
|
return asserted_unique_constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto validate_unique_constraint_props = [](const mgp::Value &properties) -> bool {
|
||||||
|
if (!properties.IsList()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto &properties_list = properties.ValueList();
|
||||||
|
if (properties_list.Empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return std::all_of(properties_list.begin(), properties_list.end(), [](const mgp::Value &property) {
|
||||||
|
return property.IsString() && !property.ValueString().empty();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto unique_constraint_exists =
|
||||||
|
[](const std::string_view label, const std::set<std::string_view> &properties,
|
||||||
|
const std::map<std::string_view, AssertedUniqueConstraintsStorage> &existing_unique_constraints) -> bool {
|
||||||
|
auto iter = existing_unique_constraints.find(label);
|
||||||
|
if (iter == existing_unique_constraints.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return iter->second.find(properties) != iter->second.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto unique_props_nested_list = unique_props_nested.ValueList();
|
||||||
|
const auto &properties : unique_props_nested_list) {
|
||||||
|
if (!validate_unique_constraint_props(properties)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto properties_list = properties.ValueList();
|
||||||
|
std::set<std::string_view> properties_coll;
|
||||||
|
std::transform(properties_list.begin(), properties_list.end(),
|
||||||
|
std::inserter(properties_coll, properties_coll.begin()),
|
||||||
|
[](const mgp::Value &property) { return property.ValueString(); });
|
||||||
|
|
||||||
|
if (unique_constraint_exists(label, properties_coll, existing_unique_constraints)) {
|
||||||
|
InsertRecordForUniqueConstraint(record_factory, label, properties_list, Schema::kStatusKept);
|
||||||
|
} else if (mgp::CreateUniqueConstraint(memgraph_graph, label, properties.ptr())) {
|
||||||
|
InsertRecordForUniqueConstraint(record_factory, label, properties_list, Schema::kStatusCreated);
|
||||||
|
}
|
||||||
|
asserted_unique_constraints.emplace(std::move(properties_coll));
|
||||||
|
}
|
||||||
|
return asserted_unique_constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessUniqueConstraints(const mgp::Map &unique_constraints_map, mgp_graph *memgraph_graph,
|
||||||
|
const auto &record_factory, bool drop_existing) {
|
||||||
|
auto mgp_existing_unique_constraints = mgp::ListAllUniqueConstraints(memgraph_graph);
|
||||||
|
// label-unique_constraints pair
|
||||||
|
std::map<std::string_view, AssertedUniqueConstraintsStorage> existing_unique_constraints;
|
||||||
|
for (const auto &constraint : mgp_existing_unique_constraints) {
|
||||||
|
auto constraint_list = constraint.ValueList();
|
||||||
|
std::set<std::string_view> properties;
|
||||||
|
for (int i = 1; i < constraint_list.Size(); i++) {
|
||||||
|
properties.emplace(constraint_list[i].ValueString());
|
||||||
|
}
|
||||||
|
const std::string_view label = constraint_list[0].ValueString();
|
||||||
|
auto [it, inserted] = existing_unique_constraints.try_emplace(label, AssertedUniqueConstraintsStorage{properties});
|
||||||
|
if (!inserted) {
|
||||||
|
it->second.emplace(std::move(properties));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string_view, AssertedUniqueConstraintsStorage> asserted_unique_constraints;
|
||||||
|
|
||||||
|
for (const auto &[label, unique_props_nested] : unique_constraints_map) {
|
||||||
|
auto asserted_unique_constraints_new = CreateUniqueConstraintsForLabel(
|
||||||
|
label, unique_props_nested, existing_unique_constraints, memgraph_graph, record_factory);
|
||||||
|
if (drop_existing) {
|
||||||
|
asserted_unique_constraints.emplace(label, std::move(asserted_unique_constraints_new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!drop_existing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string_view, std::set<std::string_view>>> unique_constraints_to_drop;
|
||||||
|
|
||||||
|
// Check for each label for we found existing constraint in the DB whether it was asserted.
|
||||||
|
// If no unique constraint was found with label, we can drop all unique constraints for this label. (if branch)
|
||||||
|
// If some unique constraint was found with label, we can drop only those unique constraints that were not asserted.
|
||||||
|
// (else branch.)
|
||||||
|
std::ranges::for_each(existing_unique_constraints, [&asserted_unique_constraints, &unique_constraints_to_drop](
|
||||||
|
const auto &existing_label_unique_constraints) {
|
||||||
|
const auto &label = existing_label_unique_constraints.first;
|
||||||
|
const auto &existing_unique_constraints_for_label = existing_label_unique_constraints.second;
|
||||||
|
const auto &asserted_unique_constraints_for_label = asserted_unique_constraints.find(label);
|
||||||
|
if (asserted_unique_constraints_for_label == asserted_unique_constraints.end()) {
|
||||||
|
std::ranges::for_each(
|
||||||
|
std::make_move_iterator(existing_unique_constraints_for_label.begin()),
|
||||||
|
std::make_move_iterator(existing_unique_constraints_for_label.end()),
|
||||||
|
[&unique_constraints_to_drop, &label](std::set<std::string_view> existing_unique_constraint_for_label) {
|
||||||
|
unique_constraints_to_drop.emplace_back(label, std::move(existing_unique_constraint_for_label));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const auto &asserted_unique_constraints_for_label_coll = asserted_unique_constraints_for_label->second;
|
||||||
|
std::ranges::for_each(
|
||||||
|
std::make_move_iterator(existing_unique_constraints_for_label.begin()),
|
||||||
|
std::make_move_iterator(existing_unique_constraints_for_label.end()),
|
||||||
|
[&unique_constraints_to_drop, &label, &asserted_unique_constraints_for_label_coll](
|
||||||
|
std::set<std::string_view> existing_unique_constraint_for_label) {
|
||||||
|
if (!asserted_unique_constraints_for_label_coll.contains(existing_unique_constraint_for_label)) {
|
||||||
|
unique_constraints_to_drop.emplace_back(label, std::move(existing_unique_constraint_for_label));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
std::ranges::for_each(
|
||||||
|
unique_constraints_to_drop, [memgraph_graph, &record_factory](const auto &label_unique_constraint) {
|
||||||
|
const auto &[label, unique_constraint] = label_unique_constraint;
|
||||||
|
|
||||||
|
auto unique_constraint_list = mgp::List();
|
||||||
|
std::ranges::for_each(unique_constraint, [&unique_constraint_list](const std::string_view &property) {
|
||||||
|
unique_constraint_list.AppendExtend(mgp::Value(property));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mgp::DropUniqueConstraint(memgraph_graph, label, mgp::Value(unique_constraint_list).ptr())) {
|
||||||
|
InsertRecordForUniqueConstraint(record_factory, label, unique_constraint_list, Schema::kStatusDropped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Schema::Assert(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||||
|
mgp::MemoryDispatcherGuard guard{memory};
|
||||||
|
const auto record_factory = mgp::RecordFactory(result);
|
||||||
|
auto arguments = mgp::List(args);
|
||||||
|
auto indices_map = arguments[0].ValueMap();
|
||||||
|
auto unique_constraints_map = arguments[1].ValueMap();
|
||||||
|
auto existence_constraints_map = arguments[2].ValueMap();
|
||||||
|
auto drop_existing = arguments[3].ValueBool();
|
||||||
|
|
||||||
|
ProcessIndices(indices_map, memgraph_graph, record_factory, drop_existing);
|
||||||
|
ProcessExistenceConstraints(existence_constraints_map, memgraph_graph, record_factory, drop_existing);
|
||||||
|
ProcessUniqueConstraints(unique_constraints_map, memgraph_graph, record_factory, drop_existing);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
|
extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
|
||||||
try {
|
try {
|
||||||
mgp::MemoryDispatcherGuard guard{memory};
|
mgp::MemoryDispatcherGuard guard{memory};
|
||||||
;
|
|
||||||
|
|
||||||
AddProcedure(Schema::NodeTypeProperties, std::string(Schema::kProcedureNodeType).c_str(), mgp::ProcedureType::Read,
|
AddProcedure(Schema::NodeTypeProperties, Schema::kProcedureNodeType, mgp::ProcedureType::Read, {},
|
||||||
{},
|
{mgp::Return(Schema::kReturnNodeType, mgp::Type::String),
|
||||||
{mgp::Return(std::string(Schema::kReturnNodeType).c_str(), mgp::Type::String),
|
mgp::Return(Schema::kReturnLabels, {mgp::Type::List, mgp::Type::String}),
|
||||||
mgp::Return(std::string(Schema::kReturnLabels).c_str(), {mgp::Type::List, mgp::Type::String}),
|
mgp::Return(Schema::kReturnPropertyName, mgp::Type::String),
|
||||||
mgp::Return(std::string(Schema::kReturnPropertyName).c_str(), mgp::Type::String),
|
mgp::Return(Schema::kReturnPropertyType, mgp::Type::Any),
|
||||||
mgp::Return(std::string(Schema::kReturnPropertyType).c_str(), mgp::Type::Any),
|
mgp::Return(Schema::kReturnMandatory, mgp::Type::Bool)},
|
||||||
mgp::Return(std::string(Schema::kReturnMandatory).c_str(), mgp::Type::Bool)},
|
|
||||||
module, memory);
|
module, memory);
|
||||||
|
|
||||||
AddProcedure(Schema::RelTypeProperties, std::string(Schema::kProcedureRelType).c_str(), mgp::ProcedureType::Read,
|
AddProcedure(Schema::RelTypeProperties, Schema::kProcedureRelType, mgp::ProcedureType::Read, {},
|
||||||
{},
|
{mgp::Return(Schema::kReturnRelType, mgp::Type::String),
|
||||||
{mgp::Return(std::string(Schema::kReturnRelType).c_str(), mgp::Type::String),
|
mgp::Return(Schema::kReturnPropertyName, mgp::Type::String),
|
||||||
mgp::Return(std::string(Schema::kReturnPropertyName).c_str(), mgp::Type::String),
|
mgp::Return(Schema::kReturnPropertyType, mgp::Type::Any),
|
||||||
mgp::Return(std::string(Schema::kReturnPropertyType).c_str(), mgp::Type::Any),
|
mgp::Return(Schema::kReturnMandatory, mgp::Type::Bool)},
|
||||||
mgp::Return(std::string(Schema::kReturnMandatory).c_str(), mgp::Type::Bool)},
|
|
||||||
module, memory);
|
module, memory);
|
||||||
|
AddProcedure(
|
||||||
|
Schema::Assert, Schema::kProcedureAssert, mgp::ProcedureType::Read,
|
||||||
|
{
|
||||||
|
mgp::Parameter(Schema::kParameterIndices, {mgp::Type::Map, mgp::Type::Any}),
|
||||||
|
mgp::Parameter(Schema::kParameterUniqueConstraints, {mgp::Type::Map, mgp::Type::Any}),
|
||||||
|
mgp::Parameter(Schema::kParameterExistenceConstraints, {mgp::Type::Map, mgp::Type::Any},
|
||||||
|
mgp::Value(mgp::Map{})),
|
||||||
|
mgp::Parameter(Schema::kParameterDropExisting, mgp::Type::Bool, mgp::Value(true)),
|
||||||
|
},
|
||||||
|
{mgp::Return(Schema::kReturnLabel, mgp::Type::String), mgp::Return(Schema::kReturnKey, mgp::Type::String),
|
||||||
|
mgp::Return(Schema::kReturnKeys, {mgp::Type::List, mgp::Type::String}),
|
||||||
|
mgp::Return(Schema::kReturnUnique, mgp::Type::Bool), mgp::Return(Schema::kReturnAction, mgp::Type::String)},
|
||||||
|
module, memory);
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << "Error while initializing query module: " << e.what() << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,9 @@ def retry(retry_limit, timeout=100):
|
|||||||
except Exception:
|
except Exception:
|
||||||
time.sleep(timeout)
|
time.sleep(timeout)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return inner_func
|
return inner_func
|
||||||
|
|
||||||
|
|
||||||
@@ -200,19 +202,19 @@ if args.version:
|
|||||||
try:
|
try:
|
||||||
current_branch = get_output("git", "rev-parse", "--abbrev-ref", "HEAD")
|
current_branch = get_output("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||||
if current_branch != "master":
|
if current_branch != "master":
|
||||||
branches = get_output("git", "branch")
|
branches = get_output("git", "branch", "-r", "--list", "origin/master")
|
||||||
if "master" in branches:
|
if "origin/master" in branches:
|
||||||
# If master is present locally, the fetch is allowed to fail
|
# If master is present locally, the fetch is allowed to fail
|
||||||
# because this script will still be able to compare against the
|
# because this script will still be able to compare against the
|
||||||
# master branch.
|
# master branch.
|
||||||
try:
|
try:
|
||||||
get_output("git", "fetch", "origin", "master:master")
|
get_output("git", "fetch", "origin", "master")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# If master is not present locally, the fetch command has to
|
# If master is not present locally, the fetch command has to
|
||||||
# succeed because something else will fail otherwise.
|
# succeed because something else will fail otherwise.
|
||||||
get_output("git", "fetch", "origin", "master:master")
|
get_output("git", "fetch", "origin", "master")
|
||||||
except Exception:
|
except Exception:
|
||||||
print("Fatal error while ensuring local master branch.")
|
print("Fatal error while ensuring local master branch.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -232,7 +234,7 @@ for branch in branches:
|
|||||||
match = branch_regex.match(branch)
|
match = branch_regex.match(branch)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
version = tuple(map(int, match.group(1).split(".")))
|
version = tuple(map(int, match.group(1).split(".")))
|
||||||
master_branch_merge = get_output("git", "merge-base", "master", branch)
|
master_branch_merge = get_output("git", "merge-base", "origin/master", branch)
|
||||||
versions.append((version, branch, master_branch_merge))
|
versions.append((version, branch, master_branch_merge))
|
||||||
versions.sort(reverse=True)
|
versions.sort(reverse=True)
|
||||||
|
|
||||||
@@ -243,7 +245,7 @@ current_version = None
|
|||||||
for version in versions:
|
for version in versions:
|
||||||
version_tuple, branch, master_branch_merge = version
|
version_tuple, branch, master_branch_merge = version
|
||||||
current_branch_merge = get_output("git", "merge-base", current_hash, branch)
|
current_branch_merge = get_output("git", "merge-base", current_hash, branch)
|
||||||
master_current_merge = get_output("git", "merge-base", current_hash, "master")
|
master_current_merge = get_output("git", "merge-base", current_hash, "origin/master")
|
||||||
# The first check checks whether this commit is a child of `master` and
|
# The first check checks whether this commit is a child of `master` and
|
||||||
# the version branch was created before us.
|
# the version branch was created before us.
|
||||||
# The second check checks whether this commit is a child of the version
|
# The second check checks whether this commit is a child of the version
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ add_subdirectory(dbms)
|
|||||||
add_subdirectory(flags)
|
add_subdirectory(flags)
|
||||||
add_subdirectory(distributed)
|
add_subdirectory(distributed)
|
||||||
add_subdirectory(replication)
|
add_subdirectory(replication)
|
||||||
|
add_subdirectory(coordination)
|
||||||
|
add_subdirectory(replication_coordination_glue)
|
||||||
|
|
||||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||||
|
|
||||||
@@ -53,7 +55,10 @@ set_target_properties(memgraph PROPERTIES
|
|||||||
OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}"
|
OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}"
|
||||||
|
|
||||||
# Output the executable in main binary dir.
|
# Output the executable in main binary dir.
|
||||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
|
||||||
|
POSITION_INDEPENDENT_CODE ON
|
||||||
|
)
|
||||||
|
|
||||||
# Create symlink to the built executable.
|
# Create symlink to the built executable.
|
||||||
add_custom_command(TARGET memgraph POST_BUILD
|
add_custom_command(TARGET memgraph POST_BUILD
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <json/json.hpp>
|
#include <json/json.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "storage/v2/temporal.hpp"
|
#include "storage/v2/temporal.hpp"
|
||||||
#include "utils/logging.hpp"
|
#include "utils/logging.hpp"
|
||||||
@@ -87,8 +88,8 @@ inline nlohmann::json PropertyValueToJson(const storage::PropertyValue &pv) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::Log(const std::filesystem::path &storage_directory, int32_t buffer_size, int32_t buffer_flush_interval_millis)
|
Log::Log(std::filesystem::path storage_directory, int32_t buffer_size, int32_t buffer_flush_interval_millis)
|
||||||
: storage_directory_(storage_directory),
|
: storage_directory_(std::move(storage_directory)),
|
||||||
buffer_size_(buffer_size),
|
buffer_size_(buffer_size),
|
||||||
buffer_flush_interval_millis_(buffer_flush_interval_millis),
|
buffer_flush_interval_millis_(buffer_flush_interval_millis),
|
||||||
started_(false) {}
|
started_(false) {}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class Log {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Log(const std::filesystem::path &storage_directory, int32_t buffer_size, int32_t buffer_flush_interval_millis);
|
Log(std::filesystem::path storage_directory, int32_t buffer_size, int32_t buffer_flush_interval_millis);
|
||||||
|
|
||||||
~Log();
|
~Log();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
// 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
|
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||||
@@ -8,17 +8,15 @@
|
|||||||
|
|
||||||
#include "auth/auth.hpp"
|
#include "auth/auth.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <limits>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "auth/crypto.hpp"
|
||||||
#include "auth/exceptions.hpp"
|
#include "auth/exceptions.hpp"
|
||||||
#include "license/license.hpp"
|
#include "license/license.hpp"
|
||||||
#include "utils/flag_validation.hpp"
|
#include "utils/flag_validation.hpp"
|
||||||
#include "utils/logging.hpp"
|
|
||||||
#include "utils/message.hpp"
|
#include "utils/message.hpp"
|
||||||
#include "utils/settings.hpp"
|
#include "utils/settings.hpp"
|
||||||
#include "utils/string.hpp"
|
#include "utils/string.hpp"
|
||||||
@@ -46,6 +44,9 @@ namespace memgraph::auth {
|
|||||||
const std::string kUserPrefix = "user:";
|
const std::string kUserPrefix = "user:";
|
||||||
const std::string kRolePrefix = "role:";
|
const std::string kRolePrefix = "role:";
|
||||||
const std::string kLinkPrefix = "link:";
|
const std::string kLinkPrefix = "link:";
|
||||||
|
const std::string kVersion = "version";
|
||||||
|
|
||||||
|
static constexpr auto kVersionV1 = "V1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All data stored in the `Auth` storage is stored in an underlying
|
* All data stored in the `Auth` storage is stored in an underlying
|
||||||
@@ -64,7 +65,59 @@ const std::string kLinkPrefix = "link:";
|
|||||||
* key="link:<username>", value="<rolename>"
|
* key="link:<username>", value="<rolename>"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Auth::Auth(const std::string &storage_directory) : storage_(storage_directory), module_(FLAGS_auth_module_executable) {}
|
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_);
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) {
|
std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) {
|
||||||
if (module_.IsUsed()) {
|
if (module_.IsUsed()) {
|
||||||
@@ -113,7 +166,7 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user->UpdatePassword(password);
|
UpdatePassword(*user, password);
|
||||||
}
|
}
|
||||||
if (FLAGS_auth_module_manage_roles) {
|
if (FLAGS_auth_module_manage_roles) {
|
||||||
if (!rolename.empty()) {
|
if (!rolename.empty()) {
|
||||||
@@ -155,6 +208,10 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
|||||||
username, "https://memgr.ph/auth"));
|
username, "https://memgr.ph/auth"));
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
if (user->UpgradeHash(password)) {
|
||||||
|
SaveUser(*user);
|
||||||
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,13 +254,46 @@ void Auth::SaveUser(const User &user) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
std::optional<User> Auth::AddUser(const std::string &username, const std::optional<std::string> &password) {
|
||||||
|
if (!NameRegexMatch(username)) {
|
||||||
|
throw AuthException("Invalid user name.");
|
||||||
|
}
|
||||||
auto existing_user = GetUser(username);
|
auto existing_user = GetUser(username);
|
||||||
if (existing_user) return std::nullopt;
|
if (existing_user) return std::nullopt;
|
||||||
auto existing_role = GetRole(username);
|
auto existing_role = GetRole(username);
|
||||||
if (existing_role) return std::nullopt;
|
if (existing_role) return std::nullopt;
|
||||||
auto new_user = User(username);
|
auto new_user = User(username);
|
||||||
new_user.UpdatePassword(password);
|
UpdatePassword(new_user, password);
|
||||||
SaveUser(new_user);
|
SaveUser(new_user);
|
||||||
return new_user;
|
return new_user;
|
||||||
}
|
}
|
||||||
@@ -255,10 +345,11 @@ void Auth::SaveRole(const Role &role) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Role> Auth::AddRole(const std::string &rolename) {
|
std::optional<Role> Auth::AddRole(const std::string &rolename) {
|
||||||
auto existing_role = GetRole(rolename);
|
if (!NameRegexMatch(rolename)) {
|
||||||
if (existing_role) return std::nullopt;
|
throw AuthException("Invalid role name.");
|
||||||
auto existing_user = GetUser(rolename);
|
}
|
||||||
if (existing_user) return std::nullopt;
|
if (auto existing_role = GetRole(rolename)) return std::nullopt;
|
||||||
|
if (auto existing_user = GetUser(rolename)) return std::nullopt;
|
||||||
auto new_role = Role(rolename);
|
auto new_role = Role(rolename);
|
||||||
SaveRole(new_role);
|
SaveRole(new_role);
|
||||||
return new_role;
|
return new_role;
|
||||||
@@ -285,8 +376,7 @@ std::vector<auth::Role> Auth::AllRoles() const {
|
|||||||
for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix); ++it) {
|
for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix); ++it) {
|
||||||
auto rolename = it->first.substr(kRolePrefix.size());
|
auto rolename = it->first.substr(kRolePrefix.size());
|
||||||
if (rolename != utils::ToLowerCase(rolename)) continue;
|
if (rolename != utils::ToLowerCase(rolename)) continue;
|
||||||
auto role = GetRole(rolename);
|
if (auto role = GetRole(rolename)) {
|
||||||
if (role) {
|
|
||||||
ret.push_back(*role);
|
ret.push_back(*role);
|
||||||
} else {
|
} else {
|
||||||
throw AuthException("Couldn't load role '{}'!", rolename);
|
throw AuthException("Couldn't load role '{}'!", rolename);
|
||||||
@@ -296,15 +386,14 @@ std::vector<auth::Role> Auth::AllRoles() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig) const {
|
std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig) const {
|
||||||
auto rolename = utils::ToLowerCase(rolename_orig);
|
const auto rolename = utils::ToLowerCase(rolename_orig);
|
||||||
std::vector<auth::User> ret;
|
std::vector<auth::User> ret;
|
||||||
for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix); ++it) {
|
for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix); ++it) {
|
||||||
auto username = it->first.substr(kLinkPrefix.size());
|
auto username = it->first.substr(kLinkPrefix.size());
|
||||||
if (username != utils::ToLowerCase(username)) continue;
|
if (username != utils::ToLowerCase(username)) continue;
|
||||||
if (it->second != utils::ToLowerCase(it->second)) continue;
|
if (it->second != utils::ToLowerCase(it->second)) continue;
|
||||||
if (it->second == rolename) {
|
if (it->second == rolename) {
|
||||||
auto user = GetUser(username);
|
if (auto user = GetUser(username)) {
|
||||||
if (user) {
|
|
||||||
ret.push_back(std::move(*user));
|
ret.push_back(std::move(*user));
|
||||||
} else {
|
} else {
|
||||||
throw AuthException("Couldn't load user '{}'!", username);
|
throw AuthException("Couldn't load user '{}'!", username);
|
||||||
@@ -316,8 +405,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
|
|||||||
|
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
|
bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
|
||||||
auto user = GetUser(name);
|
if (auto user = GetUser(name)) {
|
||||||
if (user) {
|
|
||||||
if (db == kAllDatabases) {
|
if (db == kAllDatabases) {
|
||||||
user->db_access().GrantAll();
|
user->db_access().GrantAll();
|
||||||
} else {
|
} else {
|
||||||
@@ -330,8 +418,7 @@ bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name) {
|
bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name) {
|
||||||
auto user = GetUser(name);
|
if (auto user = GetUser(name)) {
|
||||||
if (user) {
|
|
||||||
if (db == kAllDatabases) {
|
if (db == kAllDatabases) {
|
||||||
user->db_access().DenyAll();
|
user->db_access().DenyAll();
|
||||||
} else {
|
} else {
|
||||||
@@ -346,17 +433,15 @@ bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name
|
|||||||
void Auth::DeleteDatabase(const std::string &db) {
|
void Auth::DeleteDatabase(const std::string &db) {
|
||||||
for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) {
|
for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) {
|
||||||
auto username = it->first.substr(kUserPrefix.size());
|
auto username = it->first.substr(kUserPrefix.size());
|
||||||
auto user = GetUser(username);
|
if (auto user = GetUser(username)) {
|
||||||
if (user) {
|
|
||||||
user->db_access().Delete(db);
|
user->db_access().Delete(db);
|
||||||
SaveUser(*user);
|
SaveUser(*user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Auth::SetMainDatabase(const std::string &db, const std::string &name) {
|
bool Auth::SetMainDatabase(std::string_view db, const std::string &name) {
|
||||||
auto user = GetUser(name);
|
if (auto user = GetUser(name)) {
|
||||||
if (user) {
|
|
||||||
if (!user->db_access().SetDefault(db)) {
|
if (!user->db_access().SetDefault(db)) {
|
||||||
throw AuthException("Couldn't set default database '{}' for user '{}'!", db, name);
|
throw AuthException("Couldn't set default database '{}' for user '{}'!", db, name);
|
||||||
}
|
}
|
||||||
@@ -367,4 +452,19 @@ bool Auth::SetMainDatabase(const std::string &db, const std::string &name) {
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
} // namespace memgraph::auth
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
// 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
|
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||||
@@ -10,11 +10,13 @@
|
|||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <regex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "auth/exceptions.hpp"
|
#include "auth/exceptions.hpp"
|
||||||
#include "auth/models.hpp"
|
#include "auth/models.hpp"
|
||||||
#include "auth/module.hpp"
|
#include "auth/module.hpp"
|
||||||
|
#include "glue/auth_global.hpp"
|
||||||
#include "kvstore/kvstore.hpp"
|
#include "kvstore/kvstore.hpp"
|
||||||
#include "utils/settings.hpp"
|
#include "utils/settings.hpp"
|
||||||
|
|
||||||
@@ -31,7 +33,40 @@ static const constexpr char *const kAllDatabases = "*";
|
|||||||
*/
|
*/
|
||||||
class Auth final {
|
class Auth final {
|
||||||
public:
|
public:
|
||||||
explicit Auth(const std::string &storage_directory);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates a user using his username and password.
|
* Authenticates a user using his username and password.
|
||||||
@@ -85,6 +120,14 @@ class Auth final {
|
|||||||
*/
|
*/
|
||||||
bool RemoveUser(const std::string &username);
|
bool RemoveUser(const std::string &username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* @param password
|
||||||
|
*/
|
||||||
|
void UpdatePassword(auth::User &user, const std::optional<std::string> &password);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all users from the storage.
|
* Gets all users from the storage.
|
||||||
*
|
*
|
||||||
@@ -195,14 +238,24 @@ class Auth final {
|
|||||||
* @return true on success
|
* @return true on success
|
||||||
* @throw AuthException if unable to find or update the user
|
* @throw AuthException if unable to find or update the user
|
||||||
*/
|
*/
|
||||||
bool SetMainDatabase(const std::string &db, const std::string &name);
|
bool SetMainDatabase(std::string_view db, const std::string &name);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
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,
|
// Even though the `kvstore::KVStore` class is guaranteed to be thread-safe,
|
||||||
// Auth is not thread-safe because modifying users and roles might require
|
// Auth is not thread-safe because modifying users and roles might require
|
||||||
// more than one operation on the storage.
|
// more than one operation on the storage.
|
||||||
kvstore::KVStore storage_;
|
kvstore::KVStore storage_;
|
||||||
auth::Module module_;
|
auth::Module module_;
|
||||||
|
Config config_;
|
||||||
};
|
};
|
||||||
} // namespace memgraph::auth
|
} // namespace memgraph::auth
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
// 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
|
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||||
@@ -22,10 +22,14 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
inline constexpr std::array password_encryption_mappings{
|
|
||||||
std::pair{"bcrypt"sv, memgraph::auth::PasswordEncryptionAlgorithm::BCRYPT},
|
constexpr auto kHashAlgo = "hash_algo";
|
||||||
std::pair{"sha256"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256},
|
constexpr auto kPasswordHash = "password_hash";
|
||||||
std::pair{"sha256-multiple"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256_MULTIPLE}};
|
|
||||||
|
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 uint64_t ONE_SHA_ITERATION = 1;
|
inline constexpr uint64_t ONE_SHA_ITERATION = 1;
|
||||||
inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024;
|
inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024;
|
||||||
@@ -35,7 +39,7 @@ inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024;
|
|||||||
DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
|
DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
|
||||||
"The password encryption algorithm used for authentication.", {
|
"The password encryption algorithm used for authentication.", {
|
||||||
if (const auto result =
|
if (const auto result =
|
||||||
memgraph::utils::IsValidEnumValueString(value, password_encryption_mappings);
|
memgraph::utils::IsValidEnumValueString(value, password_hash_mappings);
|
||||||
result.HasError()) {
|
result.HasError()) {
|
||||||
const auto error = result.GetError();
|
const auto error = result.GetError();
|
||||||
switch (error) {
|
switch (error) {
|
||||||
@@ -45,7 +49,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
|
|||||||
}
|
}
|
||||||
case memgraph::utils::ValidationError::InvalidValue: {
|
case memgraph::utils::ValidationError::InvalidValue: {
|
||||||
std::cout << "Invalid value for password encryption algorithm. Allowed values: "
|
std::cout << "Invalid value for password encryption algorithm. Allowed values: "
|
||||||
<< memgraph::utils::GetAllowedEnumValuesString(password_encryption_mappings)
|
<< memgraph::utils::GetAllowedEnumValuesString(password_hash_mappings)
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -58,7 +62,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt",
|
|||||||
|
|
||||||
namespace memgraph::auth {
|
namespace memgraph::auth {
|
||||||
namespace BCrypt {
|
namespace BCrypt {
|
||||||
std::string EncryptPassword(const std::string &password) {
|
std::string HashPassword(const std::string &password) {
|
||||||
char salt[BCRYPT_HASHSIZE];
|
char salt[BCRYPT_HASHSIZE];
|
||||||
char hash[BCRYPT_HASHSIZE];
|
char hash[BCRYPT_HASHSIZE];
|
||||||
|
|
||||||
@@ -86,16 +90,30 @@ bool VerifyPassword(const std::string &password, const std::string &hash) {
|
|||||||
} // namespace BCrypt
|
} // namespace BCrypt
|
||||||
|
|
||||||
namespace SHA {
|
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
|
#if OPENSSL_VERSION_MAJOR >= 3
|
||||||
std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t number_of_iterations) {
|
std::string HashPasswordOpenSSL3(std::string_view password, const uint64_t number_of_iterations,
|
||||||
|
std::string_view salt) {
|
||||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||||
|
|
||||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||||
EVP_MD *md = EVP_MD_fetch(nullptr, "SHA2-256", nullptr);
|
EVP_MD *md = EVP_MD_fetch(nullptr, "SHA2-256", nullptr);
|
||||||
|
|
||||||
EVP_DigestInit_ex(ctx, md, 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++) {
|
for (auto i = 0; i < number_of_iterations; i++) {
|
||||||
EVP_DigestUpdate(ctx, password.c_str(), password.size());
|
EVP_DigestUpdate(ctx, password.data(), password.size());
|
||||||
}
|
}
|
||||||
EVP_DigestFinal_ex(ctx, hash, nullptr);
|
EVP_DigestFinal_ex(ctx, hash, nullptr);
|
||||||
|
|
||||||
@@ -103,6 +121,11 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t
|
|||||||
EVP_MD_CTX_free(ctx);
|
EVP_MD_CTX_free(ctx);
|
||||||
|
|
||||||
std::stringstream result_stream;
|
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) {
|
for (auto hash_char : hash) {
|
||||||
result_stream << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
|
result_stream << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
|
||||||
}
|
}
|
||||||
@@ -110,17 +133,27 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t
|
|||||||
return result_stream.str();
|
return result_stream.str();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_t number_of_iterations) {
|
std::string HashPasswordOpenSSL1_1(std::string_view password, const uint64_t number_of_iterations,
|
||||||
|
std::string_view salt) {
|
||||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||||
|
|
||||||
SHA256_CTX sha256;
|
SHA256_CTX sha256;
|
||||||
SHA256_Init(&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++) {
|
for (auto i = 0; i < number_of_iterations; i++) {
|
||||||
SHA256_Update(&sha256, password.c_str(), password.size());
|
SHA256_Update(&sha256, password.data(), password.size());
|
||||||
}
|
}
|
||||||
SHA256_Final(hash, &sha256);
|
SHA256_Final(hash, &sha256);
|
||||||
|
|
||||||
std::stringstream ss;
|
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) {
|
for (auto hash_char : hash) {
|
||||||
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
|
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char;
|
||||||
}
|
}
|
||||||
@@ -129,55 +162,144 @@ std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string EncryptPassword(const std::string &password, const uint64_t number_of_iterations) {
|
std::string HashPassword(std::string_view password, const uint64_t number_of_iterations, std::string_view salt) {
|
||||||
#if OPENSSL_VERSION_MAJOR >= 3
|
#if OPENSSL_VERSION_MAJOR >= 3
|
||||||
return EncryptPasswordOpenSSL3(password, number_of_iterations);
|
return HashPasswordOpenSSL3(password, number_of_iterations, salt);
|
||||||
#else
|
#else
|
||||||
return EncryptPasswordOpenSSL1_1(password, number_of_iterations);
|
return HashPasswordOpenSSL1_1(password, number_of_iterations, salt);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VerifyPassword(const std::string &password, const std::string &hash, const uint64_t number_of_iterations) {
|
auto ExtractSalt(std::string_view salt_durable) -> std::array<char, SALT_SIZE> {
|
||||||
auto password_hash = EncryptPassword(password, number_of_iterations);
|
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()});
|
||||||
|
}
|
||||||
|
});
|
||||||
return password_hash == hash;
|
return password_hash == hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
} // namespace SHA
|
} // namespace SHA
|
||||||
|
|
||||||
bool VerifyPassword(const std::string &password, const std::string &hash) {
|
HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo) {
|
||||||
const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>(
|
auto const hash_algo = override_algo.value_or(CurrentHashAlgorithm());
|
||||||
FLAGS_password_encryption_algorithm, password_encryption_mappings);
|
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)};
|
||||||
|
};
|
||||||
|
|
||||||
if (!password_encryption_algorithm.has_value()) {
|
namespace {
|
||||||
throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
return *maybe_parsed;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string EncryptPassword(const std::string &password) {
|
PasswordHashAlgorithm &InternalCurrentHashAlgorithm() {
|
||||||
const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>(
|
static auto current = PasswordHashAlgorithm::BCRYPT;
|
||||||
FLAGS_password_encryption_algorithm, password_encryption_mappings);
|
static std::once_flag flag;
|
||||||
|
std::call_once(flag, [] { current = InternalParseHashAlgorithm(FLAGS_password_encryption_algorithm); });
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
if (!password_encryption_algorithm.has_value()) {
|
auto CurrentHashAlgorithm() -> PasswordHashAlgorithm { return InternalCurrentHashAlgorithm(); }
|
||||||
throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm);
|
|
||||||
|
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()) {
|
void to_json(nlohmann::json &j, const HashedPassword &p) {
|
||||||
case PasswordEncryptionAlgorithm::BCRYPT:
|
j = nlohmann::json{{kHashAlgo, p.hash_algo}, {kPasswordHash, p.password_hash}};
|
||||||
return BCrypt::EncryptPassword(password);
|
}
|
||||||
case PasswordEncryptionAlgorithm::SHA256:
|
|
||||||
return SHA::EncryptPassword(password, ONE_SHA_ITERATION);
|
void from_json(const nlohmann::json &j, HashedPassword &p) {
|
||||||
case PasswordEncryptionAlgorithm::SHA256_MULTIPLE:
|
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
|
||||||
return SHA::EncryptPassword(password, MULTIPLE_SHA_ITERATIONS);
|
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)};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HashedPassword::IsSalted() const {
|
||||||
|
switch (hash_algo) {
|
||||||
|
case PasswordHashAlgorithm::BCRYPT:
|
||||||
|
return true;
|
||||||
|
case PasswordHashAlgorithm::SHA256:
|
||||||
|
case PasswordHashAlgorithm::SHA256_MULTIPLE:
|
||||||
|
return SHA::IsSalted(password_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
// 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
|
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||||
@@ -8,14 +8,45 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <json/json.hpp>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace memgraph::auth {
|
namespace memgraph::auth {
|
||||||
enum class PasswordEncryptionAlgorithm : uint8_t { BCRYPT, SHA256, SHA256_MULTIPLE };
|
/// Need to be stable, auth durability depends on this
|
||||||
|
enum class PasswordHashAlgorithm : uint8_t { BCRYPT = 0, SHA256 = 1, SHA256_MULTIPLE = 2 };
|
||||||
|
|
||||||
/// @throw AuthException if unable to encrypt the password.
|
void SetHashAlgorithm(std::string_view algo);
|
||||||
std::string EncryptPassword(const std::string &password);
|
|
||||||
|
|
||||||
/// @throw AuthException if unable to verify the password.
|
auto CurrentHashAlgorithm() -> PasswordHashAlgorithm;
|
||||||
bool VerifyPassword(const std::string &password, const std::string &hash);
|
|
||||||
|
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 = {});
|
||||||
} // namespace memgraph::auth
|
} // namespace memgraph::auth
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
// 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
|
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "auth/models.hpp"
|
#include "auth/models.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <regex>
|
#include <utility>
|
||||||
|
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
@@ -20,22 +20,26 @@
|
|||||||
#include "query/constants.hpp"
|
#include "query/constants.hpp"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "utils/cast.hpp"
|
#include "utils/cast.hpp"
|
||||||
#include "utils/logging.hpp"
|
|
||||||
#include "utils/settings.hpp"
|
|
||||||
#include "utils/string.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 memgraph::auth {
|
||||||
namespace {
|
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.
|
// Constant list of all available permissions.
|
||||||
const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
|
const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
|
||||||
Permission::CREATE,
|
Permission::CREATE,
|
||||||
@@ -61,7 +65,8 @@ const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
|
|||||||
Permission::TRANSACTION_MANAGEMENT,
|
Permission::TRANSACTION_MANAGEMENT,
|
||||||
Permission::STORAGE_MODE,
|
Permission::STORAGE_MODE,
|
||||||
Permission::MULTI_DATABASE_EDIT,
|
Permission::MULTI_DATABASE_EDIT,
|
||||||
Permission::MULTI_DATABASE_USE};
|
Permission::MULTI_DATABASE_USE,
|
||||||
|
Permission::COORDINATOR};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -117,6 +122,8 @@ std::string PermissionToString(Permission permission) {
|
|||||||
return "MULTI_DATABASE_EDIT";
|
return "MULTI_DATABASE_EDIT";
|
||||||
case Permission::MULTI_DATABASE_USE:
|
case Permission::MULTI_DATABASE_USE:
|
||||||
return "MULTI_DATABASE_USE";
|
return "MULTI_DATABASE_USE";
|
||||||
|
case Permission::COORDINATOR:
|
||||||
|
return "COORDINATOR";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,8 +248,9 @@ std::vector<Permission> Permissions::GetDenies() const {
|
|||||||
|
|
||||||
nlohmann::json Permissions::Serialize() const {
|
nlohmann::json Permissions::Serialize() const {
|
||||||
nlohmann::json data = nlohmann::json::object();
|
nlohmann::json data = nlohmann::json::object();
|
||||||
data["grants"] = grants_;
|
|
||||||
data["denies"] = denies_;
|
data[kGrants] = grants_;
|
||||||
|
data[kDenies] = denies_;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,10 +258,10 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
|
|||||||
if (!data.is_object()) {
|
if (!data.is_object()) {
|
||||||
throw AuthException("Couldn't load permissions data!");
|
throw AuthException("Couldn't load permissions data!");
|
||||||
}
|
}
|
||||||
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
|
if (!data[kGrants].is_number_unsigned() || !data[kDenies].is_number_unsigned()) {
|
||||||
throw AuthException("Couldn't load permissions data!");
|
throw AuthException("Couldn't load permissions data!");
|
||||||
}
|
}
|
||||||
return Permissions{data["grants"], data["denies"]};
|
return Permissions{data[kGrants], data[kDenies]};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Permissions::grants() const { return grants_; }
|
uint64_t Permissions::grants() const { return grants_; }
|
||||||
@@ -315,8 +323,8 @@ nlohmann::json FineGrainedAccessPermissions::Serialize() const {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
nlohmann::json data = nlohmann::json::object();
|
nlohmann::json data = nlohmann::json::object();
|
||||||
data["permissions"] = permissions_;
|
data[kPermissions] = permissions_;
|
||||||
data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
|
data[kGlobalPermission] = global_permission_.has_value() ? global_permission_.value() : -1;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,13 +337,13 @@ FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlo
|
|||||||
}
|
}
|
||||||
std::optional<uint64_t> global_permission;
|
std::optional<uint64_t> global_permission;
|
||||||
|
|
||||||
if (data["global_permission"].empty() || data["global_permission"] == -1) {
|
if (data[kGlobalPermission].empty() || data[kGlobalPermission] == -1) {
|
||||||
global_permission = std::nullopt;
|
global_permission = std::nullopt;
|
||||||
} else {
|
} else {
|
||||||
global_permission = data["global_permission"];
|
global_permission = data[kGlobalPermission];
|
||||||
}
|
}
|
||||||
|
|
||||||
return FineGrainedAccessPermissions(data["permissions"], global_permission);
|
return FineGrainedAccessPermissions(data[kPermissions], global_permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
|
const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
|
||||||
@@ -441,13 +449,13 @@ const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermission
|
|||||||
|
|
||||||
nlohmann::json Role::Serialize() const {
|
nlohmann::json Role::Serialize() const {
|
||||||
nlohmann::json data = nlohmann::json::object();
|
nlohmann::json data = nlohmann::json::object();
|
||||||
data["rolename"] = rolename_;
|
data[kRoleName] = rolename_;
|
||||||
data["permissions"] = permissions_.Serialize();
|
data[kPermissions] = permissions_.Serialize();
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize();
|
||||||
} else {
|
} else {
|
||||||
data["fine_grained_access_handler"] = {};
|
data[kFineGrainedAccessHandler] = {};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return data;
|
return data;
|
||||||
@@ -457,21 +465,21 @@ Role Role::Deserialize(const nlohmann::json &data) {
|
|||||||
if (!data.is_object()) {
|
if (!data.is_object()) {
|
||||||
throw AuthException("Couldn't load role data!");
|
throw AuthException("Couldn't load role data!");
|
||||||
}
|
}
|
||||||
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
|
if (!data[kRoleName].is_string() || !data[kPermissions].is_object()) {
|
||||||
throw AuthException("Couldn't load role data!");
|
throw AuthException("Couldn't load role data!");
|
||||||
}
|
}
|
||||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
auto permissions = Permissions::Deserialize(data[kPermissions]);
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||||
FineGrainedAccessHandler fine_grained_access_handler;
|
FineGrainedAccessHandler fine_grained_access_handler;
|
||||||
// We can have an empty fine_grained if the user was created without a valid license
|
// We can have an empty fine_grained if the user was created without a valid license
|
||||||
if (data["fine_grained_access_handler"].is_object()) {
|
if (data[kFineGrainedAccessHandler].is_object()) {
|
||||||
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]);
|
||||||
}
|
}
|
||||||
return {data["rolename"], permissions, std::move(fine_grained_access_handler)};
|
return {data[kRoleName], permissions, std::move(fine_grained_access_handler)};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return {data["rolename"], permissions};
|
return {data[kRoleName], permissions};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const Role &first, const Role &second) {
|
bool operator==(const Role &first, const Role &second) {
|
||||||
@@ -485,13 +493,13 @@ bool operator==(const Role &first, const Role &second) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
void Databases::Add(const std::string &db) {
|
void Databases::Add(std::string_view db) {
|
||||||
if (allow_all_) {
|
if (allow_all_) {
|
||||||
grants_dbs_.clear();
|
grants_dbs_.clear();
|
||||||
allow_all_ = false;
|
allow_all_ = false;
|
||||||
}
|
}
|
||||||
grants_dbs_.emplace(db);
|
grants_dbs_.emplace(db);
|
||||||
denies_dbs_.erase(db);
|
denies_dbs_.erase(std::string{db}); // TODO: C++23 use transparent key compare
|
||||||
}
|
}
|
||||||
|
|
||||||
void Databases::Remove(const std::string &db) {
|
void Databases::Remove(const std::string &db) {
|
||||||
@@ -522,13 +530,13 @@ void Databases::DenyAll() {
|
|||||||
denies_dbs_.clear();
|
denies_dbs_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Databases::SetDefault(const std::string &db) {
|
bool Databases::SetDefault(std::string_view db) {
|
||||||
if (!Contains(db)) return false;
|
if (!Contains(db)) return false;
|
||||||
default_db_ = db;
|
default_db_ = db;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool Databases::Contains(const std::string &db) const {
|
[[nodiscard]] bool Databases::Contains(std::string_view db) const {
|
||||||
return !denies_dbs_.contains(db) && (allow_all_ || grants_dbs_.contains(db));
|
return !denies_dbs_.contains(db) && (allow_all_ || grants_dbs_.contains(db));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,10 +549,10 @@ const std::string &Databases::GetDefault() const {
|
|||||||
|
|
||||||
nlohmann::json Databases::Serialize() const {
|
nlohmann::json Databases::Serialize() const {
|
||||||
nlohmann::json data = nlohmann::json::object();
|
nlohmann::json data = nlohmann::json::object();
|
||||||
data["grants"] = grants_dbs_;
|
data[kGrants] = grants_dbs_;
|
||||||
data["denies"] = denies_dbs_;
|
data[kDenies] = denies_dbs_;
|
||||||
data["allow_all"] = allow_all_;
|
data[kAllowAll] = allow_all_;
|
||||||
data["default"] = default_db_;
|
data[kDefault] = default_db_;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,63 +560,41 @@ Databases Databases::Deserialize(const nlohmann::json &data) {
|
|||||||
if (!data.is_object()) {
|
if (!data.is_object()) {
|
||||||
throw AuthException("Couldn't load database data!");
|
throw AuthException("Couldn't load database data!");
|
||||||
}
|
}
|
||||||
if (!data["grants"].is_structured() || !data["denies"].is_structured() || !data["allow_all"].is_boolean() ||
|
if (!data[kGrants].is_structured() || !data[kDenies].is_structured() || !data[kAllowAll].is_boolean() ||
|
||||||
!data["default"].is_string()) {
|
!data[kDefault].is_string()) {
|
||||||
throw AuthException("Couldn't load database data!");
|
throw AuthException("Couldn't load database data!");
|
||||||
}
|
}
|
||||||
return {data["allow_all"], data["grants"], data["denies"], data["default"]};
|
return {data[kAllowAll], data[kGrants], data[kDenies], data[kDefault]};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
User::User() {}
|
User::User() = default;
|
||||||
|
|
||||||
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
|
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
|
||||||
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
|
User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions)
|
||||||
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
|
: username_(utils::ToLowerCase(username)), password_hash_(std::move(password_hash)), permissions_(permissions) {}
|
||||||
|
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
|
User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions,
|
||||||
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access)
|
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access)
|
||||||
: username_(utils::ToLowerCase(username)),
|
: username_(utils::ToLowerCase(username)),
|
||||||
password_hash_(password_hash),
|
password_hash_(std::move(password_hash)),
|
||||||
permissions_(permissions),
|
permissions_(permissions),
|
||||||
fine_grained_access_handler_(std::move(fine_grained_access_handler)),
|
fine_grained_access_handler_(std::move(fine_grained_access_handler)),
|
||||||
database_access_(db_access) {}
|
database_access_(std::move(db_access)) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool User::CheckPassword(const std::string &password) {
|
bool User::CheckPassword(const std::string &password) {
|
||||||
if (password_hash_.empty()) return true;
|
return password_hash_ ? password_hash_->VerifyPassword(password) : true;
|
||||||
return VerifyPassword(password, password_hash_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void User::UpdatePassword(const std::optional<std::string> &password) {
|
void User::UpdatePassword(const std::optional<std::string> &password,
|
||||||
|
std::optional<PasswordHashAlgorithm> algo_override) {
|
||||||
if (!password) {
|
if (!password) {
|
||||||
if (!FLAGS_auth_password_permit_null) {
|
password_hash_.reset();
|
||||||
throw AuthException("Null passwords aren't permitted!");
|
|
||||||
}
|
|
||||||
password_hash_ = "";
|
|
||||||
return;
|
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); }
|
void User::SetRole(const Role &role) { role_.emplace(role); }
|
||||||
@@ -667,16 +653,20 @@ const Role *User::role() const {
|
|||||||
|
|
||||||
nlohmann::json User::Serialize() const {
|
nlohmann::json User::Serialize() const {
|
||||||
nlohmann::json data = nlohmann::json::object();
|
nlohmann::json data = nlohmann::json::object();
|
||||||
data["username"] = username_;
|
data[kUsername] = username_;
|
||||||
data["password_hash"] = password_hash_;
|
if (password_hash_.has_value()) {
|
||||||
data["permissions"] = permissions_.Serialize();
|
data[kPasswordHash] = *password_hash_;
|
||||||
|
} else {
|
||||||
|
data[kPasswordHash] = nullptr;
|
||||||
|
}
|
||||||
|
data[kPermissions] = permissions_.Serialize();
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize();
|
||||||
data["databases"] = database_access_.Serialize();
|
data[kDatabases] = database_access_.Serialize();
|
||||||
} else {
|
} else {
|
||||||
data["fine_grained_access_handler"] = {};
|
data[kFineGrainedAccessHandler] = {};
|
||||||
data["databases"] = {};
|
data[kDatabases] = {};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// The role shouldn't be serialized here, it is stored as a foreign key.
|
// The role shouldn't be serialized here, it is stored as a foreign key.
|
||||||
@@ -687,15 +677,23 @@ User User::Deserialize(const nlohmann::json &data) {
|
|||||||
if (!data.is_object()) {
|
if (!data.is_object()) {
|
||||||
throw AuthException("Couldn't load user data!");
|
throw AuthException("Couldn't load user data!");
|
||||||
}
|
}
|
||||||
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) {
|
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()) {
|
||||||
throw AuthException("Couldn't load user data!");
|
throw AuthException("Couldn't load user data!");
|
||||||
}
|
}
|
||||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
|
||||||
|
std::optional<HashedPassword> password_hash{};
|
||||||
|
if (password_hash_json.is_object()) {
|
||||||
|
password_hash = password_hash_json.get<HashedPassword>();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto permissions = Permissions::Deserialize(data[kPermissions]);
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||||
Databases db_access;
|
Databases db_access;
|
||||||
if (data["databases"].is_structured()) {
|
if (data[kDatabases].is_structured()) {
|
||||||
db_access = Databases::Deserialize(data["databases"]);
|
db_access = Databases::Deserialize(data[kDatabases]);
|
||||||
} else {
|
} else {
|
||||||
// Back-compatibility
|
// Back-compatibility
|
||||||
spdlog::warn("User without specified database access. Given access to the default database.");
|
spdlog::warn("User without specified database access. Given access to the default database.");
|
||||||
@@ -704,13 +702,13 @@ User User::Deserialize(const nlohmann::json &data) {
|
|||||||
}
|
}
|
||||||
FineGrainedAccessHandler fine_grained_access_handler;
|
FineGrainedAccessHandler fine_grained_access_handler;
|
||||||
// We can have an empty fine_grained if the user was created without a valid license
|
// We can have an empty fine_grained if the user was created without a valid license
|
||||||
if (data["fine_grained_access_handler"].is_object()) {
|
if (data[kFineGrainedAccessHandler].is_object()) {
|
||||||
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]);
|
||||||
}
|
}
|
||||||
return {data["username"], data["password_hash"], permissions, std::move(fine_grained_access_handler), db_access};
|
return {data[kUsername], std::move(password_hash), permissions, std::move(fine_grained_access_handler), db_access};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return {data["username"], data["password_hash"], permissions};
|
return {data[kUsername], std::move(password_hash), permissions};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const User &first, const User &second) {
|
bool operator==(const User &first, const User &second) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
// 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
|
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <json/json.hpp>
|
#include <json/json.hpp>
|
||||||
|
#include <utility>
|
||||||
|
#include "crypto.hpp"
|
||||||
#include "dbms/constants.hpp"
|
#include "dbms/constants.hpp"
|
||||||
#include "utils/logging.hpp"
|
#include "utils/logging.hpp"
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ enum class Permission : uint64_t {
|
|||||||
STORAGE_MODE = 1U << 22U,
|
STORAGE_MODE = 1U << 22U,
|
||||||
MULTI_DATABASE_EDIT = 1U << 23U,
|
MULTI_DATABASE_EDIT = 1U << 23U,
|
||||||
MULTI_DATABASE_USE = 1U << 24U,
|
MULTI_DATABASE_USE = 1U << 24U,
|
||||||
|
COORDINATOR = 1U << 25U,
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@@ -245,7 +248,7 @@ bool operator==(const Role &first, const Role &second);
|
|||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
class Databases final {
|
class Databases final {
|
||||||
public:
|
public:
|
||||||
Databases() : grants_dbs_({dbms::kDefaultDB}), allow_all_(false), default_db_(dbms::kDefaultDB) {}
|
Databases() : grants_dbs_{std::string{dbms::kDefaultDB}}, allow_all_(false), default_db_(dbms::kDefaultDB) {}
|
||||||
|
|
||||||
Databases(const Databases &) = default;
|
Databases(const Databases &) = default;
|
||||||
Databases &operator=(const Databases &) = default;
|
Databases &operator=(const Databases &) = default;
|
||||||
@@ -258,7 +261,7 @@ class Databases final {
|
|||||||
*
|
*
|
||||||
* @param db name of the database to grant access to
|
* @param db name of the database to grant access to
|
||||||
*/
|
*/
|
||||||
void Add(const std::string &db);
|
void Add(std::string_view db);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Remove database to the list of granted access.
|
* @brief Remove database to the list of granted access.
|
||||||
@@ -290,7 +293,7 @@ class Databases final {
|
|||||||
/**
|
/**
|
||||||
* @brief Set the default database.
|
* @brief Set the default database.
|
||||||
*/
|
*/
|
||||||
bool SetDefault(const std::string &db);
|
bool SetDefault(std::string_view db);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Checks if access is grated to the database.
|
* @brief Checks if access is grated to the database.
|
||||||
@@ -298,11 +301,11 @@ class Databases final {
|
|||||||
* @param db name of the database
|
* @param db name of the database
|
||||||
* @return true if allow_all and not denied or granted
|
* @return true if allow_all and not denied or granted
|
||||||
*/
|
*/
|
||||||
bool Contains(const std::string &db) const;
|
bool Contains(std::string_view db) const;
|
||||||
|
|
||||||
bool GetAllowAll() const { return allow_all_; }
|
bool GetAllowAll() const { return allow_all_; }
|
||||||
const std::set<std::string> &GetGrants() const { return grants_dbs_; }
|
const std::set<std::string, std::less<>> &GetGrants() const { return grants_dbs_; }
|
||||||
const std::set<std::string> &GetDenies() const { return denies_dbs_; }
|
const std::set<std::string, std::less<>> &GetDenies() const { return denies_dbs_; }
|
||||||
const std::string &GetDefault() const;
|
const std::string &GetDefault() const;
|
||||||
|
|
||||||
nlohmann::json Serialize() const;
|
nlohmann::json Serialize() const;
|
||||||
@@ -310,14 +313,17 @@ class Databases final {
|
|||||||
static Databases Deserialize(const nlohmann::json &data);
|
static Databases Deserialize(const nlohmann::json &data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Databases(bool allow_all, std::set<std::string> grant, std::set<std::string> deny,
|
Databases(bool allow_all, std::set<std::string, std::less<>> grant, std::set<std::string, std::less<>> deny,
|
||||||
const std::string &default_db = dbms::kDefaultDB)
|
std::string default_db = std::string{dbms::kDefaultDB})
|
||||||
: grants_dbs_(grant), denies_dbs_(deny), allow_all_(allow_all), default_db_(default_db) {}
|
: grants_dbs_(std::move(grant)),
|
||||||
|
denies_dbs_(std::move(deny)),
|
||||||
|
allow_all_(allow_all),
|
||||||
|
default_db_(std::move(default_db)) {}
|
||||||
|
|
||||||
std::set<std::string> grants_dbs_; //!< set of databases with granted access
|
std::set<std::string, std::less<>> grants_dbs_; //!< set of databases with granted access
|
||||||
std::set<std::string> denies_dbs_; //!< set of databases with denied access
|
std::set<std::string, std::less<>> denies_dbs_; //!< set of databases with denied access
|
||||||
bool allow_all_; //!< flag to allow access to everything (denied overrides this)
|
bool allow_all_; //!< flag to allow access to everything (denied overrides this)
|
||||||
std::string default_db_; //!< user's default database
|
std::string default_db_; //!< user's default database
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -327,9 +333,9 @@ class User final {
|
|||||||
User();
|
User();
|
||||||
|
|
||||||
explicit User(const std::string &username);
|
explicit User(const std::string &username);
|
||||||
User(const std::string &username, const std::string &password_hash, const Permissions &permissions);
|
User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions);
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
|
User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions,
|
||||||
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access = {});
|
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access = {});
|
||||||
#endif
|
#endif
|
||||||
User(const User &) = default;
|
User(const User &) = default;
|
||||||
@@ -341,8 +347,18 @@ class User final {
|
|||||||
/// @throw AuthException if unable to verify the password.
|
/// @throw AuthException if unable to verify the password.
|
||||||
bool CheckPassword(const std::string &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.
|
/// @throw AuthException if unable to set the password.
|
||||||
void UpdatePassword(const std::optional<std::string> &password = std::nullopt);
|
void UpdatePassword(const std::optional<std::string> &password = {},
|
||||||
|
std::optional<PasswordHashAlgorithm> algo_override = std::nullopt);
|
||||||
|
|
||||||
void SetRole(const Role &role);
|
void SetRole(const Role &role);
|
||||||
|
|
||||||
@@ -377,7 +393,7 @@ class User final {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::string username_;
|
std::string username_;
|
||||||
std::string password_hash_;
|
std::optional<HashedPassword> password_hash_;
|
||||||
Permissions permissions_;
|
Permissions permissions_;
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
FineGrainedAccessHandler fine_grained_access_handler_;
|
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "communication/bolt/v1/codes.hpp"
|
#include "communication/bolt/v1/codes.hpp"
|
||||||
@@ -34,8 +35,8 @@ class FailureResponseException : public utils::BasicException {
|
|||||||
|
|
||||||
explicit FailureResponseException(const std::string &message) : utils::BasicException{message} {}
|
explicit FailureResponseException(const std::string &message) : utils::BasicException{message} {}
|
||||||
|
|
||||||
FailureResponseException(const std::string &code, const std::string &message)
|
FailureResponseException(std::string code, const std::string &message)
|
||||||
: utils::BasicException{message}, code_{code} {}
|
: utils::BasicException{message}, code_{std::move(code)} {}
|
||||||
|
|
||||||
const std::string &code() const { return code_; }
|
const std::string &code() const { return code_; }
|
||||||
SPECIALIZE_GET_EXCEPTION_NAME(FailureResponseException)
|
SPECIALIZE_GET_EXCEPTION_NAME(FailureResponseException)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -51,7 +51,7 @@ enum class ChunkState : uint8_t {
|
|||||||
template <typename TBuffer>
|
template <typename TBuffer>
|
||||||
class ChunkedDecoderBuffer {
|
class ChunkedDecoderBuffer {
|
||||||
public:
|
public:
|
||||||
ChunkedDecoderBuffer(TBuffer &buffer) : buffer_(buffer) { data_.reserve(kChunkMaxDataSize); }
|
explicit ChunkedDecoderBuffer(TBuffer &buffer) : buffer_(buffer) { data_.reserve(kChunkMaxDataSize); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads data from the internal buffer.
|
* Reads data from the internal buffer.
|
||||||
|
|||||||
@@ -401,11 +401,11 @@ class Decoder {
|
|||||||
}
|
}
|
||||||
auto &labels = dv.ValueList();
|
auto &labels = dv.ValueList();
|
||||||
vertex.labels.reserve(labels.size());
|
vertex.labels.reserve(labels.size());
|
||||||
for (size_t i = 0; i < labels.size(); ++i) {
|
for (auto &label : labels) {
|
||||||
if (labels[i].type() != Value::Type::String) {
|
if (label.type() != Value::Type::String) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
vertex.labels.emplace_back(std::move(labels[i].ValueString()));
|
vertex.labels.emplace_back(std::move(label.ValueString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// read properties
|
// read properties
|
||||||
|
|||||||
@@ -111,12 +111,12 @@ class BaseEncoder {
|
|||||||
|
|
||||||
void WriteList(const std::vector<Value> &value) {
|
void WriteList(const std::vector<Value> &value) {
|
||||||
WriteTypeSize(value.size(), MarkerList);
|
WriteTypeSize(value.size(), MarkerList);
|
||||||
for (auto &x : value) WriteValue(x);
|
for (const auto &x : value) WriteValue(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteMap(const std::map<std::string, Value> &value) {
|
void WriteMap(const std::map<std::string, Value> &value) {
|
||||||
WriteTypeSize(value.size(), MarkerMap);
|
WriteTypeSize(value.size(), MarkerMap);
|
||||||
for (auto &x : value) {
|
for (const auto &x : value) {
|
||||||
WriteString(x.first);
|
WriteString(x.first);
|
||||||
WriteValue(x.second);
|
WriteValue(x.second);
|
||||||
}
|
}
|
||||||
@@ -205,11 +205,11 @@ class BaseEncoder {
|
|||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + 3);
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + 3);
|
||||||
WriteRAW(utils::UnderlyingCast(Signature::Path));
|
WriteRAW(utils::UnderlyingCast(Signature::Path));
|
||||||
WriteTypeSize(path.vertices.size(), MarkerList);
|
WriteTypeSize(path.vertices.size(), MarkerList);
|
||||||
for (auto &v : path.vertices) WriteVertex(v);
|
for (const auto &v : path.vertices) WriteVertex(v);
|
||||||
WriteTypeSize(path.edges.size(), MarkerList);
|
WriteTypeSize(path.edges.size(), MarkerList);
|
||||||
for (auto &e : path.edges) WriteEdge(e);
|
for (const auto &e : path.edges) WriteEdge(e);
|
||||||
WriteTypeSize(path.indices.size(), MarkerList);
|
WriteTypeSize(path.indices.size(), MarkerList);
|
||||||
for (auto &i : path.indices) WriteInt(i);
|
for (const auto &i : path.indices) WriteInt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteDate(const utils::Date &date) {
|
void WriteDate(const utils::Date &date) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -48,7 +48,7 @@ namespace memgraph::communication::bolt {
|
|||||||
template <class TOutputStream>
|
template <class TOutputStream>
|
||||||
class ChunkedEncoderBuffer {
|
class ChunkedEncoderBuffer {
|
||||||
public:
|
public:
|
||||||
ChunkedEncoderBuffer(TOutputStream &output_stream) : output_stream_(output_stream) {}
|
explicit ChunkedEncoderBuffer(TOutputStream &output_stream) : output_stream_(output_stream) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes n values into the buffer. If n is bigger than whole chunk size
|
* Writes n values into the buffer. If n is bigger than whole chunk size
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
|||||||
using BaseEncoder<Buffer>::buffer_;
|
using BaseEncoder<Buffer>::buffer_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ClientEncoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
explicit ClientEncoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
||||||
|
|
||||||
using BaseEncoder<Buffer>::UpdateVersion;
|
using BaseEncoder<Buffer>::UpdateVersion;
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class Encoder : private BaseEncoder<Buffer> {
|
|||||||
using BaseEncoder<Buffer>::buffer_;
|
using BaseEncoder<Buffer>::buffer_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Encoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
explicit Encoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
|
||||||
|
|
||||||
using BaseEncoder<Buffer>::UpdateVersion;
|
using BaseEncoder<Buffer>::UpdateVersion;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -93,7 +93,7 @@ State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<i
|
|||||||
return State::Close;
|
return State::Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (summary.count("has_more") && summary.at("has_more").ValueBool()) {
|
if (summary.contains("has_more") && summary.at("has_more").ValueBool()) {
|
||||||
return State::Result;
|
return State::Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +148,13 @@ State HandlePullDiscardV4(TSession &session, const State state, const Marker mar
|
|||||||
spdlog::trace("Couldn't read extra field!");
|
spdlog::trace("Couldn't read extra field!");
|
||||||
}
|
}
|
||||||
const auto &extra_map = extra.ValueMap();
|
const auto &extra_map = extra.ValueMap();
|
||||||
if (extra_map.count("n")) {
|
if (extra_map.contains("n")) {
|
||||||
if (const auto n_value = extra_map.at("n").ValueInt(); n_value != kPullAll) {
|
if (const auto n_value = extra_map.at("n").ValueInt(); n_value != kPullAll) {
|
||||||
n = n_value;
|
n = n_value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extra_map.count("qid")) {
|
if (extra_map.contains("qid")) {
|
||||||
if (const auto qid_value = extra_map.at("qid").ValueInt(); qid_value != kPullLast) {
|
if (const auto qid_value = extra_map.at("qid").ValueInt(); qid_value != kPullLast) {
|
||||||
qid = qid_value;
|
qid = qid_value;
|
||||||
}
|
}
|
||||||
@@ -170,6 +170,7 @@ inline State HandleFailure(TSession &session, const std::exception &e) {
|
|||||||
spdlog::trace("Error trace: {}", p->trace());
|
spdlog::trace("Error trace: {}", p->trace());
|
||||||
}
|
}
|
||||||
session.encoder_buffer_.Clear();
|
session.encoder_buffer_.Clear();
|
||||||
|
|
||||||
auto code_message = ExceptionToErrorMessage(e);
|
auto code_message = ExceptionToErrorMessage(e);
|
||||||
bool fail_sent = session.encoder_.MessageFailure({{"code", code_message.first}, {"message", code_message.second}});
|
bool fail_sent = session.encoder_.MessageFailure({{"code", code_message.first}, {"message", code_message.second}});
|
||||||
if (!fail_sent) {
|
if (!fail_sent) {
|
||||||
@@ -367,14 +368,16 @@ State HandleReset(TSession &session, const Marker marker) {
|
|||||||
return State::Close;
|
return State::Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.encoder_.MessageSuccess()) {
|
try {
|
||||||
spdlog::trace("Couldn't send success message!");
|
session.Abort();
|
||||||
return State::Close;
|
if (!session.encoder_.MessageSuccess({})) {
|
||||||
|
spdlog::trace("Couldn't send success message!");
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
return State::Idle;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
return HandleFailure(session, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.Abort();
|
|
||||||
|
|
||||||
return State::Idle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
@@ -397,19 +400,17 @@ State HandleBegin(TSession &session, const State state, const Marker marker) {
|
|||||||
|
|
||||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||||
|
|
||||||
if (!session.encoder_.MessageSuccess({})) {
|
|
||||||
spdlog::trace("Couldn't send success message!");
|
|
||||||
return State::Close;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.Configure(extra.ValueMap());
|
session.Configure(extra.ValueMap());
|
||||||
session.BeginTransaction(extra.ValueMap());
|
session.BeginTransaction(extra.ValueMap());
|
||||||
|
if (!session.encoder_.MessageSuccess({})) {
|
||||||
|
spdlog::trace("Couldn't send success message!");
|
||||||
|
return State::Close;
|
||||||
|
}
|
||||||
|
return State::Idle;
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
return HandleFailure(session, e);
|
return HandleFailure(session, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return State::Idle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
@@ -427,11 +428,11 @@ State HandleCommit(TSession &session, const State state, const Marker marker) {
|
|||||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
session.CommitTransaction();
|
||||||
if (!session.encoder_.MessageSuccess({})) {
|
if (!session.encoder_.MessageSuccess({})) {
|
||||||
spdlog::trace("Couldn't send success message!");
|
spdlog::trace("Couldn't send success message!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
}
|
}
|
||||||
session.CommitTransaction();
|
|
||||||
return State::Idle;
|
return State::Idle;
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
return HandleFailure(session, e);
|
return HandleFailure(session, e);
|
||||||
@@ -453,11 +454,11 @@ State HandleRollback(TSession &session, const State state, const Marker marker)
|
|||||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
session.RollbackTransaction();
|
||||||
if (!session.encoder_.MessageSuccess({})) {
|
if (!session.encoder_.MessageSuccess({})) {
|
||||||
spdlog::trace("Couldn't send success message!");
|
spdlog::trace("Couldn't send success message!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
}
|
}
|
||||||
session.RollbackTransaction();
|
|
||||||
return State::Idle;
|
return State::Idle;
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
return HandleFailure(session, e);
|
return HandleFailure(session, e);
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ std::optional<State> AuthenticateUser(TSession &session, Value &metadata) {
|
|||||||
std::string username;
|
std::string username;
|
||||||
std::string password;
|
std::string password;
|
||||||
if (data["scheme"].ValueString() == "basic") {
|
if (data["scheme"].ValueString() == "basic") {
|
||||||
if (!data.count("principal")) { // Special case principal = ""
|
if (!data.contains("principal")) { // Special case principal = ""
|
||||||
spdlog::warn("The client didn't supply the principal field! Trying with \"\"...");
|
spdlog::warn("The client didn't supply the principal field! Trying with \"\"...");
|
||||||
data["principal"] = "";
|
data["principal"] = "";
|
||||||
}
|
}
|
||||||
if (!data.count("credentials")) { // Special case credentials = ""
|
if (!data.contains("credentials")) { // Special case credentials = ""
|
||||||
spdlog::warn("The client didn't supply the credentials field! Trying with \"\"...");
|
spdlog::warn("The client didn't supply the credentials field! Trying with \"\"...");
|
||||||
data["credentials"] = "";
|
data["credentials"] = "";
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ std::optional<Value> GetMetadataV4(TSession &session, const Marker marker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto &data = metadata.ValueMap();
|
auto &data = metadata.ValueMap();
|
||||||
if (!data.count("user_agent")) {
|
if (!data.contains("user_agent")) {
|
||||||
spdlog::warn("The client didn't supply the user agent!");
|
spdlog::warn("The client didn't supply the user agent!");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ std::optional<Value> GetInitDataV5(TSession &session, const Marker marker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto &data = metadata.ValueMap();
|
const auto &data = metadata.ValueMap();
|
||||||
if (!data.count("user_agent")) {
|
if (!data.contains("user_agent")) {
|
||||||
spdlog::warn("The client didn't supply the user agent!");
|
spdlog::warn("The client didn't supply the user agent!");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ struct UnboundedEdge {
|
|||||||
* The decoder writes data into this structure.
|
* The decoder writes data into this structure.
|
||||||
*/
|
*/
|
||||||
struct Path {
|
struct Path {
|
||||||
Path() {}
|
Path() = default;
|
||||||
|
|
||||||
Path(const std::vector<Vertex> &vertices, const std::vector<Edge> &edges) {
|
Path(const std::vector<Vertex> &vertices, const std::vector<Edge> &edges) {
|
||||||
// Helper function. Looks for the given element in the collection. If found,
|
// Helper function. Looks for the given element in the collection. If found,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "communication/client.hpp"
|
#include "communication/client.hpp"
|
||||||
|
|
||||||
#include "communication/helpers.hpp"
|
#include "communication/helpers.hpp"
|
||||||
|
#include "io/network/network_error.hpp"
|
||||||
#include "utils/logging.hpp"
|
#include "utils/logging.hpp"
|
||||||
|
|
||||||
namespace memgraph::communication {
|
namespace memgraph::communication {
|
||||||
@@ -25,7 +26,9 @@ Client::~Client() {
|
|||||||
|
|
||||||
bool Client::Connect(const io::network::Endpoint &endpoint) {
|
bool Client::Connect(const io::network::Endpoint &endpoint) {
|
||||||
// Try to establish a socket connection.
|
// Try to establish a socket connection.
|
||||||
if (!socket_.Connect(endpoint)) return false;
|
if (!socket_.Connect(endpoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Enable TCP keep alive for all connections.
|
// Enable TCP keep alive for all connections.
|
||||||
// Because we manually always set the `have_more` flag to the socket
|
// Because we manually always set the `have_more` flag to the socket
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class Client final {
|
|||||||
*/
|
*/
|
||||||
class ClientInputStream final {
|
class ClientInputStream final {
|
||||||
public:
|
public:
|
||||||
ClientInputStream(Client &client);
|
explicit ClientInputStream(Client &client);
|
||||||
|
|
||||||
ClientInputStream(const ClientInputStream &) = delete;
|
ClientInputStream(const ClientInputStream &) = delete;
|
||||||
ClientInputStream(ClientInputStream &&) = delete;
|
ClientInputStream(ClientInputStream &&) = delete;
|
||||||
@@ -156,7 +156,7 @@ class ClientInputStream final {
|
|||||||
*/
|
*/
|
||||||
class ClientOutputStream final {
|
class ClientOutputStream final {
|
||||||
public:
|
public:
|
||||||
ClientOutputStream(Client &client);
|
explicit ClientOutputStream(Client &client);
|
||||||
|
|
||||||
ClientOutputStream(const ClientOutputStream &) = delete;
|
ClientOutputStream(const ClientOutputStream &) = delete;
|
||||||
ClientOutputStream(ClientOutputStream &&) = delete;
|
ClientOutputStream(ClientOutputStream &&) = delete;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -34,7 +34,7 @@ ClientContext::ClientContext(bool use_ssl) : use_ssl_(use_ssl), ctx_(nullptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClientContext::ClientContext(const std::string &key_file, const std::string &cert_file) : ClientContext(true) {
|
ClientContext::ClientContext(const std::string &key_file, const std::string &cert_file) : ClientContext(true) {
|
||||||
if (key_file != "" && cert_file != "") {
|
if (!key_file.empty() && !cert_file.empty()) {
|
||||||
MG_ASSERT(SSL_CTX_use_certificate_file(ctx_, cert_file.c_str(), SSL_FILETYPE_PEM) == 1,
|
MG_ASSERT(SSL_CTX_use_certificate_file(ctx_, cert_file.c_str(), SSL_FILETYPE_PEM) == 1,
|
||||||
"Couldn't load client certificate from file: {}", cert_file);
|
"Couldn't load client certificate from file: {}", cert_file);
|
||||||
MG_ASSERT(SSL_CTX_use_PrivateKey_file(ctx_, key_file.c_str(), SSL_FILETYPE_PEM) == 1,
|
MG_ASSERT(SSL_CTX_use_PrivateKey_file(ctx_, key_file.c_str(), SSL_FILETYPE_PEM) == 1,
|
||||||
@@ -124,7 +124,7 @@ ServerContext &ServerContext::operator=(ServerContext &&other) noexcept {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerContext::~ServerContext() {}
|
ServerContext::~ServerContext() = default;
|
||||||
|
|
||||||
SSL_CTX *ServerContext::context() {
|
SSL_CTX *ServerContext::context() {
|
||||||
MG_ASSERT(ctx_);
|
MG_ASSERT(ctx_);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// 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,7 @@
|
|||||||
|
|
||||||
namespace memgraph::communication {
|
namespace memgraph::communication {
|
||||||
|
|
||||||
const std::string SslGetLastError() {
|
std::string SslGetLastError() {
|
||||||
char buff[2048];
|
char buff[2048];
|
||||||
auto err = ERR_get_error();
|
auto err = ERR_get_error();
|
||||||
ERR_error_string_n(err, buff, sizeof(buff));
|
ERR_error_string_n(err, buff, sizeof(buff));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -18,6 +18,6 @@ namespace memgraph::communication {
|
|||||||
/**
|
/**
|
||||||
* This function reads and returns a string describing the last OpenSSL error.
|
* This function reads and returns a string describing the last OpenSSL error.
|
||||||
*/
|
*/
|
||||||
const std::string SslGetLastError();
|
std::string SslGetLastError();
|
||||||
|
|
||||||
} // namespace memgraph::communication
|
} // namespace memgraph::communication
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Listener final : public std::enable_shared_from_this<Listener<TRequestHand
|
|||||||
Listener(Listener &&) = delete;
|
Listener(Listener &&) = delete;
|
||||||
Listener &operator=(const Listener &) = delete;
|
Listener &operator=(const Listener &) = delete;
|
||||||
Listener &operator=(Listener &&) = delete;
|
Listener &operator=(Listener &&) = delete;
|
||||||
~Listener() {}
|
~Listener() = default;
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
static std::shared_ptr<Listener> Create(Args &&...args) {
|
static std::shared_ptr<Listener> Create(Args &&...args) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -107,7 +107,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe
|
|||||||
void DoRead() {
|
void DoRead() {
|
||||||
req_ = {};
|
req_ = {};
|
||||||
|
|
||||||
ExecuteForStream([this](auto &&stream) {
|
ExecuteForStream([this](auto &stream) {
|
||||||
boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(kSSLExpirySeconds));
|
boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(kSSLExpirySeconds));
|
||||||
|
|
||||||
boost::beast::http::async_read(
|
boost::beast::http::async_read(
|
||||||
@@ -129,7 +129,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto async_write = [this](boost::beast::http::response<boost::beast::http::string_body> msg) {
|
auto async_write = [this](boost::beast::http::response<boost::beast::http::string_body> msg) {
|
||||||
ExecuteForStream([this, &msg](auto &&stream) {
|
ExecuteForStream([this, &msg](auto &stream) {
|
||||||
// The lifetime of the message has to extend
|
// The lifetime of the message has to extend
|
||||||
// for the duration of the async operation so
|
// for the duration of the async operation so
|
||||||
// we use a shared_ptr to manage it.
|
// we use a shared_ptr to manage it.
|
||||||
@@ -171,7 +171,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto GetExecutor() {
|
auto GetExecutor() {
|
||||||
return std::visit(utils::Overloaded{[](auto &&stream) { return stream.get_executor(); }}, stream_);
|
return std::visit(utils::Overloaded{[](auto &stream) { return stream.get_executor(); }}, stream_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
@@ -51,13 +52,13 @@ class Listener final {
|
|||||||
using SessionHandler = Session<TSession, TSessionContext>;
|
using SessionHandler = Session<TSession, TSessionContext>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Listener(TSessionContext *data, ServerContext *context, int inactivity_timeout_sec, const std::string &service_name,
|
Listener(TSessionContext *data, ServerContext *context, int inactivity_timeout_sec, std::string service_name,
|
||||||
size_t workers_count)
|
size_t workers_count)
|
||||||
: data_(data),
|
: data_(data),
|
||||||
alive_(false),
|
alive_(false),
|
||||||
context_(context),
|
context_(context),
|
||||||
inactivity_timeout_sec_(inactivity_timeout_sec),
|
inactivity_timeout_sec_(inactivity_timeout_sec),
|
||||||
service_name_(service_name),
|
service_name_(std::move(service_name)),
|
||||||
workers_count_(workers_count) {}
|
workers_count_(workers_count) {}
|
||||||
|
|
||||||
~Listener() {
|
~Listener() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -44,7 +44,7 @@ class ResultStreamFaker {
|
|||||||
std::vector<memgraph::communication::bolt::Value> bvalues;
|
std::vector<memgraph::communication::bolt::Value> bvalues;
|
||||||
bvalues.reserve(values.size());
|
bvalues.reserve(values.size());
|
||||||
for (const auto &value : values) {
|
for (const auto &value : values) {
|
||||||
auto maybe_value = memgraph::glue::ToBoltValue(value, *store_, memgraph::storage::View::NEW);
|
auto maybe_value = memgraph::glue::ToBoltValue(value, store_, memgraph::storage::View::NEW);
|
||||||
MG_ASSERT(maybe_value.HasValue());
|
MG_ASSERT(maybe_value.HasValue());
|
||||||
bvalues.push_back(std::move(*maybe_value));
|
bvalues.push_back(std::move(*maybe_value));
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ class ResultStreamFaker {
|
|||||||
void Summary(const std::map<std::string, memgraph::query::TypedValue> &summary) {
|
void Summary(const std::map<std::string, memgraph::query::TypedValue> &summary) {
|
||||||
std::map<std::string, memgraph::communication::bolt::Value> bsummary;
|
std::map<std::string, memgraph::communication::bolt::Value> bsummary;
|
||||||
for (const auto &item : summary) {
|
for (const auto &item : summary) {
|
||||||
auto maybe_value = memgraph::glue::ToBoltValue(item.second, *store_, memgraph::storage::View::NEW);
|
auto maybe_value = memgraph::glue::ToBoltValue(item.second, store_, memgraph::storage::View::NEW);
|
||||||
MG_ASSERT(maybe_value.HasValue());
|
MG_ASSERT(maybe_value.HasValue());
|
||||||
bsummary.insert({item.first, std::move(*maybe_value)});
|
bsummary.insert({item.first, std::move(*maybe_value)});
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ class ResultStreamFaker {
|
|||||||
std::transform(header.begin(), header.end(), column_widths.begin(), [](const auto &s) { return s.size(); });
|
std::transform(header.begin(), header.end(), column_widths.begin(), [](const auto &s) { return s.size(); });
|
||||||
|
|
||||||
// convert all the results into strings, and track max column width
|
// convert all the results into strings, and track max column width
|
||||||
auto &results_data = results.GetResults();
|
const auto &results_data = results.GetResults();
|
||||||
std::vector<std::vector<std::string>> result_strings(results_data.size(),
|
std::vector<std::vector<std::string>> result_strings(results_data.size(),
|
||||||
std::vector<std::string>(column_widths.size()));
|
std::vector<std::string>(column_widths.size()));
|
||||||
for (int row_ind = 0; row_ind < static_cast<int>(results_data.size()); ++row_ind) {
|
for (int row_ind = 0; row_ind < static_cast<int>(results_data.size()); ++row_ind) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
@@ -51,7 +52,8 @@ using InputStream = Buffer::ReadEnd;
|
|||||||
*/
|
*/
|
||||||
class OutputStream final {
|
class OutputStream final {
|
||||||
public:
|
public:
|
||||||
OutputStream(std::function<bool(const uint8_t *, size_t, bool)> write_function) : write_function_(write_function) {}
|
explicit OutputStream(std::function<bool(const uint8_t *, size_t, bool)> write_function)
|
||||||
|
: write_function_(std::move(write_function)) {}
|
||||||
|
|
||||||
OutputStream(const OutputStream &) = delete;
|
OutputStream(const OutputStream &) = delete;
|
||||||
OutputStream(OutputStream &&) = delete;
|
OutputStream(OutputStream &&) = delete;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
|
|||||||
Listener(Listener &&) = delete;
|
Listener(Listener &&) = delete;
|
||||||
Listener &operator=(const Listener &) = delete;
|
Listener &operator=(const Listener &) = delete;
|
||||||
Listener &operator=(Listener &&) = delete;
|
Listener &operator=(Listener &&) = delete;
|
||||||
~Listener() {}
|
~Listener() = default;
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
static std::shared_ptr<Listener> Create(Args &&...args) {
|
static std::shared_ptr<Listener> Create(Args &&...args) {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ using tcp = boost::asio::ip::tcp;
|
|||||||
class OutputStream final {
|
class OutputStream final {
|
||||||
public:
|
public:
|
||||||
explicit OutputStream(std::function<bool(const uint8_t *, size_t, bool)> write_function)
|
explicit OutputStream(std::function<bool(const uint8_t *, size_t, bool)> write_function)
|
||||||
: write_function_(write_function) {}
|
: write_function_(std::move(write_function)) {}
|
||||||
|
|
||||||
OutputStream(const OutputStream &) = delete;
|
OutputStream(const OutputStream &) = delete;
|
||||||
OutputStream(OutputStream &&) = delete;
|
OutputStream(OutputStream &&) = delete;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <spdlog/sinks/base_sink.h>
|
#include <spdlog/sinks/base_sink.h>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <boost/asio/ip/tcp.hpp>
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "communication/websocket/listener.hpp"
|
#include "communication/websocket/listener.hpp"
|
||||||
#include "io/network/endpoint.hpp"
|
#include "io/network/endpoint.hpp"
|
||||||
@@ -45,7 +46,7 @@ class Server final {
|
|||||||
|
|
||||||
class LoggingSink : public spdlog::sinks::base_sink<std::mutex> {
|
class LoggingSink : public spdlog::sinks::base_sink<std::mutex> {
|
||||||
public:
|
public:
|
||||||
explicit LoggingSink(std::weak_ptr<Listener> listener) : listener_(listener) {}
|
explicit LoggingSink(std::weak_ptr<Listener> listener) : listener_(std::move(listener)) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sink_it_(const spdlog::details::log_msg &msg) override;
|
void sink_it_(const spdlog::details::log_msg &msg) override;
|
||||||
|
|||||||
29
src/coordination/CMakeLists.txt
Normal file
29
src/coordination/CMakeLists.txt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
add_library(mg-coordination STATIC)
|
||||||
|
add_library(mg::coordination ALIAS mg-coordination)
|
||||||
|
target_sources(mg-coordination
|
||||||
|
PUBLIC
|
||||||
|
include/coordination/coordinator_client.hpp
|
||||||
|
include/coordination/coordinator_state.hpp
|
||||||
|
include/coordination/coordinator_rpc.hpp
|
||||||
|
include/coordination/coordinator_server.hpp
|
||||||
|
include/coordination/coordinator_config.hpp
|
||||||
|
include/coordination/coordinator_exceptions.hpp
|
||||||
|
include/coordination/coordinator_instance.hpp
|
||||||
|
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
|
||||||
|
|
||||||
|
PRIVATE
|
||||||
|
coordinator_client.cpp
|
||||||
|
coordinator_state.cpp
|
||||||
|
coordinator_rpc.cpp
|
||||||
|
coordinator_server.cpp
|
||||||
|
coordinator_data.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
|
||||||
|
)
|
||||||
109
src/coordination/coordinator_client.cpp
Normal file
109
src/coordination/coordinator_client.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// 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_client.hpp"
|
||||||
|
|
||||||
|
#include "coordination/coordinator_config.hpp"
|
||||||
|
#include "coordination/coordinator_rpc.hpp"
|
||||||
|
#include "replication_coordination_glue/messages.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
auto CreateClientContext(const memgraph::coordination::CoordinatorClientConfig &config)
|
||||||
|
-> communication::ClientContext {
|
||||||
|
return (config.ssl) ? communication::ClientContext{config.ssl->key_file, config.ssl->cert_file}
|
||||||
|
: communication::ClientContext{};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CoordinatorClient::CoordinatorClient(CoordinatorData *coord_data, CoordinatorClientConfig config,
|
||||||
|
HealthCheckCallback succ_cb, HealthCheckCallback fail_cb)
|
||||||
|
: rpc_context_{CreateClientContext(config)},
|
||||||
|
rpc_client_{io::network::Endpoint(io::network::Endpoint::needs_resolving, config.ip_address, config.port),
|
||||||
|
&rpc_context_},
|
||||||
|
config_{std::move(config)},
|
||||||
|
coord_data_{coord_data},
|
||||||
|
succ_cb_{std::move(succ_cb)},
|
||||||
|
fail_cb_{std::move(fail_cb)} {}
|
||||||
|
|
||||||
|
auto CoordinatorClient::InstanceName() const -> std::string { return config_.instance_name; }
|
||||||
|
auto CoordinatorClient::SocketAddress() const -> std::string { return rpc_client_.Endpoint().SocketAddress(); }
|
||||||
|
|
||||||
|
void CoordinatorClient::StartFrequentCheck() {
|
||||||
|
MG_ASSERT(config_.health_check_frequency_sec > std::chrono::seconds(0),
|
||||||
|
"Health check frequency must be greater than 0");
|
||||||
|
|
||||||
|
instance_checker_.Run(
|
||||||
|
"Coord checker", config_.health_check_frequency_sec, [this, instance_name = config_.instance_name] {
|
||||||
|
try {
|
||||||
|
spdlog::trace("Sending frequent heartbeat to machine {} on {}", instance_name,
|
||||||
|
rpc_client_.Endpoint().SocketAddress());
|
||||||
|
auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||||
|
stream.AwaitResponse();
|
||||||
|
succ_cb_(coord_data_, instance_name);
|
||||||
|
} catch (const rpc::RpcFailedException &) {
|
||||||
|
fail_cb_(coord_data_, instance_name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoordinatorClient::StopFrequentCheck() { instance_checker_.Stop(); }
|
||||||
|
|
||||||
|
void CoordinatorClient::PauseFrequentCheck() { instance_checker_.Pause(); }
|
||||||
|
void CoordinatorClient::ResumeFrequentCheck() { instance_checker_.Resume(); }
|
||||||
|
|
||||||
|
auto CoordinatorClient::SetSuccCallback(HealthCheckCallback succ_cb) -> void { succ_cb_ = std::move(succ_cb); }
|
||||||
|
auto CoordinatorClient::SetFailCallback(HealthCheckCallback fail_cb) -> void { fail_cb_ = std::move(fail_cb); }
|
||||||
|
|
||||||
|
auto CoordinatorClient::ReplicationClientInfo() const -> const CoordinatorClientConfig::ReplicationClientInfo & {
|
||||||
|
return config_.replication_client_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorClient::ResetReplicationClientInfo() -> void {
|
||||||
|
// TODO (antoniofilipovic) Sync with Andi on this one
|
||||||
|
// config_.replication_client_info.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
spdlog::error("Failed to receive successful RPC failover response!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (const rpc::RpcFailedException &) {
|
||||||
|
spdlog::error("RPC error occurred while sending failover RPC!");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorClient::SendSetToReplicaRpc(CoordinatorClient::ReplClientInfo replication_client_info) const -> bool {
|
||||||
|
try {
|
||||||
|
auto stream{rpc_client_.Stream<SetMainToReplicaRpc>(std::move(replication_client_info))};
|
||||||
|
if (!stream.AwaitResponse().success) {
|
||||||
|
spdlog::error("Failed to set main to replica!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
spdlog::info("Sent request RPC from coordinator to instance to set it as replica!");
|
||||||
|
return true;
|
||||||
|
} catch (const rpc::RpcFailedException &) {
|
||||||
|
spdlog::error("Failed to send failover RPC from coordinator to new main!");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
221
src/coordination/coordinator_data.cpp
Normal file
221
src/coordination/coordinator_data.cpp
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
// 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 "coordination/coordinator_instance.hpp"
|
||||||
|
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "coordination/coordinator_data.hpp"
|
||||||
|
|
||||||
|
#include <range/v3/view.hpp>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include "libnuraft/nuraft.hxx"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
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](const CoordinatorInstance &instance) { return instance.InstanceName() == instance_name; });
|
||||||
|
|
||||||
|
MG_ASSERT(instance != coord_data->registered_instances_.end(), "Instance {} not found during callback!",
|
||||||
|
instance_name);
|
||||||
|
return *instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
auto &instance = find_instance(coord_data, instance_name);
|
||||||
|
MG_ASSERT(instance.IsReplica(), "Instance {} is not a replica!", instance_name);
|
||||||
|
instance.UpdateLastResponseTime();
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
auto &instance = find_instance(coord_data, instance_name);
|
||||||
|
MG_ASSERT(instance.IsReplica(), "Instance {} is not a replica!", instance_name);
|
||||||
|
instance.UpdateInstanceStatus();
|
||||||
|
};
|
||||||
|
|
||||||
|
main_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 main successful callback", instance_name);
|
||||||
|
auto &instance = find_instance(coord_data, instance_name);
|
||||||
|
MG_ASSERT(instance.IsMain(), "Instance {} is not a main!", instance_name);
|
||||||
|
instance.UpdateLastResponseTime();
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
auto &instance = find_instance(coord_data, instance_name);
|
||||||
|
MG_ASSERT(instance.IsMain(), "Instance {} is not a main!", instance_name);
|
||||||
|
if (bool main_alive = instance.UpdateInstanceStatus(); !main_alive) {
|
||||||
|
spdlog::info("Main instance {} is not alive, starting automatic failover", instance_name);
|
||||||
|
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::DoFailover() -> DoFailoverStatus {
|
||||||
|
using ReplicationClientInfo = CoordinatorClientConfig::ReplicationClientInfo;
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
return DoFailoverStatus::ALL_REPLICAS_DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
chosen_replica_instance->PrepareForFailover();
|
||||||
|
|
||||||
|
std::vector<ReplicationClientInfo> repl_clients_info;
|
||||||
|
repl_clients_info.reserve(std::ranges::distance(replica_instances));
|
||||||
|
|
||||||
|
auto const not_chosen_replica_instance = [&chosen_replica_instance](const CoordinatorInstance &instance) {
|
||||||
|
return instance != *chosen_replica_instance;
|
||||||
|
};
|
||||||
|
auto const not_main = [](const CoordinatorInstance &instance) { return !instance.IsMain(); };
|
||||||
|
|
||||||
|
// TODO (antoniofilipovic): Should we send also data on old MAIN???
|
||||||
|
// TODO: (andi) Don't send replicas which aren't alive
|
||||||
|
for (const auto &unchosen_replica_instance :
|
||||||
|
replica_instances | ranges::views::filter(not_chosen_replica_instance) | ranges::views::filter(not_main)) {
|
||||||
|
repl_clients_info.emplace_back(unchosen_replica_instance.client_.ReplicationClientInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chosen_replica_instance->client_.SendPromoteReplicaToMainRpc(std::move(repl_clients_info))) {
|
||||||
|
chosen_replica_instance->RestoreAfterFailedFailover();
|
||||||
|
return DoFailoverStatus::RPC_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto old_main = std::ranges::find_if(registered_instances_, &CoordinatorInstance::IsMain);
|
||||||
|
// TODO: (andi) For performing restoration we will have to improve this
|
||||||
|
old_main->client_.PauseFrequentCheck();
|
||||||
|
|
||||||
|
chosen_replica_instance->PostFailover(main_succ_cb_, main_fail_cb_);
|
||||||
|
|
||||||
|
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 = [](const CoordinatorInstance &instance) -> std::string {
|
||||||
|
if (!instance.IsAlive()) return "";
|
||||||
|
if (instance.IsMain()) return "main";
|
||||||
|
return "replica";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto const instance_to_status =
|
||||||
|
[&stringify_repl_role](const CoordinatorInstance &instance) -> CoordinatorInstanceStatus {
|
||||||
|
return {.instance_name = instance.InstanceName(),
|
||||||
|
.socket_address = instance.SocketAddress(),
|
||||||
|
.replication_role = stringify_repl_role(instance),
|
||||||
|
.is_alive = instance.IsAlive()};
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto lock = std::shared_lock{coord_data_lock_};
|
||||||
|
std::ranges::transform(registered_instances_, std::back_inserter(instances_status), instance_to_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorData::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus {
|
||||||
|
auto lock = std::lock_guard{coord_data_lock_};
|
||||||
|
|
||||||
|
// Find replica we already registered
|
||||||
|
auto registered_replica = std::find_if(
|
||||||
|
registered_instances_.begin(), registered_instances_.end(),
|
||||||
|
[instance_name](const CoordinatorInstance &instance) { return instance.InstanceName() == instance_name; });
|
||||||
|
|
||||||
|
// if replica not found...
|
||||||
|
if (registered_replica == registered_instances_.end()) {
|
||||||
|
spdlog::error("You didn't register instance with given name {}", instance_name);
|
||||||
|
return SetInstanceToMainCoordinatorStatus::NO_INSTANCE_WITH_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
registered_replica->client_.PauseFrequentCheck();
|
||||||
|
|
||||||
|
std::vector<CoordinatorClientConfig::ReplicationClientInfo> repl_clients_info;
|
||||||
|
repl_clients_info.reserve(registered_instances_.size() - 1);
|
||||||
|
std::ranges::for_each(registered_instances_,
|
||||||
|
[registered_replica, &repl_clients_info](const CoordinatorInstance &replica) {
|
||||||
|
if (replica != *registered_replica) {
|
||||||
|
repl_clients_info.emplace_back(replica.client_.ReplicationClientInfo());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PROMOTE REPLICA TO MAIN
|
||||||
|
// THIS SHOULD FAIL HERE IF IT IS DOWN
|
||||||
|
if (auto result = registered_replica->client_.SendPromoteReplicaToMainRpc(std::move(repl_clients_info)); !result) {
|
||||||
|
registered_replica->client_.ResumeFrequentCheck();
|
||||||
|
return SetInstanceToMainCoordinatorStatus::COULD_NOT_PROMOTE_TO_MAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
registered_replica->client_.SetSuccCallback(main_succ_cb_);
|
||||||
|
registered_replica->client_.SetFailCallback(main_fail_cb_);
|
||||||
|
registered_replica->replication_role_ = replication_coordination_glue::ReplicationRole::MAIN;
|
||||||
|
registered_replica->client_.ResumeFrequentCheck();
|
||||||
|
|
||||||
|
return SetInstanceToMainCoordinatorStatus::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorData::RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus {
|
||||||
|
auto lock = std::lock_guard{coord_data_lock_};
|
||||||
|
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](const CoordinatorInstance &instance) {
|
||||||
|
spdlog::trace("Comparing {} with {}", instance.SocketAddress(), config.SocketAddress());
|
||||||
|
return instance.SocketAddress() == config.SocketAddress();
|
||||||
|
})) {
|
||||||
|
return RegisterInstanceCoordinatorStatus::END_POINT_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoordinatorClientConfig::ReplicationClientInfo replication_client_info_copy = config.replication_client_info;
|
||||||
|
|
||||||
|
// TODO (antoniofilipovic) create and then push back
|
||||||
|
auto *instance = ®istered_instances_.emplace_back(this, std::move(config), replica_succ_cb_, replica_fail_cb_,
|
||||||
|
replication_coordination_glue::ReplicationRole::REPLICA);
|
||||||
|
if (auto res = instance->client_.SendSetToReplicaRpc(replication_client_info_copy); !res) {
|
||||||
|
return RegisterInstanceCoordinatorStatus::RPC_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->client_.StartFrequentCheck();
|
||||||
|
|
||||||
|
return RegisterInstanceCoordinatorStatus::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
107
src/coordination/coordinator_rpc.cpp
Normal file
107
src/coordination/coordinator_rpc.cpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// 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_rpc.hpp"
|
||||||
|
|
||||||
|
#include "coordination/coordinator_slk.hpp"
|
||||||
|
#include "slk/serialization.hpp"
|
||||||
|
|
||||||
|
namespace memgraph {
|
||||||
|
|
||||||
|
namespace coordination {
|
||||||
|
|
||||||
|
void PromoteReplicaToMainReq::Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoteReplicaToMainReq::Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(self, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoteReplicaToMainRes::Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PromoteReplicaToMainRes::Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(self, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMainToReplicaReq::Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMainToReplicaReq::Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(self, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMainToReplicaRes::Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMainToReplicaRes::Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(self, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace coordination
|
||||||
|
|
||||||
|
constexpr utils::TypeInfo coordination::PromoteReplicaToMainReq::kType{utils::TypeId::COORD_FAILOVER_REQ,
|
||||||
|
"CoordPromoteReplicaToMainReq", nullptr};
|
||||||
|
|
||||||
|
constexpr utils::TypeInfo coordination::PromoteReplicaToMainRes::kType{utils::TypeId::COORD_FAILOVER_RES,
|
||||||
|
"CoordPromoteReplicaToMainRes", nullptr};
|
||||||
|
|
||||||
|
constexpr utils::TypeInfo coordination::SetMainToReplicaReq::kType{utils::TypeId::COORD_SET_REPL_MAIN_REQ,
|
||||||
|
"CoordSetReplMainReq", nullptr};
|
||||||
|
|
||||||
|
constexpr utils::TypeInfo coordination::SetMainToReplicaRes::kType{utils::TypeId::COORD_SET_REPL_MAIN_RES,
|
||||||
|
"CoordSetReplMainRes", nullptr};
|
||||||
|
|
||||||
|
namespace slk {
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self.success, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(&self->success, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self.replication_clients_info, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(&self->replication_clients_info, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self.replication_client_info, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(&self->replication_client_info, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||||
|
memgraph::slk::Save(self.success, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||||
|
memgraph::slk::Load(&self->success, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace slk
|
||||||
|
|
||||||
|
} // namespace memgraph
|
||||||
|
|
||||||
|
#endif
|
||||||
57
src/coordination/coordinator_server.cpp
Normal file
57
src/coordination/coordinator_server.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// 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_server.hpp"
|
||||||
|
#include "replication_coordination_glue/messages.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto CreateServerContext(const memgraph::coordination::CoordinatorServerConfig &config)
|
||||||
|
-> communication::ServerContext {
|
||||||
|
return (config.ssl) ? communication::ServerContext{config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file,
|
||||||
|
config.ssl->verify_peer}
|
||||||
|
: communication::ServerContext{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: The coordinator server doesn't more than 1 processing thread - each replica can
|
||||||
|
// have only a single coordinator server. Also, the single-threaded guarantee
|
||||||
|
// simplifies the rest of the implementation.
|
||||||
|
constexpr auto kCoordinatorServerThreads = 1;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CoordinatorServer::CoordinatorServer(const CoordinatorServerConfig &config)
|
||||||
|
: rpc_server_context_{CreateServerContext(config)},
|
||||||
|
rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_,
|
||||||
|
kCoordinatorServerThreads} {
|
||||||
|
rpc_server_.Register<replication_coordination_glue::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
|
||||||
|
spdlog::debug("Received FrequentHeartbeatRpc on coordinator server");
|
||||||
|
replication_coordination_glue::FrequentHeartbeatHandler(req_reader, res_builder);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CoordinatorServer::~CoordinatorServer() {
|
||||||
|
if (rpc_server_.IsRunning()) {
|
||||||
|
auto const &endpoint = rpc_server_.endpoint();
|
||||||
|
spdlog::trace("Closing coordinator server on {}:{}", endpoint.address, endpoint.port);
|
||||||
|
rpc_server_.Shutdown();
|
||||||
|
}
|
||||||
|
rpc_server_.AwaitShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CoordinatorServer::Start() { return rpc_server_.Start(); }
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
89
src/coordination/coordinator_state.cpp
Normal file
89
src/coordination/coordinator_state.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// 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_state.hpp"
|
||||||
|
|
||||||
|
#include "coordination/coordinator_config.hpp"
|
||||||
|
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||||
|
#include "flags/replication.hpp"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include "utils/logging.hpp"
|
||||||
|
#include "utils/variant_helpers.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
CoordinatorState::CoordinatorState() {
|
||||||
|
MG_ASSERT(!(FLAGS_coordinator && FLAGS_coordinator_server_port),
|
||||||
|
"Instance cannot be a coordinator and have registered coordinator server.");
|
||||||
|
|
||||||
|
spdlog::info("Executing coordinator constructor");
|
||||||
|
if (FLAGS_coordinator_server_port) {
|
||||||
|
spdlog::info("Coordinator server port set");
|
||||||
|
auto const config = CoordinatorServerConfig{
|
||||||
|
.ip_address = kDefaultReplicationServerIp,
|
||||||
|
.port = static_cast<uint16_t>(FLAGS_coordinator_server_port),
|
||||||
|
};
|
||||||
|
spdlog::info("Executing coordinator constructor main replica");
|
||||||
|
|
||||||
|
data_ = CoordinatorMainReplicaData{.coordinator_server_ = std::make_unique<CoordinatorServer>(config)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorState::RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus {
|
||||||
|
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||||
|
"Coordinator cannot register replica since variant holds wrong alternative");
|
||||||
|
|
||||||
|
return std::visit(
|
||||||
|
memgraph::utils::Overloaded{
|
||||||
|
[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||||
|
return RegisterInstanceCoordinatorStatus::NOT_COORDINATOR;
|
||||||
|
},
|
||||||
|
[config](CoordinatorData &coordinator_data) { return coordinator_data.RegisterInstance(config); }},
|
||||||
|
data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorState::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus {
|
||||||
|
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||||
|
"Coordinator cannot register replica since variant holds wrong alternative");
|
||||||
|
|
||||||
|
return std::visit(
|
||||||
|
memgraph::utils::Overloaded{[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||||
|
return SetInstanceToMainCoordinatorStatus::NOT_COORDINATOR;
|
||||||
|
},
|
||||||
|
[&instance_name](CoordinatorData &coordinator_data) {
|
||||||
|
return coordinator_data.SetInstanceToMain(instance_name);
|
||||||
|
}},
|
||||||
|
data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorState::ShowInstances() const -> std::vector<CoordinatorInstanceStatus> {
|
||||||
|
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||||
|
"Can't call show instances on data_, as variant holds wrong alternative");
|
||||||
|
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");
|
||||||
|
return *std::get<CoordinatorMainReplicaData>(data_).coordinator_server_;
|
||||||
|
}
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
22
src/coordination/include/coordination/constants.hpp
Normal file
22
src/coordination/include/coordination/constants.hpp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
#ifdef MG_EXPERIMENTAL_HIGH_AVAILABILITY
|
||||||
|
constexpr bool allow_ha = true;
|
||||||
|
#else
|
||||||
|
constexpr bool allow_ha = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
77
src/coordination/include/coordination/coordinator_client.hpp
Normal file
77
src/coordination/include/coordination/coordinator_client.hpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "coordination/coordinator_config.hpp"
|
||||||
|
#include "rpc/client.hpp"
|
||||||
|
#include "utils/scheduler.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
class CoordinatorData;
|
||||||
|
using HealthCheckCallback = std::function<void(CoordinatorData *, std::string_view)>;
|
||||||
|
|
||||||
|
class CoordinatorClient {
|
||||||
|
public:
|
||||||
|
using ReplClientInfo = CoordinatorClientConfig::ReplicationClientInfo;
|
||||||
|
using ReplicationClientsInfo = std::vector<ReplClientInfo>;
|
||||||
|
|
||||||
|
explicit CoordinatorClient(CoordinatorData *coord_data_, CoordinatorClientConfig config, HealthCheckCallback succ_cb,
|
||||||
|
HealthCheckCallback fail_cb);
|
||||||
|
|
||||||
|
~CoordinatorClient() = default;
|
||||||
|
|
||||||
|
CoordinatorClient(CoordinatorClient &) = delete;
|
||||||
|
CoordinatorClient &operator=(CoordinatorClient const &) = delete;
|
||||||
|
|
||||||
|
CoordinatorClient(CoordinatorClient &&) noexcept = delete;
|
||||||
|
CoordinatorClient &operator=(CoordinatorClient &&) noexcept = delete;
|
||||||
|
|
||||||
|
void StartFrequentCheck();
|
||||||
|
void StopFrequentCheck();
|
||||||
|
void PauseFrequentCheck();
|
||||||
|
void ResumeFrequentCheck();
|
||||||
|
|
||||||
|
auto InstanceName() const -> std::string;
|
||||||
|
auto SocketAddress() const -> std::string;
|
||||||
|
|
||||||
|
auto SendPromoteReplicaToMainRpc(ReplicationClientsInfo replication_clients_info) const -> bool;
|
||||||
|
|
||||||
|
auto ReplicationClientInfo() const -> const ReplClientInfo &;
|
||||||
|
auto ResetReplicationClientInfo() -> void;
|
||||||
|
|
||||||
|
auto SendSetToReplicaRpc(ReplClientInfo replication_client_info) const -> bool;
|
||||||
|
|
||||||
|
auto SetSuccCallback(HealthCheckCallback succ_cb) -> void;
|
||||||
|
auto SetFailCallback(HealthCheckCallback fail_cb) -> void;
|
||||||
|
|
||||||
|
friend bool operator==(CoordinatorClient const &first, CoordinatorClient const &second) {
|
||||||
|
return first.config_ == second.config_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
utils::Scheduler instance_checker_;
|
||||||
|
|
||||||
|
// TODO: (andi) Pimpl?
|
||||||
|
communication::ClientContext rpc_context_;
|
||||||
|
mutable rpc::Client rpc_client_;
|
||||||
|
|
||||||
|
CoordinatorClientConfig config_;
|
||||||
|
CoordinatorData *coord_data_;
|
||||||
|
HealthCheckCallback succ_cb_;
|
||||||
|
HealthCheckCallback fail_cb_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
struct CoordinatorClusterConfig {
|
||||||
|
static constexpr int alive_response_time_difference_sec_{5};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
78
src/coordination/include/coordination/coordinator_config.hpp
Normal file
78
src/coordination/include/coordination/coordinator_config.hpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "replication_coordination_glue/mode.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
|
||||||
|
|
||||||
|
struct CoordinatorClientConfig {
|
||||||
|
std::string instance_name;
|
||||||
|
std::string ip_address;
|
||||||
|
uint16_t port{};
|
||||||
|
std::chrono::seconds health_check_frequency_sec{1};
|
||||||
|
|
||||||
|
auto SocketAddress() const -> std::string { return ip_address + ":" + std::to_string(port); }
|
||||||
|
|
||||||
|
// Info which coordinator will send to new main when performing failover
|
||||||
|
struct ReplicationClientInfo {
|
||||||
|
// Must be the same as CoordinatorClientConfig's instance_name
|
||||||
|
std::string instance_name;
|
||||||
|
replication_coordination_glue::ReplicationMode replication_mode{};
|
||||||
|
std::string replication_ip_address;
|
||||||
|
uint16_t replication_port{};
|
||||||
|
|
||||||
|
friend bool operator==(ReplicationClientInfo const &, ReplicationClientInfo const &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Each instance has replication config in case it fails
|
||||||
|
ReplicationClientInfo replication_client_info;
|
||||||
|
|
||||||
|
struct SSL {
|
||||||
|
std::string key_file;
|
||||||
|
std::string cert_file;
|
||||||
|
|
||||||
|
friend bool operator==(const SSL &, const SSL &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<SSL> ssl;
|
||||||
|
|
||||||
|
friend bool operator==(CoordinatorClientConfig const &, CoordinatorClientConfig const &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CoordinatorServerConfig {
|
||||||
|
std::string ip_address;
|
||||||
|
uint16_t port{};
|
||||||
|
struct SSL {
|
||||||
|
std::string key_file;
|
||||||
|
std::string cert_file;
|
||||||
|
std::string ca_file;
|
||||||
|
bool verify_peer{};
|
||||||
|
friend bool operator==(SSL const &, SSL const &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<SSL> ssl;
|
||||||
|
|
||||||
|
friend bool operator==(CoordinatorServerConfig const &, CoordinatorServerConfig const &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
49
src/coordination/include/coordination/coordinator_data.hpp
Normal file
49
src/coordination/include/coordination/coordinator_data.hpp
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#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 <list>
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
class CoordinatorData {
|
||||||
|
public:
|
||||||
|
CoordinatorData();
|
||||||
|
|
||||||
|
[[nodiscard]] auto DoFailover() -> DoFailoverStatus;
|
||||||
|
|
||||||
|
[[nodiscard]] auto RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
|
||||||
|
[[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
|
||||||
|
|
||||||
|
auto ShowInstances() const -> std::vector<CoordinatorInstanceStatus>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable utils::RWLock coord_data_lock_{utils::RWLock::Priority::READ};
|
||||||
|
HealthCheckCallback main_succ_cb_, main_fail_cb_, replica_succ_cb_, replica_fail_cb_;
|
||||||
|
// Must be std::list because we rely on pointer stability
|
||||||
|
std::list<CoordinatorInstance> registered_instances_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CoordinatorMainReplicaData {
|
||||||
|
std::unique_ptr<CoordinatorServer> coordinator_server_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "utils/exceptions.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
class CoordinatorFailoverException final : public utils::BasicException {
|
||||||
|
public:
|
||||||
|
explicit CoordinatorFailoverException(const std::string_view what) noexcept
|
||||||
|
: BasicException("Failover didn't complete successfully: " + std::string(what)) {}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
explicit CoordinatorFailoverException(fmt::format_string<Args...> fmt, Args &&...args) noexcept
|
||||||
|
: CoordinatorFailoverException(fmt::format(fmt, std::forward<Args>(args)...)) {}
|
||||||
|
|
||||||
|
SPECIALIZE_GET_EXCEPTION_NAME(CoordinatorFailoverException)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "coordination/coordinator_client.hpp"
|
||||||
|
#include "coordination/coordinator_cluster_config.hpp"
|
||||||
|
#include "replication_coordination_glue/role.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
class CoordinatorData;
|
||||||
|
|
||||||
|
class CoordinatorInstance {
|
||||||
|
public:
|
||||||
|
CoordinatorInstance(CoordinatorData *data, CoordinatorClientConfig config, HealthCheckCallback succ_cb,
|
||||||
|
HealthCheckCallback fail_cb, replication_coordination_glue::ReplicationRole replication_role)
|
||||||
|
: client_(data, std::move(config), std::move(succ_cb), std::move(fail_cb)),
|
||||||
|
replication_role_(replication_role),
|
||||||
|
is_alive_(true) {}
|
||||||
|
|
||||||
|
CoordinatorInstance(CoordinatorInstance const &other) = delete;
|
||||||
|
CoordinatorInstance &operator=(CoordinatorInstance const &other) = delete;
|
||||||
|
CoordinatorInstance(CoordinatorInstance &&other) noexcept = delete;
|
||||||
|
CoordinatorInstance &operator=(CoordinatorInstance &&other) noexcept = delete;
|
||||||
|
~CoordinatorInstance() = default;
|
||||||
|
|
||||||
|
auto UpdateInstanceStatus() -> bool {
|
||||||
|
is_alive_ = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - last_response_time_)
|
||||||
|
.count() < CoordinatorClusterConfig::alive_response_time_difference_sec_;
|
||||||
|
return is_alive_;
|
||||||
|
}
|
||||||
|
auto UpdateLastResponseTime() -> void { last_response_time_ = std::chrono::system_clock::now(); }
|
||||||
|
|
||||||
|
auto InstanceName() const -> std::string { return client_.InstanceName(); }
|
||||||
|
auto SocketAddress() const -> std::string { return client_.SocketAddress(); }
|
||||||
|
auto IsAlive() const -> bool { return is_alive_; }
|
||||||
|
|
||||||
|
auto IsReplica() const -> bool {
|
||||||
|
return replication_role_ == replication_coordination_glue::ReplicationRole::REPLICA;
|
||||||
|
}
|
||||||
|
auto IsMain() const -> bool { return replication_role_ == replication_coordination_glue::ReplicationRole::MAIN; }
|
||||||
|
|
||||||
|
auto PrepareForFailover() -> void { client_.PauseFrequentCheck(); }
|
||||||
|
auto RestoreAfterFailedFailover() -> void { client_.ResumeFrequentCheck(); }
|
||||||
|
|
||||||
|
auto PostFailover(HealthCheckCallback main_succ_cb, HealthCheckCallback main_fail_cb) -> void {
|
||||||
|
replication_role_ = replication_coordination_glue::ReplicationRole::MAIN;
|
||||||
|
client_.SetSuccCallback(std::move(main_succ_cb));
|
||||||
|
client_.SetFailCallback(std::move(main_fail_cb));
|
||||||
|
// Comment with Andi but we shouldn't delete this, what if this MAIN FAILS AGAIN
|
||||||
|
// client_.ResetReplicationClientInfo();
|
||||||
|
client_.ResumeFrequentCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
CoordinatorClient client_;
|
||||||
|
replication_coordination_glue::ReplicationRole replication_role_;
|
||||||
|
std::chrono::system_clock::time_point last_response_time_{};
|
||||||
|
bool is_alive_{false};
|
||||||
|
|
||||||
|
friend bool operator==(CoordinatorInstance const &first, CoordinatorInstance const &second) {
|
||||||
|
return first.client_ == second.client_ && first.replication_role_ == second.replication_role_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "io/network/endpoint.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
struct CoordinatorInstanceStatus {
|
||||||
|
std::string instance_name;
|
||||||
|
std::string socket_address;
|
||||||
|
std::string replication_role;
|
||||||
|
bool is_alive;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
|
||||||
|
#endif
|
||||||
104
src/coordination/include/coordination/coordinator_rpc.hpp
Normal file
104
src/coordination/include/coordination/coordinator_rpc.hpp
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "coordination/coordinator_config.hpp"
|
||||||
|
#include "rpc/messages.hpp"
|
||||||
|
#include "slk/serialization.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
struct PromoteReplicaToMainReq {
|
||||||
|
static const utils::TypeInfo kType;
|
||||||
|
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||||
|
|
||||||
|
static void Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader);
|
||||||
|
static void Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
explicit PromoteReplicaToMainReq(std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info)
|
||||||
|
: replication_clients_info(std::move(replication_clients_info)) {}
|
||||||
|
PromoteReplicaToMainReq() = default;
|
||||||
|
|
||||||
|
std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PromoteReplicaToMainRes {
|
||||||
|
static const utils::TypeInfo kType;
|
||||||
|
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||||
|
|
||||||
|
static void Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader);
|
||||||
|
static void Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
explicit PromoteReplicaToMainRes(bool success) : success(success) {}
|
||||||
|
PromoteReplicaToMainRes() = default;
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
|
||||||
|
using PromoteReplicaToMainRpc = rpc::RequestResponse<PromoteReplicaToMainReq, PromoteReplicaToMainRes>;
|
||||||
|
|
||||||
|
struct SetMainToReplicaReq {
|
||||||
|
static const utils::TypeInfo kType;
|
||||||
|
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||||
|
|
||||||
|
static void Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||||
|
static void Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
explicit SetMainToReplicaReq(CoordinatorClientConfig::ReplicationClientInfo replication_client_info)
|
||||||
|
: replication_client_info(std::move(replication_client_info)) {}
|
||||||
|
|
||||||
|
SetMainToReplicaReq() = default;
|
||||||
|
|
||||||
|
CoordinatorClientConfig::ReplicationClientInfo replication_client_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetMainToReplicaRes {
|
||||||
|
static const utils::TypeInfo kType;
|
||||||
|
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||||
|
|
||||||
|
static void Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||||
|
static void Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
explicit SetMainToReplicaRes(bool success) : success(success) {}
|
||||||
|
SetMainToReplicaRes() = default;
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SetMainToReplicaRpc = rpc::RequestResponse<SetMainToReplicaReq, SetMainToReplicaRes>;
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
|
||||||
|
// SLK serialization declarations
|
||||||
|
namespace memgraph::slk {
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader);
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader);
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||||
|
|
||||||
|
void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||||
|
|
||||||
|
void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||||
|
|
||||||
|
} // namespace memgraph::slk
|
||||||
|
|
||||||
|
#endif
|
||||||
44
src/coordination/include/coordination/coordinator_server.hpp
Normal file
44
src/coordination/include/coordination/coordinator_server.hpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "coordination/coordinator_config.hpp"
|
||||||
|
#include "rpc/server.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
class CoordinatorServer {
|
||||||
|
public:
|
||||||
|
explicit CoordinatorServer(const CoordinatorServerConfig &config);
|
||||||
|
CoordinatorServer(const CoordinatorServer &) = delete;
|
||||||
|
CoordinatorServer(CoordinatorServer &&) = delete;
|
||||||
|
CoordinatorServer &operator=(const CoordinatorServer &) = delete;
|
||||||
|
CoordinatorServer &operator=(CoordinatorServer &&) = delete;
|
||||||
|
|
||||||
|
virtual ~CoordinatorServer();
|
||||||
|
|
||||||
|
bool Start();
|
||||||
|
|
||||||
|
template <typename TRequestResponse, typename F>
|
||||||
|
void Register(F &&callback) {
|
||||||
|
rpc_server_.Register<TRequestResponse>(std::forward<F>(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
communication::ServerContext rpc_server_context_;
|
||||||
|
rpc::Server rpc_server_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
38
src/coordination/include/coordination/coordinator_slk.hpp
Normal file
38
src/coordination/include/coordination/coordinator_slk.hpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "coordination/coordinator_config.hpp"
|
||||||
|
#include "slk/serialization.hpp"
|
||||||
|
#include "slk/streams.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::slk {
|
||||||
|
|
||||||
|
using ReplicationClientInfo = coordination::CoordinatorClientConfig::ReplicationClientInfo;
|
||||||
|
|
||||||
|
inline void Save(const ReplicationClientInfo &obj, Builder *builder) {
|
||||||
|
Save(obj.instance_name, builder);
|
||||||
|
Save(obj.replication_mode, builder);
|
||||||
|
Save(obj.replication_ip_address, builder);
|
||||||
|
Save(obj.replication_port, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Load(ReplicationClientInfo *obj, Reader *reader) {
|
||||||
|
Load(&obj->instance_name, reader);
|
||||||
|
Load(&obj->replication_mode, reader);
|
||||||
|
Load(&obj->replication_ip_address, reader);
|
||||||
|
Load(&obj->replication_port, reader);
|
||||||
|
}
|
||||||
|
} // namespace memgraph::slk
|
||||||
|
#endif
|
||||||
53
src/coordination/include/coordination/coordinator_state.hpp
Normal file
53
src/coordination/include/coordination/coordinator_state.hpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#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>
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
class CoordinatorState {
|
||||||
|
public:
|
||||||
|
CoordinatorState();
|
||||||
|
~CoordinatorState() = default;
|
||||||
|
|
||||||
|
CoordinatorState(const CoordinatorState &) = delete;
|
||||||
|
CoordinatorState &operator=(const CoordinatorState &) = delete;
|
||||||
|
|
||||||
|
CoordinatorState(CoordinatorState &&) noexcept = delete;
|
||||||
|
CoordinatorState &operator=(CoordinatorState &&) noexcept = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] auto RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
|
||||||
|
|
||||||
|
[[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
|
||||||
|
|
||||||
|
auto ShowInstances() const -> std::vector<CoordinatorInstanceStatus>;
|
||||||
|
|
||||||
|
// 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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
21
src/coordination/include/coordination/failover_status.hpp
Normal file
21
src/coordination/include/coordination/failover_status.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#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
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace memgraph::coordination {
|
||||||
|
|
||||||
|
enum class RegisterInstanceCoordinatorStatus : uint8_t {
|
||||||
|
NAME_EXISTS,
|
||||||
|
END_POINT_EXISTS,
|
||||||
|
COULD_NOT_BE_PERSISTED,
|
||||||
|
NOT_COORDINATOR,
|
||||||
|
RPC_FAILED,
|
||||||
|
SUCCESS
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SetInstanceToMainCoordinatorStatus : uint8_t {
|
||||||
|
NO_INSTANCE_WITH_NAME,
|
||||||
|
NOT_COORDINATOR,
|
||||||
|
SUCCESS,
|
||||||
|
COULD_NOT_PROMOTE_TO_MAIN,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::coordination
|
||||||
|
#endif
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
|
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)
|
||||||
add_library(mg-dbms STATIC database.cpp replication_handler.cpp inmemory/replication_handlers.cpp)
|
target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query mg-replication mg-coordination)
|
||||||
target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query)
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -13,6 +13,13 @@
|
|||||||
|
|
||||||
namespace memgraph::dbms {
|
namespace memgraph::dbms {
|
||||||
|
|
||||||
constexpr static const char *kDefaultDB = "memgraph"; //!< Name of the default database
|
constexpr std::string_view kDefaultDB = "memgraph"; //!< Name of the default database
|
||||||
|
constexpr std::string_view kMultiTenantDir = "databases"; //!< Name of the multi-tenant directory
|
||||||
|
|
||||||
|
#ifdef MG_EXPERIMENTAL_REPLICATION_MULTITENANCY
|
||||||
|
constexpr bool allow_mt_repl = true;
|
||||||
|
#else
|
||||||
|
constexpr bool allow_mt_repl = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace memgraph::dbms
|
} // namespace memgraph::dbms
|
||||||
|
|||||||
38
src/dbms/coordinator_handler.cpp
Normal file
38
src/dbms/coordinator_handler.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// 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 "coordination/register_main_replica_coordinator_status.hpp"
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "dbms/coordinator_handler.hpp"
|
||||||
|
|
||||||
|
#include "dbms/dbms_handler.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::dbms {
|
||||||
|
|
||||||
|
CoordinatorHandler::CoordinatorHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
|
||||||
|
|
||||||
|
auto CoordinatorHandler::RegisterInstance(memgraph::coordination::CoordinatorClientConfig config)
|
||||||
|
-> coordination::RegisterInstanceCoordinatorStatus {
|
||||||
|
return dbms_handler_.CoordinatorState().RegisterInstance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorHandler::SetInstanceToMain(std::string instance_name)
|
||||||
|
-> coordination::SetInstanceToMainCoordinatorStatus {
|
||||||
|
return dbms_handler_.CoordinatorState().SetInstanceToMain(std::move(instance_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CoordinatorHandler::ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus> {
|
||||||
|
return dbms_handler_.CoordinatorState().ShowInstances();
|
||||||
|
}
|
||||||
|
} // namespace memgraph::dbms
|
||||||
|
|
||||||
|
#endif
|
||||||
47
src/dbms/coordinator_handler.hpp
Normal file
47
src/dbms/coordinator_handler.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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "utils/result.hpp"
|
||||||
|
|
||||||
|
#include "coordination/coordinator_config.hpp"
|
||||||
|
#include "coordination/coordinator_instance_status.hpp"
|
||||||
|
#include "coordination/failover_status.hpp"
|
||||||
|
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace memgraph::dbms {
|
||||||
|
|
||||||
|
class DbmsHandler;
|
||||||
|
|
||||||
|
class CoordinatorHandler {
|
||||||
|
public:
|
||||||
|
explicit CoordinatorHandler(DbmsHandler &dbms_handler);
|
||||||
|
|
||||||
|
auto RegisterInstance(coordination::CoordinatorClientConfig config)
|
||||||
|
-> coordination::RegisterInstanceCoordinatorStatus;
|
||||||
|
|
||||||
|
auto SetInstanceToMain(std::string instance_name) -> coordination::SetInstanceToMainCoordinatorStatus;
|
||||||
|
|
||||||
|
auto ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DbmsHandler &dbms_handler_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::dbms
|
||||||
|
#endif
|
||||||
153
src/dbms/coordinator_handlers.cpp
Normal file
153
src/dbms/coordinator_handlers.cpp
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// 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();
|
||||||
|
|
||||||
|
if (!repl_state.IsMain()) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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("Failover must be performed on replica!");
|
||||||
|
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
|
||||||
34
src/dbms/coordinator_handlers.hpp
Normal file
34
src/dbms/coordinator_handlers.hpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
|
||||||
|
#include "slk/serialization.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::dbms {
|
||||||
|
|
||||||
|
class DbmsHandler;
|
||||||
|
|
||||||
|
class CoordinatorHandlers {
|
||||||
|
public:
|
||||||
|
static void Register(DbmsHandler &dbms_handler);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void PromoteReplicaToMainHandler(DbmsHandler &dbms_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
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -21,12 +21,12 @@ template struct memgraph::utils::Gatekeeper<memgraph::dbms::Database>;
|
|||||||
|
|
||||||
namespace memgraph::dbms {
|
namespace memgraph::dbms {
|
||||||
|
|
||||||
Database::Database(storage::Config config, const replication::ReplicationState &repl_state)
|
Database::Database(storage::Config config, replication::ReplicationState &repl_state)
|
||||||
: trigger_store_(config.durability.storage_directory / "triggers"),
|
: trigger_store_(config.durability.storage_directory / "triggers"),
|
||||||
streams_{config.durability.storage_directory / "streams"},
|
streams_{config.durability.storage_directory / "streams"},
|
||||||
plan_cache_{FLAGS_query_plan_cache_max_size},
|
plan_cache_{FLAGS_query_plan_cache_max_size},
|
||||||
repl_state_(&repl_state) {
|
repl_state_(&repl_state) {
|
||||||
if (config.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk ||
|
if (config.salient.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk ||
|
||||||
utils::DirExists(config.disk.main_storage_directory)) {
|
utils::DirExists(config.disk.main_storage_directory)) {
|
||||||
storage_ = std::make_unique<storage::DiskStorage>(std::move(config));
|
storage_ = std::make_unique<storage::DiskStorage>(std::move(config));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -48,7 +48,7 @@ class Database {
|
|||||||
*
|
*
|
||||||
* @param config storage configuration
|
* @param config storage configuration
|
||||||
*/
|
*/
|
||||||
explicit Database(storage::Config config, const replication::ReplicationState &repl_state);
|
explicit Database(storage::Config config, replication::ReplicationState &repl_state);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the raw storage pointer.
|
* @brief Returns the raw storage pointer.
|
||||||
@@ -68,12 +68,12 @@ class Database {
|
|||||||
*/
|
*/
|
||||||
std::unique_ptr<storage::Storage::Accessor> Access(
|
std::unique_ptr<storage::Storage::Accessor> Access(
|
||||||
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
|
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
|
||||||
return storage_->Access(override_isolation_level, repl_state_->IsMain());
|
return storage_->Access(repl_state_->GetRole(), override_isolation_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<storage::Storage::Accessor> UniqueAccess(
|
std::unique_ptr<storage::Storage::Accessor> UniqueAccess(
|
||||||
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
|
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
|
||||||
return storage_->UniqueAccess(override_isolation_level, repl_state_->IsMain());
|
return storage_->UniqueAccess(repl_state_->GetRole(), override_isolation_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,7 +81,14 @@ class Database {
|
|||||||
*
|
*
|
||||||
* @return const std::string&
|
* @return const std::string&
|
||||||
*/
|
*/
|
||||||
const std::string &id() const { return storage_->id(); }
|
const std::string &name() const { return storage_->name(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unique storage identified (uuid)
|
||||||
|
*
|
||||||
|
* @return const utils::UUID&
|
||||||
|
*/
|
||||||
|
const utils::UUID &uuid() const { return storage_->uuid(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the storage configuration
|
* @brief Returns the storage configuration
|
||||||
@@ -95,7 +102,7 @@ class Database {
|
|||||||
*
|
*
|
||||||
* @return storage::StorageMode
|
* @return storage::StorageMode
|
||||||
*/
|
*/
|
||||||
storage::StorageMode GetStorageMode() const { return storage_->GetStorageMode(); }
|
storage::StorageMode GetStorageMode() const noexcept { return storage_->GetStorageMode(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the storage info
|
* @brief Get the storage info
|
||||||
@@ -103,9 +110,9 @@ class Database {
|
|||||||
* @param force_directory Use the configured directory, do not try to decipher the multi-db version
|
* @param force_directory Use the configured directory, do not try to decipher the multi-db version
|
||||||
* @return DatabaseInfo
|
* @return DatabaseInfo
|
||||||
*/
|
*/
|
||||||
DatabaseInfo GetInfo(bool force_directory = false) const {
|
DatabaseInfo GetInfo(bool force_directory, replication_coordination_glue::ReplicationRole replication_role) const {
|
||||||
DatabaseInfo info;
|
DatabaseInfo info;
|
||||||
info.storage_info = storage_->GetInfo(force_directory);
|
info.storage_info = storage_->GetInfo(force_directory, replication_role);
|
||||||
info.triggers = trigger_store_.GetTriggerInfo().size();
|
info.triggers = trigger_store_.GetTriggerInfo().size();
|
||||||
info.streams = streams_.GetStreamInfo().size();
|
info.streams = streams_.GetStreamInfo().size();
|
||||||
return info;
|
return info;
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ class DatabaseHandler : public Handler<Database> {
|
|||||||
* @param config Storage configuration
|
* @param config Storage configuration
|
||||||
* @return HandlerT::NewResult
|
* @return HandlerT::NewResult
|
||||||
*/
|
*/
|
||||||
HandlerT::NewResult New(std::string_view name, storage::Config config,
|
HandlerT::NewResult New(storage::Config config, replication::ReplicationState &repl_state) {
|
||||||
const replication::ReplicationState &repl_state) {
|
|
||||||
// Control that no one is using the same data directory
|
// Control that no one is using the same data directory
|
||||||
if (std::any_of(begin(), end(), [&](auto &elem) {
|
if (std::any_of(begin(), end(), [&](auto &elem) {
|
||||||
auto db_acc = elem.second.access();
|
auto db_acc = elem.second.access();
|
||||||
@@ -62,8 +61,7 @@ class DatabaseHandler : public Handler<Database> {
|
|||||||
spdlog::info("Tried to generate new storage using a claimed directory.");
|
spdlog::info("Tried to generate new storage using a claimed directory.");
|
||||||
return NewError::EXISTS;
|
return NewError::EXISTS;
|
||||||
}
|
}
|
||||||
config.name = name; // Set storage id via config
|
return HandlerT::New(std::piecewise_construct, config.salient.name, config, repl_state);
|
||||||
return HandlerT::New(std::piecewise_construct, name, config, repl_state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
449
src/dbms/dbms_handler.cpp
Normal file
449
src/dbms/dbms_handler.cpp
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
// 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/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 "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
|
||||||
|
constexpr std::string_view kLastCommitedSystemTsKey = "last_commited_system_ts"; // Key for timestamp durability
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct Durability {
|
||||||
|
enum class DurabilityVersion : uint8_t {
|
||||||
|
V0 = 0,
|
||||||
|
V1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VersionException : public utils::BasicException {
|
||||||
|
VersionException() : utils::BasicException("Unsupported durability version!") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UnknownVersionException : public utils::BasicException {
|
||||||
|
UnknownVersionException() : utils::BasicException("Unable to parse the durability version!") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MigrationException : public utils::BasicException {
|
||||||
|
MigrationException() : utils::BasicException("Failed to migrate to the current durability version!") {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static DurabilityVersion VersionCheck(std::optional<std::string_view> val) {
|
||||||
|
if (!val) {
|
||||||
|
return DurabilityVersion::V0;
|
||||||
|
}
|
||||||
|
if (val == "V1") {
|
||||||
|
return DurabilityVersion::V1;
|
||||||
|
}
|
||||||
|
throw UnknownVersionException();
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto GenKey(std::string_view name) -> std::string { return fmt::format("{}{}", kDBPrefix, name); }
|
||||||
|
|
||||||
|
static auto GenVal(utils::UUID uuid, std::filesystem::path rel_dir) {
|
||||||
|
nlohmann::json json;
|
||||||
|
json["uuid"] = uuid;
|
||||||
|
json["rel_dir"] = rel_dir;
|
||||||
|
// TODO: Serialize the configuration
|
||||||
|
return json.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Migrate(kvstore::KVStore *durability, const std::filesystem::path &root) {
|
||||||
|
const auto ver_val = durability->Get("version");
|
||||||
|
const auto ver = VersionCheck(ver_val);
|
||||||
|
|
||||||
|
std::map<std::string, std::string> to_put;
|
||||||
|
std::vector<std::string> to_delete;
|
||||||
|
|
||||||
|
// Update from V0 to V1
|
||||||
|
if (ver == DurabilityVersion::V0) {
|
||||||
|
for (const auto &[key, val] : *durability) {
|
||||||
|
if (key == "version") continue; // Reserved key
|
||||||
|
// Generate a UUID
|
||||||
|
auto const uuid = utils::UUID();
|
||||||
|
// New json values
|
||||||
|
auto new_key = GenKey(key);
|
||||||
|
auto path = root;
|
||||||
|
if (key != kDefaultDB) { // Special case for non-default DBs
|
||||||
|
// Move directory to new UUID dir
|
||||||
|
path = root / kMultiTenantDir / std::string{uuid};
|
||||||
|
std::filesystem::path old_dir(root / kMultiTenantDir / key);
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::rename(old_dir, path, ec);
|
||||||
|
MG_ASSERT(!ec, "Failed to upgrade durability: cannot move default directory.");
|
||||||
|
}
|
||||||
|
// Generate json and update value
|
||||||
|
auto new_data = GenVal(uuid, std::filesystem::relative(path, root));
|
||||||
|
to_put.emplace(std::move(new_key), std::move(new_data));
|
||||||
|
to_delete.emplace_back(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set version
|
||||||
|
durability->Put("version", "V1");
|
||||||
|
// Update to the new key-value pairs
|
||||||
|
if (!durability->PutAndDeleteMultiple(to_put, to_delete)) {
|
||||||
|
throw MigrationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FILESYSTEM MANIPULATION
|
||||||
|
*/
|
||||||
|
const auto &root = default_config_.durability.storage_directory;
|
||||||
|
storage::UpdatePaths(default_config_, root);
|
||||||
|
const auto &db_dir = default_config_.durability.storage_directory / kMultiTenantDir;
|
||||||
|
// TODO: Unify durability and wal
|
||||||
|
const auto durability_dir = db_dir / ".durability";
|
||||||
|
utils::EnsureDirOrDie(db_dir);
|
||||||
|
utils::EnsureDirOrDie(durability_dir);
|
||||||
|
durability_ = std::make_unique<kvstore::KVStore>(durability_dir);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DURABILITY
|
||||||
|
*/
|
||||||
|
// Migrate durability
|
||||||
|
Durability::Migrate(durability_.get(), root);
|
||||||
|
auto directories = std::set{std::string{kDefaultDB}};
|
||||||
|
|
||||||
|
// Recover previous databases
|
||||||
|
if (recovery_on_startup) {
|
||||||
|
auto it = durability_->begin(std::string(kDBPrefix));
|
||||||
|
auto end = durability_->end(std::string(kDBPrefix));
|
||||||
|
for (; it != end; ++it) {
|
||||||
|
const auto &[key, config_json] = *it;
|
||||||
|
const auto name = key.substr(kDBPrefix.size());
|
||||||
|
auto json = nlohmann::json::parse(config_json);
|
||||||
|
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, 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 it = durability_->begin(std::string{kDBPrefix});
|
||||||
|
auto end = durability_->end(std::string{kDBPrefix});
|
||||||
|
for (; it != end; ++it) {
|
||||||
|
const auto &[key, _] = *it;
|
||||||
|
const auto name = key.substr(kDBPrefix.size());
|
||||||
|
if (name == kDefaultDB) continue;
|
||||||
|
locked_auth->DeleteDatabase(name);
|
||||||
|
durability_->Delete(key);
|
||||||
|
}
|
||||||
|
// Delete the last timestamp
|
||||||
|
durability_->Delete(kLastCommitedSystemTsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DATABASES CLEAN UP
|
||||||
|
*/
|
||||||
|
// Clean the unused directories
|
||||||
|
for (const auto &entry : std::filesystem::directory_iterator(db_dir)) {
|
||||||
|
const auto &name = entry.path().filename().string();
|
||||||
|
if (entry.is_directory() && !name.empty() && name.front() != '.') {
|
||||||
|
auto itr = directories.find(name);
|
||||||
|
if (itr == directories.end()) {
|
||||||
|
std::error_code dummy;
|
||||||
|
std::filesystem::remove_all(entry, dummy);
|
||||||
|
} else {
|
||||||
|
directories.erase(itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEFAULT DB SETUP
|
||||||
|
*/
|
||||||
|
// 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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
return DeleteError::DEFAULT_DB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get DB config for the UUID and disk clean up
|
||||||
|
const auto conf = db_handler_.GetConfig(db_name);
|
||||||
|
if (!conf) {
|
||||||
|
return DeleteError::NON_EXISTENT;
|
||||||
|
}
|
||||||
|
const auto &storage_path = conf->durability.storage_directory;
|
||||||
|
const auto &uuid = conf->salient.uuid;
|
||||||
|
|
||||||
|
// Check if db exists
|
||||||
|
try {
|
||||||
|
// Low level handlers
|
||||||
|
if (!db_handler_.TryDelete(db_name)) {
|
||||||
|
return DeleteError::USING;
|
||||||
|
}
|
||||||
|
} catch (utils::BasicException &) {
|
||||||
|
return DeleteError::NON_EXISTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from durability list
|
||||||
|
if (durability_) durability_->Delete(Durability::GenKey(db_name));
|
||||||
|
|
||||||
|
// Delete disk storage
|
||||||
|
std::error_code ec;
|
||||||
|
(void)std::filesystem::remove_all(storage_path, ec);
|
||||||
|
if (ec) {
|
||||||
|
spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
// Save delta
|
||||||
|
if (system_transaction_) {
|
||||||
|
system_transaction_->delta.emplace(SystemTransaction::Delta::drop_database, uuid);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
DbmsHandler::DeleteResult DbmsHandler::Delete(std::string_view db_name) {
|
||||||
|
auto wr = std::lock_guard(lock_);
|
||||||
|
return Delete_(db_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
DbmsHandler::DeleteResult DbmsHandler::Delete(utils::UUID uuid) {
|
||||||
|
auto wr = std::lock_guard(lock_);
|
||||||
|
std::string db_name;
|
||||||
|
try {
|
||||||
|
const auto db = Get_(uuid);
|
||||||
|
db_name = db->name();
|
||||||
|
} catch (const UnknownDatabaseException &) {
|
||||||
|
return DeleteError::NON_EXISTENT;
|
||||||
|
}
|
||||||
|
return Delete_(db_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (system_transaction_) {
|
||||||
|
system_transaction_->delta.emplace(SystemTransaction::Delta::create_database, storage_config.salient);
|
||||||
|
}
|
||||||
|
UpdateDurability(storage_config);
|
||||||
|
return new_db.GetValue();
|
||||||
|
}
|
||||||
|
return new_db.GetError();
|
||||||
|
}
|
||||||
|
|
||||||
|
DbmsHandler::DeleteResult DbmsHandler::Delete_(std::string_view db_name) {
|
||||||
|
if (db_name == kDefaultDB) {
|
||||||
|
// MSG cannot delete the default db
|
||||||
|
return DeleteError::DEFAULT_DB;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto storage_path = StorageDir_(db_name);
|
||||||
|
if (!storage_path) return DeleteError::NON_EXISTENT;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto db = db_handler_.Get(db_name);
|
||||||
|
if (!db) return DeleteError::NON_EXISTENT;
|
||||||
|
// TODO: ATM we assume REPLICA won't have streams,
|
||||||
|
// this is a best effort approach just in case they do
|
||||||
|
// there is still subtle data race we stream manipulation
|
||||||
|
// can occur while we are dropping the database
|
||||||
|
db->prepare_for_deletion();
|
||||||
|
auto &database = *db->get();
|
||||||
|
database.streams()->StopAll();
|
||||||
|
database.streams()->DropAll();
|
||||||
|
database.thread_pool()->Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from durability list
|
||||||
|
if (durability_) durability_->Delete(Durability::GenKey(db_name));
|
||||||
|
|
||||||
|
// Check if db exists
|
||||||
|
// Low level handlers
|
||||||
|
db_handler_.DeferDelete(db_name, [storage_path = *storage_path, db_name = std::string{db_name}]() {
|
||||||
|
// Delete disk storage
|
||||||
|
std::error_code ec;
|
||||||
|
(void)std::filesystem::remove_all(storage_path, ec);
|
||||||
|
if (ec) {
|
||||||
|
spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {}; // Success
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbmsHandler::UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir) {
|
||||||
|
if (!durability_) return;
|
||||||
|
// Save database in a list of active databases
|
||||||
|
const auto &key = Durability::GenKey(config.salient.name);
|
||||||
|
if (rel_dir == std::nullopt)
|
||||||
|
rel_dir =
|
||||||
|
std::filesystem::relative(config.durability.storage_directory, default_config_.durability.storage_directory);
|
||||||
|
const auto &val = Durability::GenVal(config.salient.uuid, *rel_dir);
|
||||||
|
durability_->Put(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -12,32 +12,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <concepts>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <ostream>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <unordered_map>
|
#include <utility>
|
||||||
|
|
||||||
#include "auth/auth.hpp"
|
#include "auth/auth.hpp"
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
#include "dbms/database.hpp"
|
#include "dbms/database.hpp"
|
||||||
|
#include "dbms/inmemory/replication_handlers.hpp"
|
||||||
|
#include "dbms/replication_handler.hpp"
|
||||||
|
#include "kvstore/kvstore.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 "utils/thread_pool.hpp"
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
|
#include "coordination/coordinator_state.hpp"
|
||||||
#include "dbms/database_handler.hpp"
|
#include "dbms/database_handler.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#include "dbms/transaction.hpp"
|
||||||
#include "global.hpp"
|
#include "global.hpp"
|
||||||
#include "query/config.hpp"
|
#include "query/config.hpp"
|
||||||
#include "query/interpreter_context.hpp"
|
#include "query/interpreter_context.hpp"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "storage/v2/durability/durability.hpp"
|
|
||||||
#include "storage/v2/durability/paths.hpp"
|
|
||||||
#include "storage/v2/isolation_level.hpp"
|
#include "storage/v2/isolation_level.hpp"
|
||||||
#include "utils/exceptions.hpp"
|
|
||||||
#include "utils/file.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
#include "utils/logging.hpp"
|
||||||
#include "utils/result.hpp"
|
#include "utils/result.hpp"
|
||||||
#include "utils/rw_lock.hpp"
|
#include "utils/rw_lock.hpp"
|
||||||
@@ -46,6 +51,11 @@
|
|||||||
|
|
||||||
namespace memgraph::dbms {
|
namespace memgraph::dbms {
|
||||||
|
|
||||||
|
enum class AllSyncReplicaStatus {
|
||||||
|
AllCommitsConfirmed,
|
||||||
|
SomeCommitsUnconfirmed,
|
||||||
|
};
|
||||||
|
|
||||||
struct Statistics {
|
struct Statistics {
|
||||||
uint64_t num_vertex; //!< Sum of vertexes in every database
|
uint64_t num_vertex; //!< Sum of vertexes in every database
|
||||||
uint64_t num_edges; //!< Sum of edges in every database
|
uint64_t num_edges; //!< Sum of edges in every database
|
||||||
@@ -100,54 +110,25 @@ class DbmsHandler {
|
|||||||
* @param configs storage configuration
|
* @param configs storage configuration
|
||||||
* @param auth pointer to the global authenticator
|
* @param auth pointer to the global authenticator
|
||||||
* @param recovery_on_startup restore databases (and its content) and authentication data
|
* @param recovery_on_startup restore databases (and its content) and authentication data
|
||||||
* @param delete_on_drop when dropping delete any associated directories on disk
|
|
||||||
*/
|
*/
|
||||||
DbmsHandler(storage::Config config, const replication::ReplicationState &repl_state, auto *auth,
|
DbmsHandler(storage::Config config,
|
||||||
bool recovery_on_startup, bool delete_on_drop)
|
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||||
: lock_{utils::RWLock::Priority::READ},
|
bool recovery_on_startup); // TODO If more arguments are added use a config struct
|
||||||
default_config_{std::move(config)},
|
|
||||||
repl_state_(repl_state),
|
|
||||||
delete_on_drop_(delete_on_drop) {
|
|
||||||
// TODO: Decouple storage config from dbms config
|
|
||||||
// TODO: Save individual db configs inside the kvstore and restore from there
|
|
||||||
storage::UpdatePaths(default_config_, default_config_.durability.storage_directory / "databases");
|
|
||||||
const auto &db_dir = default_config_.durability.storage_directory;
|
|
||||||
const auto durability_dir = db_dir / ".durability";
|
|
||||||
utils::EnsureDirOrDie(db_dir);
|
|
||||||
utils::EnsureDirOrDie(durability_dir);
|
|
||||||
durability_ = std::make_unique<kvstore::KVStore>(durability_dir);
|
|
||||||
|
|
||||||
// Generate the default database
|
|
||||||
MG_ASSERT(!NewDefault_().HasError(), "Failed while creating the default DB.");
|
|
||||||
// Recover previous databases
|
|
||||||
if (recovery_on_startup) {
|
|
||||||
for (const auto &[name, _] : *durability_) {
|
|
||||||
if (name == kDefaultDB) continue; // Already set
|
|
||||||
spdlog::info("Restoring database {}.", name);
|
|
||||||
MG_ASSERT(!New_(name).HasError(), "Failed while creating database {}.", name);
|
|
||||||
spdlog::info("Database {} restored.", name);
|
|
||||||
}
|
|
||||||
} else { // Clear databases from the durability list and auth
|
|
||||||
auto locked_auth = auth->Lock();
|
|
||||||
for (const auto &[name, _] : *durability_) {
|
|
||||||
if (name == kDefaultDB) continue;
|
|
||||||
locked_auth->DeleteDatabase(name);
|
|
||||||
durability_->Delete(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
/**
|
/**
|
||||||
* @brief Initialize the handler. A single database is supported in community edition.
|
* @brief Initialize the handler. A single database is supported in community edition.
|
||||||
*
|
*
|
||||||
* @param configs storage configuration
|
* @param configs storage configuration
|
||||||
*/
|
*/
|
||||||
DbmsHandler(storage::Config config, const replication::ReplicationState &repl_state)
|
DbmsHandler(storage::Config config)
|
||||||
: db_gatekeeper_{[&] {
|
: repl_state_{ReplicationStateRootPath(config)},
|
||||||
config.name = kDefaultDB;
|
db_gatekeeper_{[&] {
|
||||||
|
config.salient.name = kDefaultDB;
|
||||||
return std::move(config);
|
return std::move(config);
|
||||||
}(),
|
}(),
|
||||||
repl_state} {}
|
repl_state_} {
|
||||||
|
RecoverReplication(Get());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
@@ -159,9 +140,56 @@ class DbmsHandler {
|
|||||||
*/
|
*/
|
||||||
NewResultT New(const std::string &name) {
|
NewResultT New(const std::string &name) {
|
||||||
std::lock_guard<LockT> wr(lock_);
|
std::lock_guard<LockT> wr(lock_);
|
||||||
return New_(name, name);
|
const auto uuid = utils::UUID{};
|
||||||
|
return New_(name, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create new if name/uuid do not match any database. Drop and recreate if database already present.
|
||||||
|
* @note Default database is not dropped, only its UUID is updated and only if the database is clean.
|
||||||
|
*
|
||||||
|
* @param config desired salient config
|
||||||
|
* @return NewResultT context on success, error on failure
|
||||||
|
*/
|
||||||
|
NewResultT Update(const storage::SalientConfig &config) {
|
||||||
|
std::lock_guard<LockT> wr(lock_);
|
||||||
|
auto new_db = New_(config);
|
||||||
|
if (new_db.HasValue() || new_db.GetError() != NewError::EXISTS) {
|
||||||
|
// NOTE: If db already exists we retry below
|
||||||
|
return new_db;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::debug("Trying to create db '{}' on replica which already exists.", config.name);
|
||||||
|
|
||||||
|
auto db = Get_(config.name);
|
||||||
|
if (db->uuid() == config.uuid) { // Same db
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::debug("Different UUIDs");
|
||||||
|
|
||||||
|
// TODO: Fix this hack
|
||||||
|
if (config.name == kDefaultDB) {
|
||||||
|
if (db->storage()->repl_storage_state_.last_commit_timestamp_ != storage::kTimestampInitialId) {
|
||||||
|
spdlog::debug("Default storage is not clean, cannot update UUID...");
|
||||||
|
return NewError::GENERIC; // Update error
|
||||||
|
}
|
||||||
|
spdlog::debug("Update default db's UUID");
|
||||||
|
// Default db cannot be deleted and remade, have to just update the UUID
|
||||||
|
db->storage()->config_.salient.uuid = config.uuid;
|
||||||
|
UpdateDurability(db->storage()->config_, ".");
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::debug("Drop database and recreate with the correct UUID");
|
||||||
|
// Defer drop
|
||||||
|
(void)Delete_(db->name());
|
||||||
|
// Second attempt
|
||||||
|
return New_(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the context associated with the "name" database
|
* @brief Get the context associated with the "name" database
|
||||||
*
|
*
|
||||||
@@ -173,6 +201,19 @@ class DbmsHandler {
|
|||||||
std::shared_lock<LockT> rd(lock_);
|
std::shared_lock<LockT> rd(lock_);
|
||||||
return Get_(name);
|
return Get_(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the context associated with the UUID database
|
||||||
|
*
|
||||||
|
* @param uuid
|
||||||
|
* @return DatabaseAccess
|
||||||
|
* @throw UnknownDatabaseException if database not found
|
||||||
|
*/
|
||||||
|
DatabaseAccess Get(const utils::UUID &uuid) {
|
||||||
|
std::shared_lock<LockT> rd(lock_);
|
||||||
|
return Get_(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
/**
|
/**
|
||||||
* @brief Get the context associated with the default database
|
* @brief Get the context associated with the default database
|
||||||
@@ -188,50 +229,28 @@ class DbmsHandler {
|
|||||||
|
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
/**
|
/**
|
||||||
* @brief Delete database.
|
* @brief Attempt to delete database.
|
||||||
*
|
*
|
||||||
* @param db_name database name
|
* @param db_name database name
|
||||||
* @return DeleteResult error on failure
|
* @return DeleteResult error on failure
|
||||||
*/
|
*/
|
||||||
DeleteResult Delete(const std::string &db_name) {
|
DeleteResult TryDelete(std::string_view db_name);
|
||||||
std::lock_guard<LockT> wr(lock_);
|
|
||||||
if (db_name == kDefaultDB) {
|
|
||||||
// MSG cannot delete the default db
|
|
||||||
return DeleteError::DEFAULT_DB;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto storage_path = StorageDir_(db_name);
|
/**
|
||||||
if (!storage_path) return DeleteError::NON_EXISTENT;
|
* @brief Delete or defer deletion of database.
|
||||||
|
*
|
||||||
|
* @param db_name database name
|
||||||
|
* @return DeleteResult error on failure
|
||||||
|
*/
|
||||||
|
DeleteResult Delete(std::string_view db_name);
|
||||||
|
|
||||||
// Check if db exists
|
/**
|
||||||
try {
|
* @brief Delete or defer deletion of database.
|
||||||
// Low level handlers
|
*
|
||||||
if (!db_handler_.Delete(db_name)) {
|
* @param uuid database UUID
|
||||||
return DeleteError::USING;
|
* @return DeleteResult error on failure
|
||||||
}
|
*/
|
||||||
} catch (utils::BasicException &) {
|
DeleteResult Delete(utils::UUID uuid);
|
||||||
return DeleteError::NON_EXISTENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from durability list
|
|
||||||
if (durability_) durability_->Delete(db_name);
|
|
||||||
|
|
||||||
// Delete disk storage
|
|
||||||
if (delete_on_drop_) {
|
|
||||||
std::error_code ec;
|
|
||||||
(void)std::filesystem::remove_all(*storage_path, ec);
|
|
||||||
if (ec) {
|
|
||||||
spdlog::error("Failed to clean disk while deleting database \"{}\".", db_name);
|
|
||||||
defunct_dbs_.emplace(db_name);
|
|
||||||
return DeleteError::DISK_FAIL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete from defunct_dbs_ (in case a second delete call was successful)
|
|
||||||
defunct_dbs_.erase(db_name);
|
|
||||||
|
|
||||||
return {}; // Success
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,16 +263,27 @@ class DbmsHandler {
|
|||||||
std::shared_lock<LockT> rd(lock_);
|
std::shared_lock<LockT> rd(lock_);
|
||||||
return db_handler_.All();
|
return db_handler_.All();
|
||||||
#else
|
#else
|
||||||
return {db_gatekeeper_.access()->get()->id()};
|
return {db_gatekeeper_.access()->get()->name()};
|
||||||
#endif
|
#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.
|
* @brief Return the statistics all databases.
|
||||||
*
|
*
|
||||||
* @return Statistics
|
* @return Statistics
|
||||||
*/
|
*/
|
||||||
Statistics Stats() {
|
Statistics Stats() {
|
||||||
|
auto const replication_role = repl_state_.GetRole();
|
||||||
Statistics stats{};
|
Statistics stats{};
|
||||||
// TODO: Handle overflow?
|
// TODO: Handle overflow?
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
@@ -266,7 +296,7 @@ class DbmsHandler {
|
|||||||
auto db_acc_opt = db_gk.access();
|
auto db_acc_opt = db_gk.access();
|
||||||
if (db_acc_opt) {
|
if (db_acc_opt) {
|
||||||
auto &db_acc = *db_acc_opt;
|
auto &db_acc = *db_acc_opt;
|
||||||
const auto &info = db_acc->GetInfo();
|
const auto &info = db_acc->GetInfo(false, replication_role);
|
||||||
const auto &storage_info = info.storage_info;
|
const auto &storage_info = info.storage_info;
|
||||||
stats.num_vertex += storage_info.vertex_count;
|
stats.num_vertex += storage_info.vertex_count;
|
||||||
stats.num_edges += storage_info.edge_count;
|
stats.num_edges += storage_info.edge_count;
|
||||||
@@ -290,6 +320,7 @@ class DbmsHandler {
|
|||||||
* @return std::vector<DatabaseInfo>
|
* @return std::vector<DatabaseInfo>
|
||||||
*/
|
*/
|
||||||
std::vector<DatabaseInfo> Info() {
|
std::vector<DatabaseInfo> Info() {
|
||||||
|
auto const replication_role = repl_state_.GetRole();
|
||||||
std::vector<DatabaseInfo> res;
|
std::vector<DatabaseInfo> res;
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
std::shared_lock<LockT> rd(lock_);
|
std::shared_lock<LockT> rd(lock_);
|
||||||
@@ -302,7 +333,7 @@ class DbmsHandler {
|
|||||||
auto db_acc_opt = db_gk.access();
|
auto db_acc_opt = db_gk.access();
|
||||||
if (db_acc_opt) {
|
if (db_acc_opt) {
|
||||||
auto &db_acc = *db_acc_opt;
|
auto &db_acc = *db_acc_opt;
|
||||||
res.push_back(db_acc->GetInfo());
|
res.push_back(db_acc->GetInfo(false, replication_role));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@@ -325,7 +356,7 @@ class DbmsHandler {
|
|||||||
auto db_acc_opt = db_gk.access();
|
auto db_acc_opt = db_gk.access();
|
||||||
if (db_acc_opt) {
|
if (db_acc_opt) {
|
||||||
auto &db_acc = *db_acc_opt;
|
auto &db_acc = *db_acc_opt;
|
||||||
spdlog::debug("Restoring trigger for database \"{}\"", db_acc->id());
|
spdlog::debug("Restoring trigger for database \"{}\"", db_acc->name());
|
||||||
auto storage_accessor = db_acc->Access();
|
auto storage_accessor = db_acc->Access();
|
||||||
auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
|
auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
|
||||||
db_acc->trigger_store()->RestoreTriggers(&ic->ast_cache, &dba, ic->config.query, ic->auth_checker);
|
db_acc->trigger_store()->RestoreTriggers(&ic->ast_cache, &dba, ic->config.query, ic->auth_checker);
|
||||||
@@ -350,7 +381,7 @@ class DbmsHandler {
|
|||||||
auto db_acc = db_gk.access();
|
auto db_acc = db_gk.access();
|
||||||
if (db_acc) {
|
if (db_acc) {
|
||||||
auto *db = db_acc->get();
|
auto *db = db_acc->get();
|
||||||
spdlog::debug("Restoring streams for database \"{}\"", db->id());
|
spdlog::debug("Restoring streams for database \"{}\"", db->name());
|
||||||
db->streams()->RestoreStreams(*db_acc, ic);
|
db->streams()->RestoreStreams(*db_acc, ic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,7 +392,7 @@ class DbmsHandler {
|
|||||||
*
|
*
|
||||||
* @param f
|
* @param f
|
||||||
*/
|
*/
|
||||||
void ForEach(auto f) {
|
void ForEach(std::invocable<DatabaseAccess> auto f) {
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
std::shared_lock<LockT> rd(lock_);
|
std::shared_lock<LockT> rd(lock_);
|
||||||
for (auto &[_, db_gk] : db_handler_) {
|
for (auto &[_, db_gk] : db_handler_) {
|
||||||
@@ -371,33 +402,103 @@ class DbmsHandler {
|
|||||||
#endif
|
#endif
|
||||||
auto db_acc = db_gk.access();
|
auto db_acc = db_gk.access();
|
||||||
if (db_acc) { // This isn't an error, just a defunct db
|
if (db_acc) { // This isn't an error, just a defunct db
|
||||||
f(db_acc->get());
|
f(*db_acc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void NewSystemTransaction() {
|
||||||
* @brief todo
|
DMG_ASSERT(!system_transaction_, "Already running a system transaction");
|
||||||
*
|
system_transaction_.emplace(++system_timestamp_);
|
||||||
* @param f
|
}
|
||||||
*/
|
|
||||||
void ForOne(auto f) {
|
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
|
#ifdef MG_ENTERPRISE
|
||||||
std::shared_lock<LockT> rd(lock_);
|
// When being called by intepreter no need to gain lock, it should already be under a system transaction
|
||||||
for (auto &[_, db_gk] : db_handler_) {
|
// But concurrently the FrequentCheck is running and will need to lock before reading last_commited_system_timestamp_
|
||||||
auto db_acc = db_gk.access();
|
template <bool REQUIRE_LOCK = false>
|
||||||
if (db_acc) { // This isn't an error, just a defunct db
|
void SystemRestore(replication::ReplicationClient &client) {
|
||||||
if (f(db_acc->get())) break; // Run until the first successful one
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
{
|
// Successfully recovered
|
||||||
auto db_acc = db_gatekeeper_.access();
|
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::READY; });
|
||||||
MG_ASSERT(db_acc, "Should always have the database");
|
|
||||||
f(db_acc->get());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
@@ -407,7 +508,7 @@ class DbmsHandler {
|
|||||||
* @param name Database name
|
* @param name Database name
|
||||||
* @return std::optional<std::filesystem::path>
|
* @return std::optional<std::filesystem::path>
|
||||||
*/
|
*/
|
||||||
std::optional<std::filesystem::path> StorageDir_(const std::string &name) {
|
std::optional<std::filesystem::path> StorageDir_(std::string_view name) {
|
||||||
const auto conf = db_handler_.GetConfig(name);
|
const auto conf = db_handler_.GetConfig(name);
|
||||||
if (conf) {
|
if (conf) {
|
||||||
return conf->durability.storage_directory;
|
return conf->durability.storage_directory;
|
||||||
@@ -420,105 +521,108 @@ class DbmsHandler {
|
|||||||
* @brief Create a new Database associated with the "name" database
|
* @brief Create a new Database associated with the "name" database
|
||||||
*
|
*
|
||||||
* @param name name of the database
|
* @param name name of the database
|
||||||
|
* @param uuid undelying RocksDB directory
|
||||||
* @return NewResultT context on success, error on failure
|
* @return NewResultT context on success, error on failure
|
||||||
*/
|
*/
|
||||||
NewResultT New_(const std::string &name) { return New_(name, name); }
|
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;
|
||||||
|
spdlog::debug("Creating database '{}' - '{}'", name, std::string{uuid});
|
||||||
|
if (rel_dir) {
|
||||||
|
storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / *rel_dir);
|
||||||
|
} else {
|
||||||
|
storage::UpdatePaths(config_copy,
|
||||||
|
default_config_.durability.storage_directory / kMultiTenantDir / std::string{uuid});
|
||||||
|
}
|
||||||
|
return New_(std::move(config_copy));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a new Database associated with the "name" database
|
* @brief Create a new Database using the passed configuration
|
||||||
*
|
*
|
||||||
* @param name name of the database
|
* @param config configuration to be used
|
||||||
* @param storage_subdir undelying RocksDB directory
|
|
||||||
* @return NewResultT context on success, error on failure
|
* @return NewResultT context on success, error on failure
|
||||||
*/
|
*/
|
||||||
NewResultT New_(const std::string &name, std::filesystem::path storage_subdir) {
|
NewResultT New_(const storage::SalientConfig &config) {
|
||||||
auto config_copy = default_config_;
|
auto config_copy = default_config_;
|
||||||
storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / storage_subdir);
|
config_copy.salient = config; // name, uuid, mode, etc
|
||||||
return New_(name, config_copy);
|
UpdatePaths(config_copy, config_copy.durability.storage_directory / kMultiTenantDir / std::string{config.uuid});
|
||||||
|
return New_(std::move(config_copy));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a new Database associated with the "name" database
|
* @brief Create a new Database associated with the "name" database
|
||||||
*
|
*
|
||||||
* @param name name of the database
|
|
||||||
* @param storage_config storage configuration
|
* @param storage_config storage configuration
|
||||||
* @return NewResultT context on success, error on failure
|
* @return NewResultT context on success, error on failure
|
||||||
*/
|
*/
|
||||||
NewResultT New_(const std::string &name, storage::Config &storage_config) {
|
NewResultT New_(storage::Config storage_config);
|
||||||
if (defunct_dbs_.contains(name)) {
|
|
||||||
spdlog::warn("Failed to generate database due to the unknown state of the previously defunct database \"{}\".",
|
|
||||||
name);
|
|
||||||
return NewError::DEFUNCT;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto new_db = db_handler_.New(name, storage_config, repl_state_);
|
// TODO: new overload of Delete_ with DatabaseAccess
|
||||||
if (new_db.HasValue()) {
|
DeleteResult Delete_(std::string_view db_name);
|
||||||
// Success
|
|
||||||
if (durability_) durability_->Put(name, "ok"); // TODO: Serialize the configuration?
|
|
||||||
return new_db.GetValue();
|
|
||||||
}
|
|
||||||
return new_db.GetError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a new Database associated with the default database
|
* @brief Create a new Database associated with the default database
|
||||||
*
|
*
|
||||||
* @return NewResultT context on success, error on failure
|
* @return NewResultT context on success, error on failure
|
||||||
*/
|
*/
|
||||||
NewResultT NewDefault_() {
|
void SetupDefault_() {
|
||||||
// Create the default DB in the root (this is how it was done pre multi-tenancy)
|
try {
|
||||||
auto res = New_(kDefaultDB, "..");
|
Get(kDefaultDB);
|
||||||
if (res.HasValue()) {
|
} catch (const UnknownDatabaseException &) {
|
||||||
// For back-compatibility...
|
// No default DB restored, create it
|
||||||
// Recreate the dbms layout for the default db and symlink to the root
|
MG_ASSERT(New_(kDefaultDB, {/* random UUID */}, ".").HasValue(), "Failed while creating the default database");
|
||||||
const auto dir = StorageDir_(kDefaultDB);
|
}
|
||||||
MG_ASSERT(dir, "Failed to find storage path.");
|
|
||||||
const auto main_dir = *dir / "databases" / kDefaultDB;
|
|
||||||
|
|
||||||
if (!std::filesystem::exists(main_dir)) {
|
// For back-compatibility...
|
||||||
std::filesystem::create_directory(main_dir);
|
// Recreate the dbms layout for the default db and symlink to the root
|
||||||
}
|
const auto dir = StorageDir_(kDefaultDB);
|
||||||
|
MG_ASSERT(dir, "Failed to find storage path.");
|
||||||
|
const auto main_dir = *dir / kMultiTenantDir / kDefaultDB;
|
||||||
|
|
||||||
// Force link on-disk directories
|
if (!std::filesystem::exists(main_dir)) {
|
||||||
const auto conf = db_handler_.GetConfig(kDefaultDB);
|
std::filesystem::create_directory(main_dir);
|
||||||
MG_ASSERT(conf, "No configuration for the default database.");
|
}
|
||||||
const auto &tmp_conf = conf->disk;
|
|
||||||
std::vector<std::filesystem::path> to_link{
|
|
||||||
tmp_conf.main_storage_directory, tmp_conf.label_index_directory,
|
|
||||||
tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory,
|
|
||||||
tmp_conf.name_id_mapper_directory, tmp_conf.id_name_mapper_directory,
|
|
||||||
tmp_conf.durability_directory, tmp_conf.wal_directory,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add in-memory paths
|
// Force link on-disk directories
|
||||||
// Some directories are redundant (skip those)
|
const auto conf = db_handler_.GetConfig(kDefaultDB);
|
||||||
const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"};
|
MG_ASSERT(conf, "No configuration for the default database.");
|
||||||
for (auto const &item : std::filesystem::directory_iterator{*dir}) {
|
const auto &tmp_conf = conf->disk;
|
||||||
const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path());
|
std::vector<std::filesystem::path> to_link{
|
||||||
if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue;
|
tmp_conf.main_storage_directory, tmp_conf.label_index_directory,
|
||||||
to_link.push_back(item.path());
|
tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory,
|
||||||
}
|
tmp_conf.name_id_mapper_directory, tmp_conf.id_name_mapper_directory,
|
||||||
|
tmp_conf.durability_directory, tmp_conf.wal_directory,
|
||||||
|
};
|
||||||
|
|
||||||
// Symlink to root dir
|
// Add in-memory paths
|
||||||
for (auto const &item : to_link) {
|
// Some directories are redundant (skip those)
|
||||||
const auto dir_name = std::filesystem::relative(item, item.parent_path());
|
const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"};
|
||||||
const auto link = main_dir / dir_name;
|
for (auto const &item : std::filesystem::directory_iterator{*dir}) {
|
||||||
const auto to = std::filesystem::relative(item, main_dir);
|
const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path());
|
||||||
if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) {
|
if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue;
|
||||||
std::filesystem::create_directory_symlink(to, link);
|
to_link.push_back(item.path());
|
||||||
} else { // Check existing link
|
}
|
||||||
std::error_code ec;
|
|
||||||
const auto test_link = std::filesystem::read_symlink(link, ec);
|
// Symlink to root dir
|
||||||
if (ec || test_link != to) {
|
for (auto const &item : to_link) {
|
||||||
MG_ASSERT(false,
|
const auto dir_name = std::filesystem::relative(item, item.parent_path());
|
||||||
"Memgraph storage directory incompatible with new version.\n"
|
const auto link = main_dir / dir_name;
|
||||||
"Please use a clean directory or remove \"{}\" and try again.",
|
const auto to = std::filesystem::relative(item, main_dir);
|
||||||
link.string());
|
if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) {
|
||||||
}
|
std::filesystem::create_directory_symlink(to, link);
|
||||||
|
} else { // Check existing link
|
||||||
|
std::error_code ec;
|
||||||
|
const auto test_link = std::filesystem::read_symlink(link, ec);
|
||||||
|
if (ec || test_link != to) {
|
||||||
|
MG_ASSERT(false,
|
||||||
|
"Memgraph storage directory incompatible with new version.\n"
|
||||||
|
"Please use a clean directory or remove \"{}\" and try again.",
|
||||||
|
link.string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -536,16 +640,57 @@ class DbmsHandler {
|
|||||||
throw UnknownDatabaseException("Tried to retrieve an unknown database \"{}\".", name);
|
throw UnknownDatabaseException("Tried to retrieve an unknown database \"{}\".", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutable LockT lock_; //!< protective lock
|
/**
|
||||||
storage::Config default_config_; //!< Storage configuration used when creating new databases
|
* @brief Get the context associated with the UUID database
|
||||||
const replication::ReplicationState &repl_state_; //!< Global replication state
|
*
|
||||||
DatabaseHandler db_handler_; //!< multi-tenancy storage handler
|
* @param uuid
|
||||||
std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation)
|
* @return DatabaseAccess
|
||||||
bool delete_on_drop_; //!< Flag defining if dropping storage also deletes its directory
|
* @throw UnknownDatabaseException if database not found
|
||||||
std::set<std::string> defunct_dbs_; //!< Databases that are in an unknown state due to various failures
|
*/
|
||||||
#else
|
DatabaseAccess Get_(const utils::UUID &uuid) {
|
||||||
|
// TODO Speed up
|
||||||
|
for (auto &[_, db_gk] : db_handler_) {
|
||||||
|
auto acc = db_gk.access();
|
||||||
|
if (acc->get()->uuid() == uuid) {
|
||||||
|
return std::move(*acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw UnknownDatabaseException("Tried to retrieve an unknown database with UUID \"{}\".", std::string{uuid});
|
||||||
|
}
|
||||||
|
#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
|
||||||
|
std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation)
|
||||||
|
coordination::CoordinatorState coordinator_state_; //!< Replication coordinator
|
||||||
|
#endif
|
||||||
|
// TODO: Make an api
|
||||||
|
public:
|
||||||
|
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
|
mutable utils::Gatekeeper<Database> db_gatekeeper_; //!< Single databases gatekeeper
|
||||||
#endif
|
#endif
|
||||||
};
|
}; // namespace memgraph::dbms
|
||||||
|
|
||||||
} // namespace memgraph::dbms
|
} // namespace memgraph::dbms
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "utils/exceptions.hpp"
|
#include "utils/exceptions.hpp"
|
||||||
#include "utils/gatekeeper.hpp"
|
#include "utils/gatekeeper.hpp"
|
||||||
#include "utils/result.hpp"
|
#include "utils/result.hpp"
|
||||||
|
#include "utils/thread_pool.hpp"
|
||||||
|
|
||||||
namespace memgraph::dbms {
|
namespace memgraph::dbms {
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ class Handler {
|
|||||||
* @brief Empty Handler constructor.
|
* @brief Empty Handler constructor.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
Handler() {}
|
Handler() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generate a new context and corresponding configuration.
|
* @brief Generate a new context and corresponding configuration.
|
||||||
@@ -82,7 +83,7 @@ class Handler {
|
|||||||
* @return true on success
|
* @return true on success
|
||||||
* @throw BasicException
|
* @throw BasicException
|
||||||
*/
|
*/
|
||||||
bool Delete(const std::string &name) {
|
bool TryDelete(std::string_view name) {
|
||||||
if (auto itr = items_.find(name); itr != items_.end()) {
|
if (auto itr = items_.find(name); itr != items_.end()) {
|
||||||
auto db_acc = itr->second.access();
|
auto db_acc = itr->second.access();
|
||||||
if (db_acc && db_acc->try_delete()) {
|
if (db_acc && db_acc->try_delete()) {
|
||||||
@@ -92,9 +93,42 @@ class Handler {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// TODO: Change to return enum
|
||||||
throw utils::BasicException("Unknown item \"{}\".", name);
|
throw utils::BasicException("Unknown item \"{}\".", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete or defunct the context associated with the name.
|
||||||
|
*
|
||||||
|
* @param name Name associated with the context to delete
|
||||||
|
* @param post_delete_func What to do after deletion has happened
|
||||||
|
*/
|
||||||
|
template <typename Func>
|
||||||
|
void DeferDelete(std::string_view name, Func &&post_delete_func) {
|
||||||
|
auto itr = items_.find(name);
|
||||||
|
if (itr == items_.end()) return;
|
||||||
|
|
||||||
|
auto db_acc = itr->second.access();
|
||||||
|
if (!db_acc) return;
|
||||||
|
|
||||||
|
if (db_acc->try_delete()) {
|
||||||
|
// Delete the database now
|
||||||
|
db_acc->reset();
|
||||||
|
post_delete_func();
|
||||||
|
} else {
|
||||||
|
// Defer deletion
|
||||||
|
db_acc->reset();
|
||||||
|
// TODO: Make sure this shuts down correctly
|
||||||
|
auto task = [gk = std::move(itr->second), post_delete_func = std::forward<Func>(post_delete_func)]() mutable {
|
||||||
|
gk.~Gatekeeper<T>();
|
||||||
|
post_delete_func();
|
||||||
|
};
|
||||||
|
defer_pool_.AddTask(utils::CopyMovableFunctionWrapper{std::move(task)});
|
||||||
|
}
|
||||||
|
// In any case remove from handled map
|
||||||
|
items_.erase(itr);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if a name is already used.
|
* @brief Check if a name is already used.
|
||||||
*
|
*
|
||||||
@@ -120,6 +154,7 @@ class Handler {
|
|||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, utils::Gatekeeper<T>, string_hash, std::equal_to<>>
|
std::unordered_map<std::string, utils::Gatekeeper<T>, string_hash, std::equal_to<>>
|
||||||
items_; //!< map to all active items
|
items_; //!< map to all active items
|
||||||
|
utils::ThreadPool defer_pool_{1};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace memgraph::dbms
|
} // namespace memgraph::dbms
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
// licenses/APL.txt.
|
// licenses/APL.txt.
|
||||||
|
|
||||||
#include "dbms/inmemory/replication_handlers.hpp"
|
#include "dbms/inmemory/replication_handlers.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <optional>
|
||||||
#include "dbms/constants.hpp"
|
#include "dbms/constants.hpp"
|
||||||
#include "dbms/dbms_handler.hpp"
|
#include "dbms/dbms_handler.hpp"
|
||||||
#include "replication/replication_server.hpp"
|
#include "replication/replication_server.hpp"
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
#include "storage/v2/inmemory/storage.hpp"
|
#include "storage/v2/inmemory/storage.hpp"
|
||||||
#include "storage/v2/inmemory/unique_constraints.hpp"
|
#include "storage/v2/inmemory/unique_constraints.hpp"
|
||||||
|
|
||||||
|
using memgraph::replication_coordination_glue::ReplicationRole;
|
||||||
using memgraph::storage::Delta;
|
using memgraph::storage::Delta;
|
||||||
using memgraph::storage::EdgeAccessor;
|
using memgraph::storage::EdgeAccessor;
|
||||||
using memgraph::storage::EdgeRef;
|
using memgraph::storage::EdgeRef;
|
||||||
@@ -47,29 +50,29 @@ std::pair<uint64_t, WalDeltaData> ReadDelta(storage::durability::BaseDecoder *de
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, std::string_view db_name) {
|
std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, const utils::UUID &uuid) {
|
||||||
try {
|
try {
|
||||||
#ifdef MG_ENTERPRISE
|
#ifdef MG_ENTERPRISE
|
||||||
auto acc = dbms_handler->Get(db_name);
|
auto acc = dbms_handler->Get(uuid);
|
||||||
#else
|
|
||||||
if (db_name != dbms::kDefaultDB) {
|
|
||||||
spdlog::warn("Trying to replicate a non-default database on a community replica.");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
auto acc = dbms_handler->Get();
|
|
||||||
#endif
|
|
||||||
if (!acc) {
|
if (!acc) {
|
||||||
spdlog::error("Failed to get access to ", db_name);
|
spdlog::error("Failed to get access to UUID ", std::string{uuid});
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
auto acc = dbms_handler->Get();
|
||||||
|
if (!acc) {
|
||||||
|
spdlog::warn("Failed to get access to the default db.");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
auto *inmem_storage = dynamic_cast<storage::InMemoryStorage *>(acc.get()->storage());
|
auto *inmem_storage = dynamic_cast<storage::InMemoryStorage *>(acc.get()->storage());
|
||||||
if (!inmem_storage || inmem_storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
|
if (!inmem_storage || inmem_storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
|
||||||
spdlog::error("Database \"{}\" is not IN_MEMORY_TRANSACTIONAL.", db_name);
|
spdlog::error("Database is not IN_MEMORY_TRANSACTIONAL.");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return std::optional{std::move(acc)};
|
return std::optional{std::move(acc)};
|
||||||
} catch (const dbms::UnknownDatabaseException &e) {
|
} catch (const dbms::UnknownDatabaseException &e) {
|
||||||
spdlog::warn("No database \"{}\" on replica!", db_name);
|
spdlog::warn("No database with UUID \"{}\" on replica!", std::string{uuid});
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,13 +110,16 @@ void InMemoryReplicationHandlers::HeartbeatHandler(dbms::DbmsHandler *dbms_handl
|
|||||||
slk::Builder *res_builder) {
|
slk::Builder *res_builder) {
|
||||||
storage::replication::HeartbeatReq req;
|
storage::replication::HeartbeatReq req;
|
||||||
slk::Load(&req, req_reader);
|
slk::Load(&req, req_reader);
|
||||||
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||||
if (!db_acc) return;
|
if (!db_acc) {
|
||||||
|
storage::replication::HeartbeatRes res{false, 0, ""};
|
||||||
|
slk::Save(res, res_builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this handler is agnostic of InMemory, move to be reused by on-disk
|
// TODO: this handler is agnostic of InMemory, move to be reused by on-disk
|
||||||
auto const *storage = db_acc->get()->storage();
|
auto const *storage = db_acc->get()->storage();
|
||||||
storage::replication::HeartbeatRes res{storage->id(), true,
|
storage::replication::HeartbeatRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load(),
|
||||||
storage->repl_storage_state_.last_commit_timestamp_.load(),
|
|
||||||
std::string{storage->repl_storage_state_.epoch_.id()}};
|
std::string{storage->repl_storage_state_.epoch_.id()}};
|
||||||
slk::Save(res, res_builder);
|
slk::Save(res, res_builder);
|
||||||
}
|
}
|
||||||
@@ -122,8 +128,12 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
|
|||||||
slk::Builder *res_builder) {
|
slk::Builder *res_builder) {
|
||||||
storage::replication::AppendDeltasReq req;
|
storage::replication::AppendDeltasReq req;
|
||||||
slk::Load(&req, req_reader);
|
slk::Load(&req, req_reader);
|
||||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||||
if (!db_acc) return;
|
if (!db_acc) {
|
||||||
|
storage::replication::AppendDeltasRes res{false, 0};
|
||||||
|
slk::Save(res, res_builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
storage::replication::Decoder decoder(req_reader);
|
storage::replication::Decoder decoder(req_reader);
|
||||||
|
|
||||||
@@ -163,7 +173,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
|
|||||||
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
|
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::replication::AppendDeltasRes res{storage->id(), false, repl_storage_state.last_commit_timestamp_.load()};
|
storage::replication::AppendDeltasRes res{false, repl_storage_state.last_commit_timestamp_.load()};
|
||||||
slk::Save(res, res_builder);
|
slk::Save(res, res_builder);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -172,7 +182,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
|
|||||||
storage, &decoder,
|
storage, &decoder,
|
||||||
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
|
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
|
||||||
|
|
||||||
storage::replication::AppendDeltasRes res{storage->id(), true, repl_storage_state.last_commit_timestamp_.load()};
|
storage::replication::AppendDeltasRes res{true, repl_storage_state.last_commit_timestamp_.load()};
|
||||||
slk::Save(res, res_builder);
|
slk::Save(res, res_builder);
|
||||||
spdlog::debug("Replication recovery from append deltas finished, replica is now up to date!");
|
spdlog::debug("Replication recovery from append deltas finished, replica is now up to date!");
|
||||||
}
|
}
|
||||||
@@ -181,15 +191,19 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
|
|||||||
slk::Builder *res_builder) {
|
slk::Builder *res_builder) {
|
||||||
storage::replication::SnapshotReq req;
|
storage::replication::SnapshotReq req;
|
||||||
slk::Load(&req, req_reader);
|
slk::Load(&req, req_reader);
|
||||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||||
if (!db_acc) return;
|
if (!db_acc) {
|
||||||
|
storage::replication::SnapshotRes res{false, 0};
|
||||||
|
slk::Save(res, res_builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
storage::replication::Decoder decoder(req_reader);
|
storage::replication::Decoder decoder(req_reader);
|
||||||
|
|
||||||
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
|
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
|
||||||
utils::EnsureDirOrDie(storage->snapshot_directory_);
|
utils::EnsureDirOrDie(storage->recovery_.snapshot_directory_);
|
||||||
|
|
||||||
const auto maybe_snapshot_path = decoder.ReadFile(storage->snapshot_directory_);
|
const auto maybe_snapshot_path = decoder.ReadFile(storage->recovery_.snapshot_directory_);
|
||||||
MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!");
|
MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!");
|
||||||
spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path);
|
spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path);
|
||||||
|
|
||||||
@@ -219,20 +233,23 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
|
|||||||
storage->timestamp_ = std::max(storage->timestamp_, recovery_info.next_timestamp);
|
storage->timestamp_ = std::max(storage->timestamp_, recovery_info.next_timestamp);
|
||||||
|
|
||||||
spdlog::trace("Recovering indices and constraints from snapshot.");
|
spdlog::trace("Recovering indices and constraints from snapshot.");
|
||||||
storage::durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &storage->indices_,
|
memgraph::storage::durability::RecoverIndicesAndStats(recovered_snapshot.indices_constraints.indices,
|
||||||
&storage->constraints_, &storage->vertices_);
|
&storage->indices_, &storage->vertices_,
|
||||||
|
storage->name_id_mapper_.get());
|
||||||
|
memgraph::storage::durability::RecoverConstraints(recovered_snapshot.indices_constraints.constraints,
|
||||||
|
&storage->constraints_, &storage->vertices_,
|
||||||
|
storage->name_id_mapper_.get());
|
||||||
} catch (const storage::durability::RecoveryFailure &e) {
|
} catch (const storage::durability::RecoveryFailure &e) {
|
||||||
LOG_FATAL("Couldn't load the snapshot because of: {}", e.what());
|
LOG_FATAL("Couldn't load the snapshot because of: {}", e.what());
|
||||||
}
|
}
|
||||||
storage_guard.unlock();
|
storage_guard.unlock();
|
||||||
|
|
||||||
storage::replication::SnapshotRes res{storage->id(), true,
|
storage::replication::SnapshotRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
|
||||||
storage->repl_storage_state_.last_commit_timestamp_.load()};
|
|
||||||
slk::Save(res, res_builder);
|
slk::Save(res, res_builder);
|
||||||
|
|
||||||
spdlog::trace("Deleting old snapshot files due to snapshot recovery.");
|
spdlog::trace("Deleting old snapshot files due to snapshot recovery.");
|
||||||
// Delete other durability files
|
// Delete other durability files
|
||||||
auto snapshot_files = storage::durability::GetSnapshotFiles(storage->snapshot_directory_, storage->uuid_);
|
auto snapshot_files = storage::durability::GetSnapshotFiles(storage->recovery_.snapshot_directory_, storage->uuid_);
|
||||||
for (const auto &[path, uuid, _] : snapshot_files) {
|
for (const auto &[path, uuid, _] : snapshot_files) {
|
||||||
if (path != *maybe_snapshot_path) {
|
if (path != *maybe_snapshot_path) {
|
||||||
spdlog::trace("Deleting snapshot file {}", path);
|
spdlog::trace("Deleting snapshot file {}", path);
|
||||||
@@ -241,7 +258,7 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
|
|||||||
}
|
}
|
||||||
|
|
||||||
spdlog::trace("Deleting old WAL files due to snapshot recovery.");
|
spdlog::trace("Deleting old WAL files due to snapshot recovery.");
|
||||||
auto wal_files = storage::durability::GetWalFiles(storage->wal_directory_, storage->uuid_);
|
auto wal_files = storage::durability::GetWalFiles(storage->recovery_.wal_directory_, storage->uuid_);
|
||||||
if (wal_files) {
|
if (wal_files) {
|
||||||
for (const auto &wal_file : *wal_files) {
|
for (const auto &wal_file : *wal_files) {
|
||||||
spdlog::trace("Deleting WAL file {}", wal_file.path);
|
spdlog::trace("Deleting WAL file {}", wal_file.path);
|
||||||
@@ -257,8 +274,12 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle
|
|||||||
slk::Builder *res_builder) {
|
slk::Builder *res_builder) {
|
||||||
storage::replication::WalFilesReq req;
|
storage::replication::WalFilesReq req;
|
||||||
slk::Load(&req, req_reader);
|
slk::Load(&req, req_reader);
|
||||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||||
if (!db_acc) return;
|
if (!db_acc) {
|
||||||
|
storage::replication::WalFilesRes res{false, 0};
|
||||||
|
slk::Save(res, res_builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto wal_file_number = req.file_number;
|
const auto wal_file_number = req.file_number;
|
||||||
spdlog::debug("Received WAL files: {}", wal_file_number);
|
spdlog::debug("Received WAL files: {}", wal_file_number);
|
||||||
@@ -266,14 +287,13 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle
|
|||||||
storage::replication::Decoder decoder(req_reader);
|
storage::replication::Decoder decoder(req_reader);
|
||||||
|
|
||||||
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
|
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
|
||||||
utils::EnsureDirOrDie(storage->wal_directory_);
|
utils::EnsureDirOrDie(storage->recovery_.wal_directory_);
|
||||||
|
|
||||||
for (auto i = 0; i < wal_file_number; ++i) {
|
for (auto i = 0; i < wal_file_number; ++i) {
|
||||||
LoadWal(storage, &decoder);
|
LoadWal(storage, &decoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::replication::WalFilesRes res{storage->id(), true,
|
storage::replication::WalFilesRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
|
||||||
storage->repl_storage_state_.last_commit_timestamp_.load()};
|
|
||||||
slk::Save(res, res_builder);
|
slk::Save(res, res_builder);
|
||||||
spdlog::debug("Replication recovery from WAL files ended successfully, replica is now up to date!");
|
spdlog::debug("Replication recovery from WAL files ended successfully, replica is now up to date!");
|
||||||
}
|
}
|
||||||
@@ -282,18 +302,21 @@ void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_hand
|
|||||||
slk::Builder *res_builder) {
|
slk::Builder *res_builder) {
|
||||||
storage::replication::CurrentWalReq req;
|
storage::replication::CurrentWalReq req;
|
||||||
slk::Load(&req, req_reader);
|
slk::Load(&req, req_reader);
|
||||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||||
if (!db_acc) return;
|
if (!db_acc) {
|
||||||
|
storage::replication::CurrentWalRes res{false, 0};
|
||||||
|
slk::Save(res, res_builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
storage::replication::Decoder decoder(req_reader);
|
storage::replication::Decoder decoder(req_reader);
|
||||||
|
|
||||||
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
|
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
|
||||||
utils::EnsureDirOrDie(storage->wal_directory_);
|
utils::EnsureDirOrDie(storage->recovery_.wal_directory_);
|
||||||
|
|
||||||
LoadWal(storage, &decoder);
|
LoadWal(storage, &decoder);
|
||||||
|
|
||||||
storage::replication::CurrentWalRes res{storage->id(), true,
|
storage::replication::CurrentWalRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
|
||||||
storage->repl_storage_state_.last_commit_timestamp_.load()};
|
|
||||||
slk::Save(res, res_builder);
|
slk::Save(res, res_builder);
|
||||||
spdlog::debug("Replication recovery from current WAL ended successfully, replica is now up to date!");
|
spdlog::debug("Replication recovery from current WAL ended successfully, replica is now up to date!");
|
||||||
}
|
}
|
||||||
@@ -312,6 +335,8 @@ void InMemoryReplicationHandlers::LoadWal(storage::InMemoryStorage *storage, sto
|
|||||||
}
|
}
|
||||||
auto &replica_epoch = storage->repl_storage_state_.epoch_;
|
auto &replica_epoch = storage->repl_storage_state_.epoch_;
|
||||||
if (wal_info.epoch_id != replica_epoch.id()) {
|
if (wal_info.epoch_id != replica_epoch.id()) {
|
||||||
|
// questionable behaviour, we trust that any change in epoch implies change in who is MAIN
|
||||||
|
// when we use high availability, this assumption need to be checked.
|
||||||
auto prev_epoch = replica_epoch.SetEpoch(wal_info.epoch_id);
|
auto prev_epoch = replica_epoch.SetEpoch(wal_info.epoch_id);
|
||||||
storage->repl_storage_state_.AddEpochToHistoryForce(prev_epoch);
|
storage->repl_storage_state_.AddEpochToHistoryForce(prev_epoch);
|
||||||
}
|
}
|
||||||
@@ -349,13 +374,16 @@ void InMemoryReplicationHandlers::TimestampHandler(dbms::DbmsHandler *dbms_handl
|
|||||||
slk::Builder *res_builder) {
|
slk::Builder *res_builder) {
|
||||||
storage::replication::TimestampReq req;
|
storage::replication::TimestampReq req;
|
||||||
slk::Load(&req, req_reader);
|
slk::Load(&req, req_reader);
|
||||||
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||||
if (!db_acc) return;
|
if (!db_acc) {
|
||||||
|
storage::replication::TimestampRes res{false, 0};
|
||||||
|
slk::Save(res, res_builder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this handler is agnostic of InMemory, move to be reused by on-disk
|
// TODO: this handler is agnostic of InMemory, move to be reused by on-disk
|
||||||
auto const *storage = db_acc->get()->storage();
|
auto const *storage = db_acc->get()->storage();
|
||||||
storage::replication::TimestampRes res{storage->id(), true,
|
storage::replication::TimestampRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
|
||||||
storage->repl_storage_state_.last_commit_timestamp_.load()};
|
|
||||||
slk::Save(res, res_builder);
|
slk::Save(res, res_builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,14 +397,15 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
constexpr bool kSharedAccess = false;
|
constexpr bool kSharedAccess = false;
|
||||||
|
|
||||||
std::optional<std::pair<uint64_t, storage::InMemoryStorage::ReplicationAccessor>> commit_timestamp_and_accessor;
|
std::optional<std::pair<uint64_t, storage::InMemoryStorage::ReplicationAccessor>> commit_timestamp_and_accessor;
|
||||||
auto get_transaction = [storage, &commit_timestamp_and_accessor](uint64_t commit_timestamp,
|
auto const get_transaction = [storage, &commit_timestamp_and_accessor](
|
||||||
bool unique = kSharedAccess) {
|
uint64_t commit_timestamp,
|
||||||
|
bool unique = kSharedAccess) -> storage::InMemoryStorage::ReplicationAccessor * {
|
||||||
if (!commit_timestamp_and_accessor) {
|
if (!commit_timestamp_and_accessor) {
|
||||||
std::unique_ptr<storage::Storage::Accessor> acc = nullptr;
|
std::unique_ptr<storage::Storage::Accessor> acc = nullptr;
|
||||||
if (unique) {
|
if (unique) {
|
||||||
acc = storage->UniqueAccess(std::nullopt, false /*not main*/);
|
acc = storage->UniqueAccess(ReplicationRole::REPLICA);
|
||||||
} else {
|
} else {
|
||||||
acc = storage->Access(std::nullopt, false /*not main*/);
|
acc = storage->Access(ReplicationRole::REPLICA);
|
||||||
}
|
}
|
||||||
auto inmem_acc = std::unique_ptr<storage::InMemoryStorage::InMemoryAccessor>(
|
auto inmem_acc = std::unique_ptr<storage::InMemoryStorage::InMemoryAccessor>(
|
||||||
static_cast<storage::InMemoryStorage::InMemoryAccessor *>(acc.release()));
|
static_cast<storage::InMemoryStorage::InMemoryAccessor *>(acc.release()));
|
||||||
@@ -414,9 +443,11 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint());
|
spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint());
|
||||||
auto *transaction = get_transaction(timestamp);
|
auto *transaction = get_transaction(timestamp);
|
||||||
auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, View::NEW);
|
auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, View::NEW);
|
||||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
if (!vertex)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto ret = transaction->DeleteVertex(&*vertex);
|
auto ret = transaction->DeleteVertex(&*vertex);
|
||||||
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
|
if (ret.HasError() || !ret.GetValue())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::VERTEX_ADD_LABEL: {
|
case WalDeltaData::Type::VERTEX_ADD_LABEL: {
|
||||||
@@ -424,9 +455,11 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
delta.vertex_add_remove_label.label);
|
delta.vertex_add_remove_label.label);
|
||||||
auto *transaction = get_transaction(timestamp);
|
auto *transaction = get_transaction(timestamp);
|
||||||
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
|
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
|
||||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
if (!vertex)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
||||||
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
|
if (ret.HasError() || !ret.GetValue())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
|
case WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
|
||||||
@@ -434,9 +467,11 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
delta.vertex_add_remove_label.label);
|
delta.vertex_add_remove_label.label);
|
||||||
auto *transaction = get_transaction(timestamp);
|
auto *transaction = get_transaction(timestamp);
|
||||||
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
|
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW);
|
||||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
if (!vertex)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
||||||
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
|
if (ret.HasError() || !ret.GetValue())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::VERTEX_SET_PROPERTY: {
|
case WalDeltaData::Type::VERTEX_SET_PROPERTY: {
|
||||||
@@ -444,10 +479,12 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
|
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
|
||||||
auto *transaction = get_transaction(timestamp);
|
auto *transaction = get_transaction(timestamp);
|
||||||
auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, View::NEW);
|
auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, View::NEW);
|
||||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
if (!vertex)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
||||||
delta.vertex_edge_set_property.value);
|
delta.vertex_edge_set_property.value);
|
||||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
if (ret.HasError())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::EDGE_CREATE: {
|
case WalDeltaData::Type::EDGE_CREATE: {
|
||||||
@@ -456,13 +493,16 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
||||||
auto *transaction = get_transaction(timestamp);
|
auto *transaction = get_transaction(timestamp);
|
||||||
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
|
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
|
||||||
if (!from_vertex) throw utils::BasicException("Invalid transaction!");
|
if (!from_vertex)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
|
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
|
||||||
if (!to_vertex) throw utils::BasicException("Invalid transaction!");
|
if (!to_vertex)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto edge = transaction->CreateEdgeEx(&*from_vertex, &*to_vertex,
|
auto edge = transaction->CreateEdgeEx(&*from_vertex, &*to_vertex,
|
||||||
transaction->NameToEdgeType(delta.edge_create_delete.edge_type),
|
transaction->NameToEdgeType(delta.edge_create_delete.edge_type),
|
||||||
delta.edge_create_delete.gid);
|
delta.edge_create_delete.gid);
|
||||||
if (edge.HasError()) throw utils::BasicException("Invalid transaction!");
|
if (edge.HasError())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::EDGE_DELETE: {
|
case WalDeltaData::Type::EDGE_DELETE: {
|
||||||
@@ -471,22 +511,23 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
||||||
auto *transaction = get_transaction(timestamp);
|
auto *transaction = get_transaction(timestamp);
|
||||||
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
|
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW);
|
||||||
if (!from_vertex) throw utils::BasicException("Invalid transaction!");
|
if (!from_vertex)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
|
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW);
|
||||||
if (!to_vertex) throw utils::BasicException("Invalid transaction!");
|
if (!to_vertex)
|
||||||
auto edges = from_vertex->OutEdges(View::NEW, {transaction->NameToEdgeType(delta.edge_create_delete.edge_type)},
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
&*to_vertex);
|
auto edgeType = transaction->NameToEdgeType(delta.edge_create_delete.edge_type);
|
||||||
if (edges.HasError()) throw utils::BasicException("Invalid transaction!");
|
auto edge =
|
||||||
if (edges->edges.size() != 1) throw utils::BasicException("Invalid transaction!");
|
transaction->FindEdge(delta.edge_create_delete.gid, View::NEW, edgeType, &*from_vertex, &*to_vertex);
|
||||||
auto &edge = (*edges).edges[0];
|
if (!edge) throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
auto ret = transaction->DeleteEdge(&edge);
|
if (auto ret = transaction->DeleteEdge(&*edge); ret.HasError())
|
||||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::EDGE_SET_PROPERTY: {
|
case WalDeltaData::Type::EDGE_SET_PROPERTY: {
|
||||||
spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
|
spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
|
||||||
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
|
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
|
||||||
if (!storage->config_.items.properties_on_edges)
|
if (!storage->config_.salient.items.properties_on_edges)
|
||||||
throw utils::BasicException(
|
throw utils::BasicException(
|
||||||
"Can't set properties on edges because properties on edges "
|
"Can't set properties on edges because properties on edges "
|
||||||
"are disabled!");
|
"are disabled!");
|
||||||
@@ -497,7 +538,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
// yields an accessor that is only valid for managing the edge's
|
// yields an accessor that is only valid for managing the edge's
|
||||||
// properties.
|
// properties.
|
||||||
auto edge = edge_acc.find(delta.vertex_edge_set_property.gid);
|
auto edge = edge_acc.find(delta.vertex_edge_set_property.gid);
|
||||||
if (edge == edge_acc.end()) throw utils::BasicException("Invalid transaction!");
|
if (edge == edge_acc.end())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
// The edge visibility check must be done here manually because we
|
// The edge visibility check must be done here manually because we
|
||||||
// don't allow direct access to the edges through the public API.
|
// don't allow direct access to the edges through the public API.
|
||||||
{
|
{
|
||||||
@@ -529,7 +571,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!is_visible) throw utils::BasicException("Invalid transaction!");
|
if (!is_visible)
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
}
|
}
|
||||||
EdgeRef edge_ref(&*edge);
|
EdgeRef edge_ref(&*edge);
|
||||||
// Here we create an edge accessor that we will use to get the
|
// Here we create an edge accessor that we will use to get the
|
||||||
@@ -542,7 +585,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
|
|
||||||
auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
||||||
delta.vertex_edge_set_property.value);
|
delta.vertex_edge_set_property.value);
|
||||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
if (ret.HasError())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,9 +594,10 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
spdlog::trace(" Transaction end");
|
spdlog::trace(" Transaction end");
|
||||||
if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp)
|
if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp)
|
||||||
throw utils::BasicException("Invalid commit data!");
|
throw utils::BasicException("Invalid commit data!");
|
||||||
auto ret =
|
auto ret = commit_timestamp_and_accessor->second.Commit(
|
||||||
commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first, false /* not main */);
|
{.desired_commit_timestamp = commit_timestamp_and_accessor->first, .is_main = false});
|
||||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
if (ret.HasError())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
commit_timestamp_and_accessor = std::nullopt;
|
commit_timestamp_and_accessor = std::nullopt;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -562,14 +607,14 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
// Need to send the timestamp
|
// Need to send the timestamp
|
||||||
auto *transaction = get_transaction(timestamp, kUniqueAccess);
|
auto *transaction = get_transaction(timestamp, kUniqueAccess);
|
||||||
if (transaction->CreateIndex(storage->NameToLabel(delta.operation_label.label)).HasError())
|
if (transaction->CreateIndex(storage->NameToLabel(delta.operation_label.label)).HasError())
|
||||||
throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::LABEL_INDEX_DROP: {
|
case WalDeltaData::Type::LABEL_INDEX_DROP: {
|
||||||
spdlog::trace(" Drop label index on :{}", delta.operation_label.label);
|
spdlog::trace(" Drop label index on :{}", delta.operation_label.label);
|
||||||
auto *transaction = get_transaction(timestamp, kUniqueAccess);
|
auto *transaction = get_transaction(timestamp, kUniqueAccess);
|
||||||
if (transaction->DropIndex(storage->NameToLabel(delta.operation_label.label)).HasError())
|
if (transaction->DropIndex(storage->NameToLabel(delta.operation_label.label)).HasError())
|
||||||
throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::LABEL_INDEX_STATS_SET: {
|
case WalDeltaData::Type::LABEL_INDEX_STATS_SET: {
|
||||||
@@ -600,7 +645,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
->CreateIndex(storage->NameToLabel(delta.operation_label_property.label),
|
->CreateIndex(storage->NameToLabel(delta.operation_label_property.label),
|
||||||
storage->NameToProperty(delta.operation_label_property.property))
|
storage->NameToProperty(delta.operation_label_property.property))
|
||||||
.HasError())
|
.HasError())
|
||||||
throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
|
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
|
||||||
@@ -611,7 +656,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
->DropIndex(storage->NameToLabel(delta.operation_label_property.label),
|
->DropIndex(storage->NameToLabel(delta.operation_label_property.label),
|
||||||
storage->NameToProperty(delta.operation_label_property.property))
|
storage->NameToProperty(delta.operation_label_property.property))
|
||||||
.HasError())
|
.HasError())
|
||||||
throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET: {
|
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET: {
|
||||||
@@ -643,7 +688,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
auto ret =
|
auto ret =
|
||||||
transaction->CreateExistenceConstraint(storage->NameToLabel(delta.operation_label_property.label),
|
transaction->CreateExistenceConstraint(storage->NameToLabel(delta.operation_label_property.label),
|
||||||
storage->NameToProperty(delta.operation_label_property.property));
|
storage->NameToProperty(delta.operation_label_property.property));
|
||||||
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
|
if (ret.HasError())
|
||||||
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
|
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
|
||||||
@@ -654,7 +700,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
->DropExistenceConstraint(storage->NameToLabel(delta.operation_label_property.label),
|
->DropExistenceConstraint(storage->NameToLabel(delta.operation_label_property.label),
|
||||||
storage->NameToProperty(delta.operation_label_property.property))
|
storage->NameToProperty(delta.operation_label_property.property))
|
||||||
.HasError())
|
.HasError())
|
||||||
throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: {
|
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: {
|
||||||
@@ -669,7 +715,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
auto ret = transaction->CreateUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label),
|
auto ret = transaction->CreateUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label),
|
||||||
properties);
|
properties);
|
||||||
if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS)
|
if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS)
|
||||||
throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
|
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
|
||||||
@@ -684,7 +730,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
|||||||
auto ret =
|
auto ret =
|
||||||
transaction->DropUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label), properties);
|
transaction->DropUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label), properties);
|
||||||
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) {
|
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) {
|
||||||
throw utils::BasicException("Invalid transaction!");
|
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
// Copyright 2024 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// 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
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "replication/replication_server.hpp"
|
#include "replication/replication_server.hpp"
|
||||||
#include "replication/state.hpp"
|
|
||||||
#include "storage/v2/replication/serialization.hpp"
|
#include "storage/v2/replication/serialization.hpp"
|
||||||
|
|
||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user