Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bdd6cf0395 | |||
| 5d67fe9d00 | |||
| 197c731a49 | |||
| 1fb612030e | |||
| 6d518e4a86 | |||
| fb5ae0ecca | |||
| 69a718c14b | |||
| 79e69550c9 | |||
| 5b564a3e28 | |||
| b6b2d7b615 | |||
| 356ee40276 | |||
| f0146d03d3 | |||
| 742e28d2a3 | |||
| 58f364dc04 | |||
| a5c9450b2d | |||
| b222574ff3 | |||
| 28e1c486f5 | |||
| 82370f3555 | |||
| 4f6c57fbba | |||
| 5a558e3b71 | |||
| b33f670b9d |
+28
-2
@@ -59,11 +59,12 @@ jobs:
|
|||||||
- run: ./lazy.sh devsetup
|
- run: ./lazy.sh devsetup
|
||||||
|
|
||||||
- run: make zrepl-bin
|
- run: make zrepl-bin
|
||||||
|
- run: make test-platform-bin
|
||||||
- run: make vet
|
- run: make vet
|
||||||
- run: make test
|
|
||||||
- run: make lint
|
- run: make lint
|
||||||
- run: make release
|
- run: make release
|
||||||
|
- run: make test-go
|
||||||
|
# cannot run test-platform because circle-ci runs in linux containers
|
||||||
|
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: ./artifacts/release
|
path: ./artifacts/release
|
||||||
@@ -85,6 +86,7 @@ jobs:
|
|||||||
mc config host add --api s3v4 zrepl-minio https://minio.cschwarz.com ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
|
mc config host add --api s3v4 zrepl-minio https://minio.cschwarz.com ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
|
||||||
|
|
||||||
# Upload artifacts
|
# Upload artifacts
|
||||||
|
mkdir -p artifacts/release
|
||||||
echo "$CIRCLE_BUILD_URL" > ./artifacts/release/cirlceci_build_url
|
echo "$CIRCLE_BUILD_URL" > ./artifacts/release/cirlceci_build_url
|
||||||
mc cp -r artifacts/release "zrepl-minio/zrepl-ci-artifacts/${CIRCLE_SHA1}/${CIRCLE_JOB}/"
|
mc cp -r artifacts/release "zrepl-minio/zrepl-ci-artifacts/${CIRCLE_SHA1}/${CIRCLE_JOB}/"
|
||||||
|
|
||||||
@@ -98,6 +100,30 @@ jobs:
|
|||||||
-X POST \
|
-X POST \
|
||||||
-d '{"context":"zrepl/publish-ci-artifacts", "state": "success", "description":"CI Build Artifacts for '"$JOB_NAME"'", "target_url":"https://minio.cschwarz.com/minio/zrepl-ci-artifacts/'"$COMMIT"'/"}'
|
-d '{"context":"zrepl/publish-ci-artifacts", "state": "success", "description":"CI Build Artifacts for '"$JOB_NAME"'", "target_url":"https://minio.cschwarz.com/minio/zrepl-ci-artifacts/'"$COMMIT"'/"}'
|
||||||
|
|
||||||
|
- run:
|
||||||
|
shell: /bin/bash -eo pipefail
|
||||||
|
when: always
|
||||||
|
command: |
|
||||||
|
if [ -n "$CIRCLE_PR_NUMBER" ]; then # CIRCLE_PR_NUMBER is guaranteed to be only present in forked PRs (external)
|
||||||
|
echo "Forked PR detected. Sry, can't trust you with credentials to external artifact store, use CircleCI's instead."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
set -u # from now on
|
||||||
|
|
||||||
|
# Trigger Platform Tests
|
||||||
|
# ZREPL_BOT_GIT_PRWN_EU_ACCESS_TOKEN from secrets
|
||||||
|
## Sync this commit to the GitLab
|
||||||
|
git remote add prwn-gitlab "https://zrepl-bot:$ZREPL_BOT_GIT_PRWN_EU_ACCESS_TOKEN@git.prwn.eu/problame/zrepl.git"
|
||||||
|
REMOTE_REF=circleci/$CIRCLE_BRANCH/$CIRCLE_BUILD_NUM
|
||||||
|
git push -f prwn-gitlab "HEAD:$REMOTE_REF"
|
||||||
|
## Trigger Tests
|
||||||
|
curl -v --request POST \
|
||||||
|
--header "PRIVATE-TOKEN: $ZREPL_BOT_GIT_PRWN_EU_ACCESS_TOKEN" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
--data '{ "ref":"'"$REMOTE_REF"'", "variables": [ {"key":"ZREPL_MAIN_REPO_COMMIT", "value": "'"$CIRCLE_SHA1"'" }, { "key":"ZREPL_CI_BUILDJOB", "value": "'"$CIRCLE_JOB"'" } ] } ' \
|
||||||
|
"https://git.prwn.eu/api/v4/projects/13/pipeline"
|
||||||
|
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
shell: /bin/bash -euo pipefail
|
shell: /bin/bash -euo pipefail
|
||||||
command: |
|
command: |
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
build-and-run-platformtests:
|
||||||
|
variables:
|
||||||
|
GIT_STRATEGY: none
|
||||||
|
script:
|
||||||
|
- mkdir artifacts
|
||||||
|
# platformtests are installed by the CI environment
|
||||||
|
- /opt/platformtest-linux-amd64
|
||||||
|
-test.coverprofile artifacts/platformtest.cover
|
||||||
|
-test.v
|
||||||
|
__DEVEL--i-heard-you-like-tests
|
||||||
|
-poolname zreplplatformtest
|
||||||
|
-imagepath /tmp/zreplplatformtest.pool.img
|
||||||
|
-mountpoint /tmp/zreplplatformtest.pool
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- artifacts
|
||||||
|
image: "images:ubuntu/focal/cloud"
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
commit="$1"
|
||||||
|
build="$2"
|
||||||
|
src="$3"
|
||||||
|
dst="$4"
|
||||||
|
|
||||||
|
[ -n "$commit" -a -n "$build" -a -n "$src" -a -n "$dst" ] || ( echo "arguments must not be empty"; exit 1 )
|
||||||
|
|
||||||
|
aws="aws --endpoint-url https://minio.cschwarz.com --no-sign-request"
|
||||||
|
|
||||||
|
|
||||||
|
path="s3://zrepl-ci-artifacts/$commit/$build/$src"
|
||||||
|
$aws s3 cp "$path" "$dst"
|
||||||
@@ -27,6 +27,8 @@ GO_EXTRA_BUILDFLAGS :=
|
|||||||
GO_BUILDFLAGS := $(GO_MOD_READONLY) $(GO_EXTRA_BUILDFLAGS)
|
GO_BUILDFLAGS := $(GO_MOD_READONLY) $(GO_EXTRA_BUILDFLAGS)
|
||||||
GO_BUILD := $(GO_ENV_VARS) $(GO) build $(GO_BUILDFLAGS) -ldflags $(GO_LDFLAGS)
|
GO_BUILD := $(GO_ENV_VARS) $(GO) build $(GO_BUILDFLAGS) -ldflags $(GO_LDFLAGS)
|
||||||
GOLANGCI_LINT := golangci-lint
|
GOLANGCI_LINT := golangci-lint
|
||||||
|
GOCOVMERGE := gocovmerge
|
||||||
|
|
||||||
ifneq ($(GOARM),)
|
ifneq ($(GOARM),)
|
||||||
ZREPL_TARGET_TUPLE := $(GOOS)-$(GOARCH)v$(GOARM)
|
ZREPL_TARGET_TUPLE := $(GOOS)-$(GOARCH)v$(GOARM)
|
||||||
else
|
else
|
||||||
@@ -45,7 +47,7 @@ printvars:
|
|||||||
|
|
||||||
release: clean
|
release: clean
|
||||||
# no cross-platform support for target test
|
# no cross-platform support for target test
|
||||||
$(MAKE) test
|
$(MAKE) test-go
|
||||||
$(MAKE) bins-all
|
$(MAKE) bins-all
|
||||||
$(MAKE) noarch
|
$(MAKE) noarch
|
||||||
$(MAKE) wrapup-and-checksum
|
$(MAKE) wrapup-and-checksum
|
||||||
@@ -99,9 +101,9 @@ clean: docs-clean
|
|||||||
rm -rf "$(ARTIFACTDIR)"
|
rm -rf "$(ARTIFACTDIR)"
|
||||||
|
|
||||||
##################### BINARIES #####################
|
##################### BINARIES #####################
|
||||||
.PHONY: bins-all lint test vet zrepl-bin platformtest-bin
|
.PHONY: bins-all lint test-go test-platform cover-merge cover-html vet zrepl-bin test-platform-bin generate-platform-test-list
|
||||||
|
|
||||||
BINS_ALL_TARGETS := zrepl-bin platformtest-bin vet lint
|
BINS_ALL_TARGETS := zrepl-bin test-platform-bin vet lint
|
||||||
GO_SUPPORTS_ILLUMOS := $(shell $(GO) version | gawk -F '.' '/^go version /{split($$0, comps, " "); split(comps[3], v, "."); if (v[1] == "go1" && v[2] >= 13) { print "illumos"; } else { print "noillumos"; }}')
|
GO_SUPPORTS_ILLUMOS := $(shell $(GO) version | gawk -F '.' '/^go version /{split($$0, comps, " "); split(comps[3], v, "."); if (v[1] == "go1" && v[2] >= 13) { print "illumos"; } else { print "noillumos"; }}')
|
||||||
bins-all:
|
bins-all:
|
||||||
$(MAKE) $(BINS_ALL_TARGETS) GOOS=freebsd GOARCH=amd64
|
$(MAKE) $(BINS_ALL_TARGETS) GOOS=freebsd GOARCH=amd64
|
||||||
@@ -122,27 +124,34 @@ endif
|
|||||||
|
|
||||||
lint:
|
lint:
|
||||||
$(GO_ENV_VARS) $(GOLANGCI_LINT) run ./...
|
$(GO_ENV_VARS) $(GOLANGCI_LINT) run ./...
|
||||||
test:
|
|
||||||
$(GO_ENV_VARS) $(GO) test $(GO_BUILDFLAGS) ./...
|
|
||||||
vet:
|
vet:
|
||||||
$(GO_ENV_VARS) $(GO) vet $(GO_BUILDFLAGS) ./...
|
$(GO_ENV_VARS) $(GO) vet $(GO_BUILDFLAGS) ./...
|
||||||
|
|
||||||
|
test-go: $(ARTIFACTDIR)
|
||||||
|
rm -f "$(ARTIFACTDIR)/gotest.cover"
|
||||||
|
ifeq ($(COVER),1)
|
||||||
|
$(GO_ENV_VARS) $(GO) test $(GO_BUILDFLAGS) \
|
||||||
|
-coverpkg github.com/zrepl/zrepl/... \
|
||||||
|
-covermode atomic \
|
||||||
|
-coverprofile "$(ARTIFACTDIR)/gotest.cover" \
|
||||||
|
./...
|
||||||
|
else
|
||||||
|
$(GO_ENV_VARS) $(GO) test $(GO_BUILDFLAGS) \
|
||||||
|
./...
|
||||||
|
endif
|
||||||
|
|
||||||
zrepl-bin:
|
zrepl-bin:
|
||||||
$(GO_BUILD) -o "$(ARTIFACTDIR)/zrepl-$(ZREPL_TARGET_TUPLE)"
|
$(GO_BUILD) -o "$(ARTIFACTDIR)/zrepl-$(ZREPL_TARGET_TUPLE)"
|
||||||
|
|
||||||
platformtest-bin:
|
generate-platform-test-list:
|
||||||
$(GO_BUILD) -o "$(ARTIFACTDIR)/platformtest-$(ZREPL_TARGET_TUPLE)" ./platformtest/harness
|
$(GO_BUILD) -o $(ARTIFACTDIR)/generate-platform-test-list ./platformtest/tests/gen
|
||||||
|
|
||||||
##################### DEV TARGETS #####################
|
test-platform-bin:
|
||||||
# not part of the build, must do that manually
|
$(GO_ENV_VARS) $(GO) test $(GO_BUILDFLAGS) \
|
||||||
.PHONY: generate format platformtest
|
-c -o "$(ARTIFACTDIR)/platformtest-$(ZREPL_TARGET_TUPLE)" \
|
||||||
|
-covermode=atomic -cover -coverpkg github.com/zrepl/zrepl/... \
|
||||||
generate:
|
./platformtest/harness
|
||||||
protoc -I=replication/logic/pdu --go_out=plugins=grpc:replication/logic/pdu replication/logic/pdu/pdu.proto
|
|
||||||
$(GO_ENV_VARS) $(GO) generate $(GO_BUILDFLAGS) -x ./...
|
|
||||||
|
|
||||||
format:
|
|
||||||
goimports -srcdir . -local 'github.com/zrepl/zrepl' -w $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -name '*.pb.go' -not -name '*_enumer.go')
|
|
||||||
|
|
||||||
ZREPL_PLATFORMTEST_POOLNAME := zreplplatformtest
|
ZREPL_PLATFORMTEST_POOLNAME := zreplplatformtest
|
||||||
ZREPL_PLATFORMTEST_IMAGEPATH := /tmp/zreplplatformtest.pool.img
|
ZREPL_PLATFORMTEST_IMAGEPATH := /tmp/zreplplatformtest.pool.img
|
||||||
@@ -150,16 +159,42 @@ ZREPL_PLATFORMTEST_MOUNTPOINT := /tmp/zreplplatformtest.pool
|
|||||||
ZREPL_PLATFORMTEST_ZFS_LOG := /tmp/zreplplatformtest.zfs.log
|
ZREPL_PLATFORMTEST_ZFS_LOG := /tmp/zreplplatformtest.zfs.log
|
||||||
# ZREPL_PLATFORMTEST_STOP_AND_KEEP := -failure.stop-and-keep-pool
|
# ZREPL_PLATFORMTEST_STOP_AND_KEEP := -failure.stop-and-keep-pool
|
||||||
ZREPL_PLATFORMTEST_ARGS :=
|
ZREPL_PLATFORMTEST_ARGS :=
|
||||||
platformtest: # do not track dependency on platformtest-bin to allow build of platformtest outside of test VM
|
test-platform: $(ARTIFACTDIR) # do not track dependency on test-platform-bin to allow build of platformtest outside of test VM
|
||||||
rm -f "$(ZREPL_PLATFORMTEST_ZFS_LOG)"
|
rm -f "$(ZREPL_PLATFORMTEST_ZFS_LOG)"
|
||||||
|
rm -f "$(ARTIFACTDIR)/platformtest.cover"
|
||||||
platformtest/logmockzfs/logzfsenv "$(ZREPL_PLATFORMTEST_ZFS_LOG)" `which zfs` \
|
platformtest/logmockzfs/logzfsenv "$(ZREPL_PLATFORMTEST_ZFS_LOG)" `which zfs` \
|
||||||
"$(ARTIFACTDIR)/platformtest-$(ZREPL_TARGET_TUPLE)" \
|
"$(ARTIFACTDIR)/platformtest-$(ZREPL_TARGET_TUPLE)" \
|
||||||
|
-test.coverprofile "$(ARTIFACTDIR)/platformtest.cover" \
|
||||||
|
-test.v \
|
||||||
|
__DEVEL--i-heard-you-like-tests \
|
||||||
-poolname "$(ZREPL_PLATFORMTEST_POOLNAME)" \
|
-poolname "$(ZREPL_PLATFORMTEST_POOLNAME)" \
|
||||||
-imagepath "$(ZREPL_PLATFORMTEST_IMAGEPATH)" \
|
-imagepath "$(ZREPL_PLATFORMTEST_IMAGEPATH)" \
|
||||||
-mountpoint "$(ZREPL_PLATFORMTEST_MOUNTPOINT)" \
|
-mountpoint "$(ZREPL_PLATFORMTEST_MOUNTPOINT)" \
|
||||||
$(ZREPL_PLATFORMTEST_STOP_AND_KEEP) \
|
$(ZREPL_PLATFORMTEST_STOP_AND_KEEP) \
|
||||||
$(ZREPL_PLATFORMTEST_ARGS)
|
$(ZREPL_PLATFORMTEST_ARGS)
|
||||||
|
|
||||||
|
cover-merge: $(ARTIFACTDIR)
|
||||||
|
$(GOCOVMERGE) $(ARTIFACTDIR)/platformtest.cover $(ARTIFACTDIR)/gotest.cover > $(ARTIFACTDIR)/merged.cover
|
||||||
|
cover-html: cover-merge
|
||||||
|
$(GO) tool cover -html "$(ARTIFACTDIR)/merged.cover" -o "$(ARTIFACTDIR)/merged.cover.html"
|
||||||
|
|
||||||
|
test-full:
|
||||||
|
test "$$(id -u)" = "0" || echo "MUST RUN AS ROOT" 1>&2
|
||||||
|
$(MAKE) test-go COVER=1
|
||||||
|
$(MAKE) test-platform
|
||||||
|
$(MAKE) cover-html
|
||||||
|
|
||||||
|
##################### DEV TARGETS #####################
|
||||||
|
# not part of the build, must do that manually
|
||||||
|
.PHONY: generate format
|
||||||
|
|
||||||
|
generate: generate-platform-test-list
|
||||||
|
protoc -I=replication/logic/pdu --go_out=plugins=grpc:replication/logic/pdu replication/logic/pdu/pdu.proto
|
||||||
|
$(GO_ENV_VARS) $(GO) generate $(GO_BUILDFLAGS) -x ./...
|
||||||
|
|
||||||
|
format:
|
||||||
|
goimports -srcdir . -local 'github.com/zrepl/zrepl' -w $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -name '*.pb.go' -not -name '*_enumer.go')
|
||||||
|
|
||||||
##################### NOARCH #####################
|
##################### NOARCH #####################
|
||||||
.PHONY: noarch $(ARTIFACTDIR)/bash_completion $(ARTIFACTDIR)/_zrepl.zsh_completion $(ARTIFACTDIR)/go_env.txt docs docs-clean
|
.PHONY: noarch $(ARTIFACTDIR)/bash_completion $(ARTIFACTDIR)/_zrepl.zsh_completion $(ARTIFACTDIR)/go_env.txt docs docs-clean
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Check out the *Coding Workflow* section below for details.
|
|||||||
* Ship other material provided in `./dist`, e.g. in `/usr/share/zrepl/`.
|
* Ship other material provided in `./dist`, e.g. in `/usr/share/zrepl/`.
|
||||||
* Use `make release ZREPL_VERSION='mydistro-1.2.3_1'`
|
* Use `make release ZREPL_VERSION='mydistro-1.2.3_1'`
|
||||||
* Your distro's name and any versioning supplemental to zrepl's (e.g. package revision) should be in this string
|
* Your distro's name and any versioning supplemental to zrepl's (e.g. package revision) should be in this string
|
||||||
* Use `make platformtest` **on a test system** to validate that zrepl's abstractions on top of ZFS work with the system ZFS.
|
* Use `sudo make test-platform` **on a test system** to validate that zrepl's abstractions on top of ZFS work with the system ZFS.
|
||||||
* Make sure you are informed about new zrepl versions, e.g. by subscribing to GitHub's release RSS feed.
|
* Make sure you are informed about new zrepl versions, e.g. by subscribing to GitHub's release RSS feed.
|
||||||
|
|
||||||
## Developer Documentation
|
## Developer Documentation
|
||||||
@@ -47,10 +47,18 @@ Check out the *Coding Workflow* section below for details.
|
|||||||
zrepl is written in [Go](https://golang.org) and uses [Go modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
|
zrepl is written in [Go](https://golang.org) and uses [Go modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
|
||||||
The documentation is written in [ReStructured Text](http://docutils.sourceforge.net/rst.html) using the [Sphinx](https://www.sphinx-doc.org) framework.
|
The documentation is written in [ReStructured Text](http://docutils.sourceforge.net/rst.html) using the [Sphinx](https://www.sphinx-doc.org) framework.
|
||||||
|
|
||||||
To get started, run `./lazy.sh devsetup` to easily install build dependencies and read `docs/installation.rst -> Compiling from Source`.
|
Install **build dependencies** using `./lazy.sh devsetup`.
|
||||||
`lazy.sh` uses `python3-pip` to fetch the build dependencies for the docs - you might want to use a [venv](https://docs.python.org/3/library/venv.html).
|
`lazy.sh` uses `python3-pip` to fetch the build dependencies for the docs - you might want to use a [venv](https://docs.python.org/3/library/venv.html).
|
||||||
If you just want to install the Go dependencies, run `./lazy.sh godep`.
|
If you just want to install the Go dependencies, run `./lazy.sh godep`.
|
||||||
|
|
||||||
|
The **test suite** is split into pure **Go tests** (`make test-go`) and **platform tests** that interact with ZFS and thus generally **require root privileges** (`sudo make test-platform`).
|
||||||
|
Platform tests run on their own pool with the name `zreplplatformtest`, which is created using the file vdev in `/tmp`.
|
||||||
|
|
||||||
|
For a full **code coverage** profile, run `make test-go COVER=1 && sudo make test-platform && make cover-merge`.
|
||||||
|
An HTML report can be generated using `make cover-html`.
|
||||||
|
|
||||||
|
**Code generation** is triggered by `make generate`. Generated code is committed to the source tree.
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ require (
|
|||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/viper v1.3.2 // indirect
|
github.com/spf13/viper v1.3.2 // indirect
|
||||||
github.com/stretchr/testify v1.4.0 // indirect
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
|
github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad // indirect
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||||
golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b
|
golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
|
|||||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||||
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||||
|
github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0=
|
||||||
|
github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
_ "github.com/alvaroloes/enumer"
|
_ "github.com/alvaroloes/enumer"
|
||||||
_ "github.com/golang/protobuf/protoc-gen-go"
|
_ "github.com/golang/protobuf/protoc-gen-go"
|
||||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||||
|
_ "github.com/wadey/gocovmerge"
|
||||||
_ "golang.org/x/tools/cmd/goimports"
|
_ "golang.org/x/tools/cmd/goimports"
|
||||||
_ "golang.org/x/tools/cmd/stringer"
|
_ "golang.org/x/tools/cmd/stringer"
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-1
@@ -76,7 +76,12 @@ type SnapJob struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SendOptions struct {
|
type SendOptions struct {
|
||||||
Encrypted bool `yaml:"encrypted"`
|
Encrypted bool `yaml:"encrypted"`
|
||||||
|
StepHolds SendOptionsStepHolds `yaml:"step_holds,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendOptionsStepHolds struct {
|
||||||
|
DisableIncremental bool `yaml:"disable_incremental,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ yaml.Defaulter = (*SendOptions)(nil)
|
var _ yaml.Defaulter = (*SendOptions)(nil)
|
||||||
|
|||||||
+7
-1
@@ -120,7 +120,13 @@ func (j *controlJob) Run(ctx context.Context) {
|
|||||||
jsonResponder{log, func() (interface{}, error) {
|
jsonResponder{log, func() (interface{}, error) {
|
||||||
jobs := j.jobs.status()
|
jobs := j.jobs.status()
|
||||||
globalZFS := zfscmd.GetReport()
|
globalZFS := zfscmd.GetReport()
|
||||||
s := Status{Jobs: jobs, Global: GlobalStatus{ZFSCmds: globalZFS}}
|
envconstReport := envconst.GetReport()
|
||||||
|
s := Status{
|
||||||
|
Jobs: jobs,
|
||||||
|
Global: GlobalStatus{
|
||||||
|
ZFSCmds: globalZFS,
|
||||||
|
Envconst: envconstReport,
|
||||||
|
}}
|
||||||
return s, nil
|
return s, nil
|
||||||
}})
|
}})
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -13,6 +13,8 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/zrepl/zrepl/daemon/logging/trace"
|
"github.com/zrepl/zrepl/daemon/logging/trace"
|
||||||
|
"github.com/zrepl/zrepl/endpoint"
|
||||||
|
"github.com/zrepl/zrepl/util/envconst"
|
||||||
|
|
||||||
"github.com/zrepl/zrepl/config"
|
"github.com/zrepl/zrepl/config"
|
||||||
"github.com/zrepl/zrepl/daemon/job"
|
"github.com/zrepl/zrepl/daemon/job"
|
||||||
@@ -95,6 +97,7 @@ func Run(ctx context.Context, conf *config.Config) error {
|
|||||||
// register global (=non job-local) metrics
|
// register global (=non job-local) metrics
|
||||||
zfscmd.RegisterMetrics(prometheus.DefaultRegisterer)
|
zfscmd.RegisterMetrics(prometheus.DefaultRegisterer)
|
||||||
trace.RegisterMetrics(prometheus.DefaultRegisterer)
|
trace.RegisterMetrics(prometheus.DefaultRegisterer)
|
||||||
|
endpoint.RegisterMetrics(prometheus.DefaultRegisterer)
|
||||||
|
|
||||||
log.Info("starting daemon")
|
log.Info("starting daemon")
|
||||||
|
|
||||||
@@ -148,7 +151,8 @@ type Status struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GlobalStatus struct {
|
type GlobalStatus struct {
|
||||||
ZFSCmds *zfscmd.Report
|
ZFSCmds *zfscmd.Report
|
||||||
|
Envconst *envconst.Report
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *jobs) status() map[string]*job.Status {
|
func (s *jobs) status() map[string]*job.Status {
|
||||||
|
|||||||
+16
-3
@@ -152,9 +152,10 @@ func modePushFromConfig(g *config.Global, in *config.PushJob, jobID endpoint.Job
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.senderConfig = &endpoint.SenderConfig{
|
m.senderConfig = &endpoint.SenderConfig{
|
||||||
FSF: fsf,
|
FSF: fsf,
|
||||||
Encrypt: &zfs.NilBool{B: in.Send.Encrypted},
|
Encrypt: &zfs.NilBool{B: in.Send.Encrypted},
|
||||||
JobID: jobID,
|
DisableIncrementalStepHolds: in.Send.StepHolds.DisableIncremental,
|
||||||
|
JobID: jobID,
|
||||||
}
|
}
|
||||||
m.plannerPolicy = &logic.PlannerPolicy{
|
m.plannerPolicy = &logic.PlannerPolicy{
|
||||||
EncryptedSend: logic.TriFromBool(in.Send.Encrypted),
|
EncryptedSend: logic.TriFromBool(in.Send.Encrypted),
|
||||||
@@ -376,9 +377,21 @@ func (j *ActiveSide) SenderConfig() *endpoint.SenderConfig {
|
|||||||
return push.senderConfig
|
return push.senderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The active side of a replication uses one end (sender or receiver)
|
||||||
|
// directly by method invocation, without going through a transport that
|
||||||
|
// provides a client identity.
|
||||||
|
// However, in order to avoid the need to distinguish between direct-method-invocating
|
||||||
|
// clients and RPC client, we use an invalid client identity as a sentinel value.
|
||||||
|
func FakeActiveSideDirectMethodInvocationClientIdentity(jobId endpoint.JobID) string {
|
||||||
|
return fmt.Sprintf("<local><active><job><client><identity><job=%q>", jobId.String())
|
||||||
|
}
|
||||||
|
|
||||||
func (j *ActiveSide) Run(ctx context.Context) {
|
func (j *ActiveSide) Run(ctx context.Context) {
|
||||||
ctx, endTask := trace.WithTaskAndSpan(ctx, "active-side-job", j.Name())
|
ctx, endTask := trace.WithTaskAndSpan(ctx, "active-side-job", j.Name())
|
||||||
defer endTask()
|
defer endTask()
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, endpoint.ClientIdentityKey, FakeActiveSideDirectMethodInvocationClientIdentity(j.name))
|
||||||
|
|
||||||
log := GetLogger(ctx)
|
log := GetLogger(ctx)
|
||||||
|
|
||||||
defer log.Info("job exiting")
|
defer log.Info("job exiting")
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zrepl/zrepl/endpoint"
|
||||||
|
"github.com/zrepl/zrepl/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFakeActiveSideDirectMethodInvocationClientIdentityDoesNotPassValidityTest(t *testing.T) {
|
||||||
|
jobid, err := endpoint.MakeJobID("validjobname")
|
||||||
|
require.NoError(t, err)
|
||||||
|
clientIdentity := FakeActiveSideDirectMethodInvocationClientIdentity(jobid)
|
||||||
|
t.Logf("%v", clientIdentity)
|
||||||
|
err = transport.ValidateClientIdentity(clientIdentity)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
@@ -79,9 +79,10 @@ func modeSourceFromConfig(g *config.Global, in *config.SourceJob, jobID endpoint
|
|||||||
return nil, errors.Wrap(err, "cannot build filesystem filter")
|
return nil, errors.Wrap(err, "cannot build filesystem filter")
|
||||||
}
|
}
|
||||||
m.senderConfig = &endpoint.SenderConfig{
|
m.senderConfig = &endpoint.SenderConfig{
|
||||||
FSF: fsf,
|
FSF: fsf,
|
||||||
Encrypt: &zfs.NilBool{B: in.Send.Encrypted},
|
Encrypt: &zfs.NilBool{B: in.Send.Encrypted},
|
||||||
JobID: jobID,
|
DisableIncrementalStepHolds: in.Send.StepHolds.DisableIncremental,
|
||||||
|
JobID: jobID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.snapper, err = snapper.FromConfig(g, fsf, in.Snapshotting); err != nil {
|
if m.snapper, err = snapper.FromConfig(g, fsf, in.Snapshotting); err != nil {
|
||||||
|
|||||||
@@ -175,6 +175,8 @@ func (j *SnapJob) doPrune(ctx context.Context) {
|
|||||||
FSF: j.fsfilter,
|
FSF: j.fsfilter,
|
||||||
// FIXME encryption setting is irrelevant for SnapJob because the endpoint is only used as pruner.Target
|
// FIXME encryption setting is irrelevant for SnapJob because the endpoint is only used as pruner.Target
|
||||||
Encrypt: &zfs.NilBool{B: true},
|
Encrypt: &zfs.NilBool{B: true},
|
||||||
|
// FIXME DisableIncrementalStepHolds setting is irrelevant for SnapJob because the endpoint is only used as pruner.Target
|
||||||
|
DisableIncrementalStepHolds: false,
|
||||||
})
|
})
|
||||||
j.pruner = j.prunerFactory.BuildLocalPruner(ctx, sender, alwaysUpToDateReplicationCursorHistory{sender})
|
j.pruner = j.prunerFactory.BuildLocalPruner(ctx, sender, alwaysUpToDateReplicationCursorHistory{sender})
|
||||||
log.Info("start pruning")
|
log.Info("start pruning")
|
||||||
|
|||||||
+26
-16
@@ -35,18 +35,23 @@ This is a big one! Headlining features:
|
|||||||
* **Resumable Send & Recv Support**
|
* **Resumable Send & Recv Support**
|
||||||
No knobs required, automatically used where supported.
|
No knobs required, automatically used where supported.
|
||||||
* **Hold-Protected Send & Recv**
|
* **Hold-Protected Send & Recv**
|
||||||
Automatic ZFS holds to ensure that we can always resume a replication step.
|
Automatic ZFS holds to ensure that we can always use resumable send&recv for a replication step.
|
||||||
* **Encrypted Send & Recv Support** for OpenZFS native encryption.
|
* **Encrypted Send & Recv Support** for OpenZFS native encryption,
|
||||||
:ref:`Configurable <job-send-options>` at the job level, i.e., for all filesystems a job is responsible for.
|
:ref:`configurable <job-send-options>` at the job level, i.e., for all filesystems a job is responsible for.
|
||||||
* **Receive-side hold on last received dataset**
|
* **Receive-side hold on last received dataset**
|
||||||
The counterpart to the replication cursor bookmark on the send-side.
|
The counterpart to the replication cursor bookmark on the send-side.
|
||||||
Ensures that incremental replication will always be possible between a sender and receiver.
|
Ensures that incremental replication will always be possible between a sender and receiver.
|
||||||
|
|
||||||
Actual changelog:
|
.. TIP::
|
||||||
|
|
||||||
* |break_config| **more restrictive job names than in prior zrepl versions**
|
We highly recommend studying the :ref:`overview section of the configuration chapter <overview-how-replication-works>` to understand how replication works.
|
||||||
Starting with this version, job names are going to be embedded into ZFS holds and bookmark names.
|
|
||||||
|
|
||||||
|
Additional changelog:
|
||||||
|
|
||||||
|
* |break| |break_config| **more restrictive job names than in prior zrepl versions**
|
||||||
|
Starting with this version, job names are going to be embedded into ZFS holds and bookmark names (see :ref:`here<replication-cursor-and-last-received-hold>` and :ref:`here<step-holds-and-bookmarks>`).
|
||||||
|
Therefore you might need to adjust your job names.
|
||||||
|
**Note that jobs** :issue:`cannot be renamed easily<327>` **once you start using zrepl 0.3.**
|
||||||
* |break| |mig| replication cursor representation changed
|
* |break| |mig| replication cursor representation changed
|
||||||
|
|
||||||
* zrepl now manages the :ref:`replication cursor bookmark <replication-cursor-and-last-received-hold>` per job-filesystem tuple instead of a single replication cursor per filesystem.
|
* zrepl now manages the :ref:`replication cursor bookmark <replication-cursor-and-last-received-hold>` per job-filesystem tuple instead of a single replication cursor per filesystem.
|
||||||
@@ -56,16 +61,29 @@ Actual changelog:
|
|||||||
* Run ``zrepl migrate replication-cursor:v1-v2`` to safely destroy old-format cursors.
|
* Run ``zrepl migrate replication-cursor:v1-v2`` to safely destroy old-format cursors.
|
||||||
The migration will ensure that only those old-format cursors are destroyed that have been superseeded by new-format cursors.
|
The migration will ensure that only those old-format cursors are destroyed that have been superseeded by new-format cursors.
|
||||||
|
|
||||||
|
* |feature| New option ``listen_freebind`` (tcp, tls, prometheus listener)
|
||||||
|
* |feature| :issue:`<265>` transport/tcp: support for CIDR masks in client IP whitelist
|
||||||
|
* |feature| documented subcommand to generate ``bash`` and ``zsh`` completions
|
||||||
|
* |feature| :issue:`<307>` ``chrome://trace`` -compatible activity tracing of zrepl daemon activity
|
||||||
|
* |feature| logging: trace IDs for better log entry correlation with concurrent replication jobs
|
||||||
|
* |feature| experimental environment variable for parallel replication (see :issue:`<306>` )
|
||||||
* |bugfix| missing logger context vars in control connection handlers
|
* |bugfix| missing logger context vars in control connection handlers
|
||||||
* |bugfix| improved error messages on ``zfs send`` errors
|
* |bugfix| improved error messages on ``zfs send`` errors
|
||||||
* |feature| New option ``listen_freebind`` (tcp, tls, prometheus listener)
|
|
||||||
* |bugfix| |docs| snapshotting: clarify sync-up behavior and warn about filesystems
|
* |bugfix| |docs| snapshotting: clarify sync-up behavior and warn about filesystems
|
||||||
|
* |bugfix| transport/ssh: do not leak zombie ssh process on connection failures
|
||||||
that will not be snapshotted until the sync-up phase is over
|
that will not be snapshotted until the sync-up phase is over
|
||||||
|
* |docs| Installation: :ref:`FreeBSD jail with iocage <installation-freebsd-jail-with-iocage>`
|
||||||
* |docs| Document new replication features in the :ref:`config overview <overview-how-replication-works>` and :repomasterlink:`replication/design.md`.
|
* |docs| Document new replication features in the :ref:`config overview <overview-how-replication-works>` and :repomasterlink:`replication/design.md`.
|
||||||
* |feature| documented subcommand to generate ``bash`` and ``zsh`` completions
|
|
||||||
* **[MAINTAINER NOTICE]** New platform tests in this version, please make sure you run them for your distro!
|
* **[MAINTAINER NOTICE]** New platform tests in this version, please make sure you run them for your distro!
|
||||||
* **[MAINTAINER NOTICE]** Please add the shell completions to the zrepl packages.
|
* **[MAINTAINER NOTICE]** Please add the shell completions to the zrepl packages.
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
| zrepl is a spare-time project primarily developed by `Christian Schwarz <https://cschwarz.com>`_.
|
||||||
|
| You can support maintenance and feature development through one of the following services:
|
||||||
|
| |Donate via Patreon| |Donate via Liberapay| |Donate via PayPal|
|
||||||
|
| Note that PayPal processing fees are relatively high for small donations.
|
||||||
|
| For SEPA wire transfer and **commercial support**, please `contact Christian directly <https://cschwarz.com>`_.
|
||||||
|
|
||||||
0.2.1
|
0.2.1
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -102,14 +120,6 @@ Actual changelog:
|
|||||||
The pool name ``zreplplatformtest`` is reserved for this use case.
|
The pool name ``zreplplatformtest`` is reserved for this use case.
|
||||||
Only run ``make platformtest`` on test systems, e.g. a FreeBSD VM image.
|
Only run ``make platformtest`` on test systems, e.g. a FreeBSD VM image.
|
||||||
|
|
||||||
.. NOTE::
|
|
||||||
| zrepl is a spare-time project primarily developed by `Christian Schwarz <https://cschwarz.com>`_.
|
|
||||||
| You can support maintenance and feature development through one of the following services:
|
|
||||||
| |Donate via Patreon| |Donate via Liberapay| |Donate via PayPal|
|
|
||||||
| Note that PayPal processing fees are relatively high for small donations.
|
|
||||||
| For SEPA wire transfer and **commercial support**, please `contact Christian directly <https://cschwarz.com>`_.
|
|
||||||
|
|
||||||
|
|
||||||
0.1.1
|
0.1.1
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Job Type ``push``
|
|||||||
* - ``type``
|
* - ``type``
|
||||||
- = ``push``
|
- = ``push``
|
||||||
* - ``name``
|
* - ``name``
|
||||||
- unique name of the job
|
- unique name of the job :issue:`(must not change)<327>`
|
||||||
* - ``connect``
|
* - ``connect``
|
||||||
- |connect-transport|
|
- |connect-transport|
|
||||||
* - ``filesystems``
|
* - ``filesystems``
|
||||||
@@ -47,7 +47,7 @@ Job Type ``sink``
|
|||||||
* - ``type``
|
* - ``type``
|
||||||
- = ``sink``
|
- = ``sink``
|
||||||
* - ``name``
|
* - ``name``
|
||||||
- unique name of the job
|
- unique name of the job :issue:`(must not change)<327>`
|
||||||
* - ``serve``
|
* - ``serve``
|
||||||
- |serve-transport|
|
- |serve-transport|
|
||||||
* - ``root_fs``
|
* - ``root_fs``
|
||||||
@@ -70,7 +70,7 @@ Job Type ``pull``
|
|||||||
* - ``type``
|
* - ``type``
|
||||||
- = ``pull``
|
- = ``pull``
|
||||||
* - ``name``
|
* - ``name``
|
||||||
- unique name of the job
|
- unique name of the job :issue:`(must not change)<327>`
|
||||||
* - ``connect``
|
* - ``connect``
|
||||||
- |connect-transport|
|
- |connect-transport|
|
||||||
* - ``root_fs``
|
* - ``root_fs``
|
||||||
@@ -98,7 +98,7 @@ Job Type ``source``
|
|||||||
* - ``type``
|
* - ``type``
|
||||||
- = ``source``
|
- = ``source``
|
||||||
* - ``name``
|
* - ``name``
|
||||||
- unique name of the job
|
- unique name of the job :issue:`(must not change)<327>`
|
||||||
* - ``serve``
|
* - ``serve``
|
||||||
- |serve-transport|
|
- |serve-transport|
|
||||||
* - ``filesystems``
|
* - ``filesystems``
|
||||||
@@ -137,7 +137,7 @@ Job type that only takes snapshots and performs pruning on the local machine.
|
|||||||
* - ``type``
|
* - ``type``
|
||||||
- = ``snap``
|
- = ``snap``
|
||||||
* - ``name``
|
* - ``name``
|
||||||
- unique name of the job
|
- unique name of the job :issue:`(must not change)<327>`
|
||||||
* - ``filesystems``
|
* - ``filesystems``
|
||||||
- |filter-spec| for filesystems to be snapshotted
|
- |filter-spec| for filesystems to be snapshotted
|
||||||
* - ``snapshotting``
|
* - ``snapshotting``
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ A *job* is the unit of activity tracked by the zrepl daemon.
|
|||||||
The ``type`` of a job determines its role in a replication setup and in snapshot management.
|
The ``type`` of a job determines its role in a replication setup and in snapshot management.
|
||||||
Jobs are identified by their ``name``, both in log files and the ``zrepl status`` command.
|
Jobs are identified by their ``name``, both in log files and the ``zrepl status`` command.
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
The job name is persisted in several places on disk and thus :issue:`cannot be changed easily<327>`.
|
||||||
|
|
||||||
|
|
||||||
Replication always happens between a pair of jobs: one is the **active side**, and one the **passive side**.
|
Replication always happens between a pair of jobs: one is the **active side**, and one the **passive side**.
|
||||||
The active side connects to the passive side using a :ref:`transport <transport>` and starts executing the replication logic.
|
The active side connects to the passive side using a :ref:`transport <transport>` and starts executing the replication logic.
|
||||||
The passive side responds to requests from the active side after checking its permissions.
|
The passive side responds to requests from the active side after checking its permissions.
|
||||||
@@ -99,7 +103,7 @@ One of the major design goals of the replication module is to avoid any duplicat
|
|||||||
As such, the code works on abstract senders and receiver **endpoints**, where typically one will be implemented by a local program object and the other is an RPC client instance.
|
As such, the code works on abstract senders and receiver **endpoints**, where typically one will be implemented by a local program object and the other is an RPC client instance.
|
||||||
Regardless of push- or pull-style setup, the logic executes on the active side, i.e. in the ``push`` or ``pull`` job.
|
Regardless of push- or pull-style setup, the logic executes on the active side, i.e. in the ``push`` or ``pull`` job.
|
||||||
|
|
||||||
The following steps take place during replication and can be monitored using the ``zrepl status`` subcommand:
|
The following high-level steps take place during replication and can be monitored using the ``zrepl status`` subcommand:
|
||||||
|
|
||||||
* Plan the replication:
|
* Plan the replication:
|
||||||
|
|
||||||
@@ -107,9 +111,9 @@ The following steps take place during replication and can be monitored using the
|
|||||||
* Build the **replication plan**
|
* Build the **replication plan**
|
||||||
|
|
||||||
* Per filesystem, compute a diff between sender and receiver snapshots
|
* Per filesystem, compute a diff between sender and receiver snapshots
|
||||||
* Build a list of replication steps
|
* Build a list of **replication steps**
|
||||||
|
|
||||||
* If possible, use incremental and resumable sends (``zfs send -i``)
|
* If possible, use incremental and resumable sends
|
||||||
* Otherwise, use full send of most recent snapshot on sender
|
* Otherwise, use full send of most recent snapshot on sender
|
||||||
|
|
||||||
* Retry on errors that are likely temporary (i.e. network failures).
|
* Retry on errors that are likely temporary (i.e. network failures).
|
||||||
@@ -128,16 +132,8 @@ The following steps take place during replication and can be monitored using the
|
|||||||
|
|
||||||
The idea behind the execution order of replication steps is that if the sender snapshots all filesystems simultaneously at fixed intervals, the receiver will have all filesystems snapshotted at time ``T1`` before the first snapshot at ``T2 = T1 + $interval`` is replicated.
|
The idea behind the execution order of replication steps is that if the sender snapshots all filesystems simultaneously at fixed intervals, the receiver will have all filesystems snapshotted at time ``T1`` before the first snapshot at ``T2 = T1 + $interval`` is replicated.
|
||||||
|
|
||||||
.. _replication-cursor-and-last-received-hold:
|
Placeholder Filesystems
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
**Replication cursor** bookmark and **last-received-hold** are managed by zrepl to ensure that future replications can always be done incrementally:
|
|
||||||
the replication cursor is a send-side bookmark of the most recent successfully replicated snapshot,
|
|
||||||
and the last-received-hold is a hold of that snapshot on the receiving side.
|
|
||||||
The replication cursor has the format ``#zrepl_CUSOR_G_<GUID>_J_<JOBNAME>``.
|
|
||||||
The last-received-hold tag has the format ``#zrepl_last_received_J_<JOBNAME>``.
|
|
||||||
Encoding the job name in the names ensures that multiple sending jobs can replicate the same filesystem to different receivers without interference.
|
|
||||||
The ``zrepl holds list`` provides a listing of all bookmarks and holds managed by zrepl.
|
|
||||||
|
|
||||||
.. _replication-placeholder-property:
|
.. _replication-placeholder-property:
|
||||||
|
|
||||||
**Placeholder filesystems** on the receiving side are regular ZFS filesystems with the placeholder property ``zrepl:placeholder=on``.
|
**Placeholder filesystems** on the receiving side are regular ZFS filesystems with the placeholder property ``zrepl:placeholder=on``.
|
||||||
@@ -148,16 +144,83 @@ Thus, zrepl creates the parent filesystems as placeholders on the receiving side
|
|||||||
If at some point ``S/H`` and ``S`` shall be replicated, the receiving side invalidates the placeholder flag automatically.
|
If at some point ``S/H`` and ``S`` shall be replicated, the receiving side invalidates the placeholder flag automatically.
|
||||||
The ``zrepl test placeholder`` command can be used to check whether a filesystem is a placeholder.
|
The ``zrepl test placeholder`` command can be used to check whether a filesystem is a placeholder.
|
||||||
|
|
||||||
|
ZFS Background Knowledge
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This section gives some background knowledge about ZFS features that zrepl uses to guarantee that
|
||||||
|
**incremental replication is always possible and that started replication steps can always be resumed if they are interrupted.**
|
||||||
|
|
||||||
|
**ZFS Send Modes & Bookmarks**
|
||||||
|
ZFS supports full sends (``zfs send fs@to``) and incremental sends (``zfs send -i @from fs@to``).
|
||||||
|
Full sends are used to create a new filesystem on the receiver with the send-side state of ``fs@to``.
|
||||||
|
Incremental sends only transfer the delta between ``@from`` and ``@to``.
|
||||||
|
Incremental sends require that ``@from`` be present on the receiving side when receiving the incremental stream.
|
||||||
|
Incremental sends can also use a ZFS bookmark as *from* on the sending side (``zfs send -i #bm_from fs@to``), where ``#bm_from`` was created using ``zfs bookmark fs@from fs#bm_from``.
|
||||||
|
The receiving side must always have the actual snapshot ``@from``, regardless of whether the sending side uses ``@from`` or a bookmark of it.
|
||||||
|
|
||||||
|
**Resumable Send & Recv**
|
||||||
|
The ``-s`` flag for ``zfs recv`` tells zfs to save the partially received send stream in case it is interrupted.
|
||||||
|
To resume the replication, the receiving side filesystem's ``receive_resume_token`` must be passed to a new ``zfs send -t <value> | zfs recv`` command.
|
||||||
|
A full send can only be resumed if ``@to`` still exists.
|
||||||
|
An incremental send can only be resumed if ``@to`` still exists *and* either ``@from`` still exists *or* a bookmark ``#fbm`` of ``@from`` still exists.
|
||||||
|
|
||||||
|
**ZFS Holds**
|
||||||
|
ZFS holds prevent a snapshot from being deleted through ``zfs destroy``, letting the destroy fail with a ``datset is busy`` error.
|
||||||
|
Holds are created and referred to by a user-defined *tag*. They can be thought of as a named, persistent lock on the snapshot.
|
||||||
|
|
||||||
|
.. _replication-cursor-and-last-received-hold:
|
||||||
|
|
||||||
|
Guaranteeing That Incremental Sends Are Always Possible
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Replication cursor** bookmark and **last-received-hold** are managed by zrepl to ensure that future replications can always be done incrementally.
|
||||||
|
The replication cursor is a send-side bookmark of the most recent successfully replicated snapshot,
|
||||||
|
and the last-received-hold is a hold of that snapshot on the receiving side.
|
||||||
|
Both are moved aomically after the receiving side has confirmed that a replication step is complete.
|
||||||
|
|
||||||
|
The replication cursor has the format ``#zrepl_CUSOR_G_<GUID>_J_<JOBNAME>``.
|
||||||
|
The last-received-hold tag has the format ``zrepl_last_received_J_<JOBNAME>``.
|
||||||
|
Encoding the job name in the names ensures that multiple sending jobs can replicate the same filesystem to different receivers without interference.
|
||||||
|
|
||||||
|
The ``zrepl zfs-abstraction list`` command provides a listing of all bookmarks and holds managed by zrepl.
|
||||||
|
|
||||||
|
.. _step-holds-and-bookmarks:
|
||||||
|
|
||||||
|
Guaranteeing That Sends Are Always Resumable
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Step holds** are zfs holds managed by zrepl to ensure that a replication step can always be resumed if it is interrupted, e.g., due to network outage.
|
||||||
|
zrepl creates step holds before it attempts a replication step and releases them after the receiver confirms that the replication step is complete.
|
||||||
|
For an initial replication ``full @initial_snap``, zrepl puts a zfs hold on ``@initial_snap``.
|
||||||
|
For an incremental send ``@from -> @to``, zrepl puts a zfs hold on both ``@from`` and ``@to``.
|
||||||
|
Note that ``@from`` is not strictly necessary for resumability -- a bookmark on the sending side would be sufficient --, but size-estimation in currently used OpenZFS versions only works if ``@from`` is a snapshot.
|
||||||
|
|
||||||
|
The hold tag has the format ``zrepl_STEP_J_<JOBNAME>``.
|
||||||
|
A job only ever has one active send per filesystem.
|
||||||
|
Thus, there are never more than two step holds for a given pair of ``(job,filesystem)``.
|
||||||
|
|
||||||
|
**Step bookmarks** are zrepl's equivalent for holds on bookmarks (ZFS does not support putting holds on bookmarks).
|
||||||
|
They are intended for a situation where a replication step uses a bookmark ``#bm`` as incremental ``from`` that is not managed by zrepl.
|
||||||
|
To ensure resumability, zrepl copies ``#bm`` to step bookmark ``#zrepl_STEP_G_<GUID>_J_<JOBNAME>``.
|
||||||
|
If the replication is interrupted and ``#bm`` is deleted by the user, the step bookmark remains as an incremental source for the resumable send.
|
||||||
|
Note that zrepl does not yet support creating step bookmarks because the `corresponding ZFS feature for copying bookmarks <https://github.com/openzfs/zfs/pull/9571>`_ is not yet widely available .
|
||||||
|
Subscribe to zrepl :issue:`326` for details.
|
||||||
|
|
||||||
|
The ``zrepl zfs-abstraction list`` command provides a listing of all bookmarks and holds managed by zrepl.
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
|
||||||
|
More details can be found in the design document :repomasterlink:`replication/design.md`.
|
||||||
|
|
||||||
|
Limitations
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
.. ATTENTION::
|
.. ATTENTION::
|
||||||
|
|
||||||
Currently, zrepl does not replicate filesystem properties.
|
Currently, zrepl does not replicate filesystem properties.
|
||||||
When receiving a filesystem, it is never mounted (`-u` flag) and `mountpoint=none` is set.
|
When receiving a filesystem, it is never mounted (`-u` flag) and `mountpoint=none` is set.
|
||||||
This is temporary and being worked on :issue:`24`.
|
This is temporary and being worked on :issue:`24`.
|
||||||
|
|
||||||
.. NOTE::
|
|
||||||
|
|
||||||
More details can be found in the design document :repomasterlink:`replication/design.md`.
|
|
||||||
|
|
||||||
|
|
||||||
.. _jobs-multiple-jobs:
|
.. _jobs-multiple-jobs:
|
||||||
|
|
||||||
@@ -177,7 +240,7 @@ Multiple Jobs & More than 2 Machines
|
|||||||
If you would like to see improvements to multi-job setups, please `open an issue on GitHub <https://github.com/zrepl/zrepl/issues/new>`_.
|
If you would like to see improvements to multi-job setups, please `open an issue on GitHub <https://github.com/zrepl/zrepl/issues/new>`_.
|
||||||
|
|
||||||
No Overlapping
|
No Overlapping
|
||||||
~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Jobs run independently of each other.
|
Jobs run independently of each other.
|
||||||
If two jobs match the same filesystem with their ``filesystems`` filter, they will operate on that filesystem independently and potentially in parallel.
|
If two jobs match the same filesystem with their ``filesystems`` filter, they will operate on that filesystem independently and potentially in parallel.
|
||||||
@@ -185,14 +248,14 @@ For example, if job A prunes snapshots that job B is planning to replicate, the
|
|||||||
However, the next replication attempt will re-examine the situation from scratch and should work.
|
However, the next replication attempt will re-examine the situation from scratch and should work.
|
||||||
|
|
||||||
N push jobs to 1 sink
|
N push jobs to 1 sink
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The :ref:`sink job <job-sink>` namespaces by client identity.
|
The :ref:`sink job <job-sink>` namespaces by client identity.
|
||||||
It is thus safe to push to one sink job with different client identities.
|
It is thus safe to push to one sink job with different client identities.
|
||||||
If the push jobs have the same client identity, the filesystems matched by the push jobs must be disjoint to avoid races.
|
If the push jobs have the same client identity, the filesystems matched by the push jobs must be disjoint to avoid races.
|
||||||
|
|
||||||
N pull jobs from 1 source
|
N pull jobs from 1 source
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Multiple pull jobs pulling from the same source have potential for race conditions during pruning:
|
Multiple pull jobs pulling from the same source have potential for race conditions during pruning:
|
||||||
each pull job prunes the source side independently, causing replication-prune and prune-prune races.
|
each pull job prunes the source side independently, causing replication-prune and prune-prune races.
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ Send Options
|
|||||||
filesystems: ...
|
filesystems: ...
|
||||||
send:
|
send:
|
||||||
encrypted: true
|
encrypted: true
|
||||||
|
step_holds:
|
||||||
|
disable_incremental: false
|
||||||
...
|
...
|
||||||
|
|
||||||
:ref:`Source<job-source>` and :ref:`push<job-push>` jobs have an optional ``send`` configuration section.
|
:ref:`Source<job-source>` and :ref:`push<job-push>` jobs have an optional ``send`` configuration section.
|
||||||
@@ -34,6 +36,17 @@ Filesystems matched by ``filesystems`` that are not encrypted are not sent and w
|
|||||||
|
|
||||||
If ``encryption=false``, zrepl expects that filesystems matching ``filesystems`` are not encrypted or have loaded encryption keys.
|
If ``encryption=false``, zrepl expects that filesystems matching ``filesystems`` are not encrypted or have loaded encryption keys.
|
||||||
|
|
||||||
|
|
||||||
|
``step_holds.disable_incremental`` option
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
The ``step_holds.disable_incremental`` variable controls whether the creation of :ref:`step holds <step-holds-and-bookmarks>` should be disabled for incremental replication.
|
||||||
|
The default value is ``false``.
|
||||||
|
|
||||||
|
Disabling step holds has the disadvantage that steps :ref:`might not be resumable <step-holds-and-bookmarks>` if interrupted.
|
||||||
|
However, the advantage and :issue:`reason for existence <288>` of this flag is that it allows the pruner to delete snapshots of interrupted replication steps
|
||||||
|
which is useful if replication happens so rarely (or fails so frequently) that the amount of disk space exclusively referenced by the step's snapshots becomes intolerable.
|
||||||
|
|
||||||
.. _job-recv-options:
|
.. _job-recv-options:
|
||||||
|
|
||||||
Recv Options
|
Recv Options
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ We would like to thank the following people and organizations for supporting zre
|
|||||||
|
|
||||||
<div class="fa fa-code" style="width: 1em;"></div>
|
<div class="fa fa-code" style="width: 1em;"></div>
|
||||||
|
|
||||||
|
* |supporter-std| Henning Kessler
|
||||||
|
* |supporter-std| `John Ramsden <https://github.com/johnramsden>`_
|
||||||
|
* |supporter-std| `DrLuke <https://github.com/drluke>`_
|
||||||
* |supporter-gold| `Mateusz Kwiatkowski (runhyve.app) <https://runhyve.app>`_
|
* |supporter-gold| `Mateusz Kwiatkowski (runhyve.app) <https://runhyve.app>`_
|
||||||
* |supporter-std| `Gaelan D'costa <https://www.robot-disco.net>`_
|
* |supporter-std| `Gaelan D'costa <https://www.robot-disco.net>`_
|
||||||
* |supporter-std| `Tenzin Lhakhang <https://confluence.tenzin.io>`_
|
* |supporter-std| `Tenzin Lhakhang <https://confluence.tenzin.io>`_
|
||||||
|
|||||||
+90
-194
@@ -7,8 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
|
||||||
|
|
||||||
|
"github.com/kr/pretty"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zrepl/zrepl/daemon/logging/trace"
|
"github.com/zrepl/zrepl/daemon/logging/trace"
|
||||||
|
|
||||||
@@ -21,9 +21,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SenderConfig struct {
|
type SenderConfig struct {
|
||||||
FSF zfs.DatasetFilter
|
FSF zfs.DatasetFilter
|
||||||
Encrypt *zfs.NilBool
|
Encrypt *zfs.NilBool
|
||||||
JobID JobID
|
DisableIncrementalStepHolds bool
|
||||||
|
JobID JobID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SenderConfig) Validate() error {
|
func (c *SenderConfig) Validate() error {
|
||||||
@@ -39,9 +40,10 @@ func (c *SenderConfig) Validate() error {
|
|||||||
|
|
||||||
// Sender implements replication.ReplicationEndpoint for a sending side
|
// Sender implements replication.ReplicationEndpoint for a sending side
|
||||||
type Sender struct {
|
type Sender struct {
|
||||||
FSFilter zfs.DatasetFilter
|
FSFilter zfs.DatasetFilter
|
||||||
encrypt *zfs.NilBool
|
encrypt *zfs.NilBool
|
||||||
jobId JobID
|
disableIncrementalStepHolds bool
|
||||||
|
jobId JobID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSender(conf SenderConfig) *Sender {
|
func NewSender(conf SenderConfig) *Sender {
|
||||||
@@ -49,9 +51,10 @@ func NewSender(conf SenderConfig) *Sender {
|
|||||||
panic("invalid config" + err.Error())
|
panic("invalid config" + err.Error())
|
||||||
}
|
}
|
||||||
return &Sender{
|
return &Sender{
|
||||||
FSFilter: conf.FSF,
|
FSFilter: conf.FSF,
|
||||||
encrypt: conf.Encrypt,
|
encrypt: conf.Encrypt,
|
||||||
jobId: conf.JobID,
|
disableIncrementalStepHolds: conf.DisableIncrementalStepHolds,
|
||||||
|
jobId: conf.JobID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,119 +120,8 @@ func (s *Sender) ListFilesystemVersions(ctx context.Context, r *pdu.ListFilesyst
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Sender) HintMostRecentCommonAncestor(ctx context.Context, r *pdu.HintMostRecentCommonAncestorReq) (*pdu.HintMostRecentCommonAncestorRes, error) {
|
var maxConcurrentZFSSend = envconst.Int64("ZREPL_ENDPOINT_MAX_CONCURRENT_SEND", 10)
|
||||||
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
var maxConcurrentZFSSendSemaphore = semaphore.New(maxConcurrentZFSSend)
|
||||||
|
|
||||||
fsp, err := p.filterCheckFS(r.GetFilesystem())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fs := fsp.ToString()
|
|
||||||
|
|
||||||
log := getLogger(ctx).WithField("fs", fs).WithField("hinted_most_recent", fmt.Sprintf("%#v", r.GetSenderVersion()))
|
|
||||||
|
|
||||||
log.WithField("full_hint", r).Debug("full hint")
|
|
||||||
|
|
||||||
if r.GetSenderVersion() == nil {
|
|
||||||
// no common ancestor found, likely due to failed prior replication attempt
|
|
||||||
// => release stale step holds to prevent them from accumulating
|
|
||||||
// (they can accumulate on initial replication because each inital replication step might hold a different `to`)
|
|
||||||
// => replication cursors cannot accumulate because we always _move_ the replication cursor
|
|
||||||
log.Debug("releasing all step holds on the filesystem")
|
|
||||||
TryReleaseStepStaleFS(ctx, fs, p.jobId)
|
|
||||||
return &pdu.HintMostRecentCommonAncestorRes{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// we were hinted a specific common ancestor
|
|
||||||
|
|
||||||
mostRecentVersion, err := sendArgsFromPDUAndValidateExistsAndGetVersion(ctx, fs, r.GetSenderVersion())
|
|
||||||
if err != nil {
|
|
||||||
msg := "HintMostRecentCommonAncestor rpc with nonexistent most recent version"
|
|
||||||
log.Warn(msg)
|
|
||||||
return nil, errors.Wrap(err, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// move replication cursor to this position
|
|
||||||
destroyedCursors, err := MoveReplicationCursor(ctx, fs, mostRecentVersion, p.jobId)
|
|
||||||
if err == zfs.ErrBookmarkCloningNotSupported {
|
|
||||||
log.Debug("not creating replication cursor from bookmark because ZFS does not support it")
|
|
||||||
// fallthrough
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot set replication cursor to hinted version")
|
|
||||||
}
|
|
||||||
|
|
||||||
// take care of stale step holds
|
|
||||||
log.WithField("step-holds-cleanup-mode", senderHintMostRecentCommonAncestorStepCleanupMode).
|
|
||||||
Debug("taking care of possibly stale step holds")
|
|
||||||
doStepCleanup := false
|
|
||||||
var stepCleanupSince *CreateTXGRangeBound
|
|
||||||
switch senderHintMostRecentCommonAncestorStepCleanupMode {
|
|
||||||
case StepCleanupNoCleanup:
|
|
||||||
doStepCleanup = false
|
|
||||||
case StepCleanupRangeSinceUnbounded:
|
|
||||||
doStepCleanup = true
|
|
||||||
stepCleanupSince = nil
|
|
||||||
case StepCleanupRangeSinceReplicationCursor:
|
|
||||||
doStepCleanup = true
|
|
||||||
// Use the destroyed replication cursors as indicator how far the previous replication got.
|
|
||||||
// To be precise: We limit the amount of visisted snapshots to exactly those snapshots
|
|
||||||
// created since the last successful replication cursor movement (i.e. last successful replication step)
|
|
||||||
//
|
|
||||||
// If we crash now, we'll leak the step we are about to release, but the performance gain
|
|
||||||
// of limiting the amount of snapshots we visit makes up for that.
|
|
||||||
// Users have the `zrepl holds release-stale` command to cleanup leaked step holds.
|
|
||||||
for _, destroyed := range destroyedCursors {
|
|
||||||
if stepCleanupSince == nil {
|
|
||||||
stepCleanupSince = &CreateTXGRangeBound{
|
|
||||||
CreateTXG: destroyed.GetCreateTXG(),
|
|
||||||
Inclusive: &zfs.NilBool{B: true},
|
|
||||||
}
|
|
||||||
} else if destroyed.GetCreateTXG() < stepCleanupSince.CreateTXG {
|
|
||||||
stepCleanupSince.CreateTXG = destroyed.GetCreateTXG()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(senderHintMostRecentCommonAncestorStepCleanupMode)
|
|
||||||
}
|
|
||||||
if !doStepCleanup {
|
|
||||||
log.Info("skipping cleanup of prior invocations' step holds due to environment variable setting")
|
|
||||||
} else {
|
|
||||||
if err := ReleaseStepCummulativeInclusive(ctx, fs, stepCleanupSince, mostRecentVersion, p.jobId); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "cannot cleanup prior invocation's step holds and bookmarks")
|
|
||||||
} else {
|
|
||||||
log.Info("step hold cleanup done")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pdu.HintMostRecentCommonAncestorRes{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HintMostRecentCommonAncestorStepCleanupMode struct{ string }
|
|
||||||
|
|
||||||
var (
|
|
||||||
StepCleanupRangeSinceReplicationCursor = HintMostRecentCommonAncestorStepCleanupMode{"range-since-replication-cursor"}
|
|
||||||
StepCleanupRangeSinceUnbounded = HintMostRecentCommonAncestorStepCleanupMode{"range-since-unbounded"}
|
|
||||||
StepCleanupNoCleanup = HintMostRecentCommonAncestorStepCleanupMode{"no-cleanup"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m HintMostRecentCommonAncestorStepCleanupMode) String() string { return string(m.string) }
|
|
||||||
func (m *HintMostRecentCommonAncestorStepCleanupMode) Set(s string) error {
|
|
||||||
switch s {
|
|
||||||
case StepCleanupRangeSinceReplicationCursor.String():
|
|
||||||
*m = StepCleanupRangeSinceReplicationCursor
|
|
||||||
case StepCleanupRangeSinceUnbounded.String():
|
|
||||||
*m = StepCleanupRangeSinceUnbounded
|
|
||||||
case StepCleanupNoCleanup.String():
|
|
||||||
*m = StepCleanupNoCleanup
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown step cleanup mode %q", s)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var senderHintMostRecentCommonAncestorStepCleanupMode = *envconst.Var("ZREPL_ENDPOINT_SENDER_HINT_MOST_RECENT_STEP_HOLD_CLEANUP_MODE", &StepCleanupRangeSinceReplicationCursor).(*HintMostRecentCommonAncestorStepCleanupMode)
|
|
||||||
|
|
||||||
var maxConcurrentZFSSendSemaphore = semaphore.New(envconst.Int64("ZREPL_ENDPOINT_MAX_CONCURRENT_SEND", 10))
|
|
||||||
|
|
||||||
func uncheckedSendArgsFromPDU(fsv *pdu.FilesystemVersion) *zfs.ZFSSendArgVersion {
|
func uncheckedSendArgsFromPDU(fsv *pdu.FilesystemVersion) *zfs.ZFSSendArgVersion {
|
||||||
if fsv == nil {
|
if fsv == nil {
|
||||||
@@ -319,10 +211,11 @@ func (s *Sender) Send(ctx context.Context, r *pdu.SendReq) (*pdu.SendRes, io.Rea
|
|||||||
return res, nil, nil
|
return res, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// update replication cursor
|
// create a replication cursor for `From` (usually an idempotent no-op because SendCompleted already created it before)
|
||||||
|
var fromReplicationCursor Abstraction
|
||||||
if sendArgs.From != nil {
|
if sendArgs.From != nil {
|
||||||
// For all but the first replication, this should always be a no-op because SendCompleted already moved the cursor
|
// For all but the first replication, this should always be a no-op because SendCompleted already moved the cursor
|
||||||
_, err = MoveReplicationCursor(ctx, sendArgs.FS, sendArgs.FromVersion, s.jobId)
|
fromReplicationCursor, err = CreateReplicationCursor(ctx, sendArgs.FS, *sendArgs.FromVersion, s.jobId) // no shadow
|
||||||
if err == zfs.ErrBookmarkCloningNotSupported {
|
if err == zfs.ErrBookmarkCloningNotSupported {
|
||||||
getLogger(ctx).Debug("not creating replication cursor from bookmark because ZFS does not support it")
|
getLogger(ctx).Debug("not creating replication cursor from bookmark because ZFS does not support it")
|
||||||
// fallthrough
|
// fallthrough
|
||||||
@@ -331,9 +224,12 @@ func (s *Sender) Send(ctx context.Context, r *pdu.SendReq) (*pdu.SendRes, io.Rea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
takeStepHolds := sendArgs.FromVersion == nil || !s.disableIncrementalStepHolds
|
||||||
|
|
||||||
|
var fromHold, toHold Abstraction
|
||||||
// make sure `From` doesn't go away in order to make this step resumable
|
// make sure `From` doesn't go away in order to make this step resumable
|
||||||
if sendArgs.From != nil {
|
if sendArgs.From != nil && takeStepHolds {
|
||||||
_, err := HoldStep(ctx, sendArgs.FS, *sendArgs.FromVersion, s.jobId)
|
fromHold, err = HoldStep(ctx, sendArgs.FS, *sendArgs.FromVersion, s.jobId) // no shadow
|
||||||
if err == zfs.ErrBookmarkCloningNotSupported {
|
if err == zfs.ErrBookmarkCloningNotSupported {
|
||||||
getLogger(ctx).Debug("not creating step bookmark because ZFS does not support it")
|
getLogger(ctx).Debug("not creating step bookmark because ZFS does not support it")
|
||||||
// fallthrough
|
// fallthrough
|
||||||
@@ -341,18 +237,72 @@ func (s *Sender) Send(ctx context.Context, r *pdu.SendReq) (*pdu.SendRes, io.Rea
|
|||||||
return nil, nil, errors.Wrapf(err, "cannot hold `from` version %q before starting send", *sendArgs.FromVersion)
|
return nil, nil, errors.Wrapf(err, "cannot hold `from` version %q before starting send", *sendArgs.FromVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// make sure `To` doesn't go away in order to make this step resumable
|
if takeStepHolds {
|
||||||
_, err = HoldStep(ctx, sendArgs.FS, sendArgs.ToVersion, s.jobId)
|
// make sure `To` doesn't go away in order to make this step resumable
|
||||||
if err != nil {
|
toHold, err = HoldStep(ctx, sendArgs.FS, sendArgs.ToVersion, s.jobId)
|
||||||
return nil, nil, errors.Wrapf(err, "cannot hold `to` version %q before starting send", sendArgs.ToVersion)
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrapf(err, "cannot hold `to` version %q before starting send", sendArgs.ToVersion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// step holds & replication cursor released / moved forward in s.SendCompleted => s.moveCursorAndReleaseSendHolds
|
// cleanup the mess that _this function_ might have created in prior failed attempts:
|
||||||
|
//
|
||||||
|
// In summary, we delete every endpoint ZFS abstraction created on this filesystem for this job id,
|
||||||
|
// except for the ones we just created above.
|
||||||
|
//
|
||||||
|
// This is the most robust approach to avoid leaking (= forgetting to clean up) endpoint ZFS abstractions,
|
||||||
|
// all under the assumption that there will only ever be one send for a (jobId,fs) combination at any given time.
|
||||||
|
//
|
||||||
|
// Note that the SendCompleted rpc can't be relied upon for this purpose:
|
||||||
|
// - it might be lost due to network errors,
|
||||||
|
// - or never be sent by a potentially malicious or buggy client,
|
||||||
|
// - or never be send because the replication step failed at some point
|
||||||
|
// (potentially leaving a resumable state on the receiver, which is the case where we really do not want to blow away the step holds too soon.)
|
||||||
|
//
|
||||||
|
// Note further that a resuming send, due to the idempotent nature of func CreateReplicationCursor and HoldStep,
|
||||||
|
// will never lose its step holds because we just (idempotently re-)created them above, before attempting the cleanup.
|
||||||
|
liveAbs := []Abstraction{fromHold, toHold, fromReplicationCursor}
|
||||||
|
func() {
|
||||||
|
ctx, endSpan := trace.WithSpan(ctx, "cleanup-stale-abstractions")
|
||||||
|
defer endSpan()
|
||||||
|
|
||||||
|
keep := func(a Abstraction) (keep bool) {
|
||||||
|
keep = false
|
||||||
|
for _, k := range liveAbs {
|
||||||
|
keep = keep || AbstractionEquals(a, k)
|
||||||
|
}
|
||||||
|
return keep
|
||||||
|
}
|
||||||
|
check := func(obsoleteAbs []Abstraction) {
|
||||||
|
// last line of defense: check that we don't destroy the incremental `from` and `to`
|
||||||
|
// if we did that, we might be about to blow away the last common filesystem version between sender and receiver
|
||||||
|
mustLiveVersions := []zfs.FilesystemVersion{sendArgs.ToVersion}
|
||||||
|
if sendArgs.FromVersion != nil {
|
||||||
|
mustLiveVersions = append(mustLiveVersions, *sendArgs.FromVersion)
|
||||||
|
}
|
||||||
|
for _, staleVersion := range obsoleteAbs {
|
||||||
|
for _, mustLiveVersion := range mustLiveVersions {
|
||||||
|
if zfs.FilesystemVersionEqualIdentity(mustLiveVersion, staleVersion.GetFilesystemVersion()) {
|
||||||
|
panic(fmt.Sprintf("impl error: %q would be destroyed because it is considered stale but it is part of of sendArgs=%s", mustLiveVersion.String(), pretty.Sprint(sendArgs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendAbstractionsCacheSingleton.TryBatchDestroy(ctx, s.jobId, sendArgs.FS, keep, check)
|
||||||
|
}()
|
||||||
|
// now add the newly created abstractions to the cleaned-up cache
|
||||||
|
for _, a := range liveAbs {
|
||||||
|
if a != nil {
|
||||||
|
sendAbstractionsCacheSingleton.Put(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendStream, err := zfs.ZFSSend(ctx, sendArgs)
|
sendStream, err := zfs.ZFSSend(ctx, sendArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// it's ok to not destroy the abstractions we just created here, a new send attempt will take care of it
|
||||||
return nil, nil, errors.Wrap(err, "zfs send failed")
|
return nil, nil, errors.Wrap(err, "zfs send failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, sendStream, nil
|
return res, sendStream, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,8 +339,7 @@ func (p *Sender) SendCompleted(ctx context.Context, r *pdu.SendCompletedReq) (*p
|
|||||||
return log
|
return log
|
||||||
}
|
}
|
||||||
|
|
||||||
log(ctx).Debug("move replication cursor to most recent common version")
|
toReplicationCursor, err := CreateReplicationCursor(ctx, fs, to, p.jobId)
|
||||||
destroyedCursors, err := MoveReplicationCursor(ctx, fs, to, p.jobId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == zfs.ErrBookmarkCloningNotSupported {
|
if err == zfs.ErrBookmarkCloningNotSupported {
|
||||||
log(ctx).Debug("not setting replication cursor, bookmark cloning not supported")
|
log(ctx).Debug("not setting replication cursor, bookmark cloning not supported")
|
||||||
@@ -398,63 +347,20 @@ func (p *Sender) SendCompleted(ctx context.Context, r *pdu.SendCompletedReq) (*p
|
|||||||
msg := "cannot move replication cursor, keeping hold on `to` until successful"
|
msg := "cannot move replication cursor, keeping hold on `to` until successful"
|
||||||
log(ctx).WithError(err).Error(msg)
|
log(ctx).WithError(err).Error(msg)
|
||||||
err = errors.Wrap(err, msg)
|
err = errors.Wrap(err, msg)
|
||||||
// it is correct to not release the hold if we can't move the cursor!
|
// it is correct to not destroy from and to step holds if we can't move the cursor!
|
||||||
return &pdu.SendCompletedRes{}, err
|
return &pdu.SendCompletedRes{}, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log(ctx).Info("successfully moved replication cursor")
|
log(ctx).WithField("to_cursor", toReplicationCursor.String()).Info("successfully created `to` replication cursor")
|
||||||
}
|
}
|
||||||
|
|
||||||
// kick off releasing of step holds / bookmarks
|
keep := func(a Abstraction) bool {
|
||||||
// if we fail to release them, don't bother the caller:
|
return AbstractionEquals(a, toReplicationCursor)
|
||||||
// they are merely an implementation detail on the sender for better resumability
|
}
|
||||||
var wg sync.WaitGroup
|
sendAbstractionsCacheSingleton.TryBatchDestroy(ctx, p.jobId, fs, keep, nil)
|
||||||
wg.Add(2)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
ctx, endTask := trace.WithTask(ctx, "release-step-hold-to")
|
|
||||||
defer endTask()
|
|
||||||
|
|
||||||
log(ctx).Debug("release step-hold of or step-bookmark on `to`")
|
|
||||||
err = ReleaseStep(ctx, fs, to, p.jobId)
|
|
||||||
if err != nil {
|
|
||||||
log(ctx).WithError(err).Error("cannot release step-holds on or destroy step-bookmark of `to`")
|
|
||||||
} else {
|
|
||||||
log(ctx).Info("successfully released step-holds on or destroyed step-bookmark of `to`")
|
|
||||||
}
|
|
||||||
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
ctx, endTask := trace.WithTask(ctx, "release-step-hold-from")
|
|
||||||
defer endTask()
|
|
||||||
|
|
||||||
if from == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log(ctx).Debug("release step-hold of or step-bookmark on `from`")
|
|
||||||
err := ReleaseStep(ctx, fs, *from, p.jobId)
|
|
||||||
if err != nil {
|
|
||||||
if dne, ok := err.(*zfs.DatasetDoesNotExist); ok {
|
|
||||||
// If bookmark cloning is not supported, `from` might be the old replication cursor
|
|
||||||
// and thus have already been destroyed by MoveReplicationCursor above
|
|
||||||
// In that case, nonexistence of `from` is not an error, otherwise it is.
|
|
||||||
for _, c := range destroyedCursors {
|
|
||||||
if c.GetFullPath() == dne.Path {
|
|
||||||
log(ctx).Info("`from` was a replication cursor and has already been destroyed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
}
|
|
||||||
log(ctx).WithError(err).Error("cannot release step-holds on or destroy step-bookmark of `from`")
|
|
||||||
} else {
|
|
||||||
log(ctx).Info("successfully released step-holds on or destroyed step-bookmark of `from`")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return &pdu.SendCompletedRes{}, nil
|
return &pdu.SendCompletedRes{}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Sender) DestroySnapshots(ctx context.Context, req *pdu.DestroySnapshotsReq) (*pdu.DestroySnapshotsRes, error) {
|
func (p *Sender) DestroySnapshots(ctx context.Context, req *pdu.DestroySnapshotsReq) (*pdu.DestroySnapshotsRes, error) {
|
||||||
@@ -974,16 +880,6 @@ func (s *Receiver) DestroySnapshots(ctx context.Context, req *pdu.DestroySnapsho
|
|||||||
return doDestroySnapshots(ctx, lp, req.Snapshots)
|
return doDestroySnapshots(ctx, lp, req.Snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Receiver) HintMostRecentCommonAncestor(ctx context.Context, r *pdu.HintMostRecentCommonAncestorReq) (*pdu.HintMostRecentCommonAncestorRes, error) {
|
|
||||||
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
|
||||||
// we don't move last-received-hold as part of this hint
|
|
||||||
// because that wouldn't give us any benefit wrt resumability.
|
|
||||||
//
|
|
||||||
// Other reason: the replication logic that issues this RPC would require refactoring
|
|
||||||
// to include the receiver's FilesystemVersion in the request)
|
|
||||||
return &pdu.HintMostRecentCommonAncestorRes{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Receiver) SendCompleted(ctx context.Context, _ *pdu.SendCompletedReq) (*pdu.SendCompletedRes, error) {
|
func (p *Receiver) SendCompleted(ctx context.Context, _ *pdu.SendCompletedReq) (*pdu.SendCompletedRes, error) {
|
||||||
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import "github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
func RegisterMetrics(r prometheus.Registerer) {
|
||||||
|
r.MustRegister(sendAbstractionsCacheMetrics.count)
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/zrepl/zrepl/daemon/logging/trace"
|
||||||
|
"github.com/zrepl/zrepl/util/chainlock"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sendAbstractionsCacheMetrics struct {
|
||||||
|
count prometheus.Gauge
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sendAbstractionsCacheMetrics.count = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: "zrepl",
|
||||||
|
Subsystem: "endpoint",
|
||||||
|
Name: "send_abstractions_cache_entry_count",
|
||||||
|
Help: "number of send abstractions tracked in the sendAbstractionsCache data structure",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendAbstractionsCacheSingleton = newSendAbstractionsCache()
|
||||||
|
|
||||||
|
func SendAbstractionsCacheInvalidate(fs string) {
|
||||||
|
sendAbstractionsCacheSingleton.InvalidateFSCache(fs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sendAbstractionsCacheDidLoadFSState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
sendAbstractionsCacheDidLoadFSStateNo sendAbstractionsCacheDidLoadFSState = iota // 0-value has meaning
|
||||||
|
sendAbstractionsCacheDidLoadFSStateInProgress
|
||||||
|
sendAbstractionsCacheDidLoadFSStateDone
|
||||||
|
)
|
||||||
|
|
||||||
|
type sendAbstractionsCache struct {
|
||||||
|
mtx chainlock.L
|
||||||
|
abstractions []Abstraction
|
||||||
|
didLoadFS map[string]sendAbstractionsCacheDidLoadFSState
|
||||||
|
didLoadFSChanged *sync.Cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSendAbstractionsCache() *sendAbstractionsCache {
|
||||||
|
c := &sendAbstractionsCache{
|
||||||
|
didLoadFS: make(map[string]sendAbstractionsCacheDidLoadFSState),
|
||||||
|
}
|
||||||
|
c.didLoadFSChanged = c.mtx.NewCond()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sendAbstractionsCache) Put(a Abstraction) {
|
||||||
|
defer s.mtx.Lock().Unlock()
|
||||||
|
|
||||||
|
var zeroJobId JobID
|
||||||
|
if a.GetJobID() == nil {
|
||||||
|
panic("abstraction must not have nil job id")
|
||||||
|
} else if *a.GetJobID() == zeroJobId {
|
||||||
|
panic(fmt.Sprintf("abstraction must not have zero-value job id: %s", a))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.abstractions = append(s.abstractions, a)
|
||||||
|
sendAbstractionsCacheMetrics.count.Set(float64(len(s.abstractions)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sendAbstractionsCache) InvalidateFSCache(fs string) {
|
||||||
|
// FIXME: O(n)
|
||||||
|
newAbs := make([]Abstraction, 0, len(s.abstractions))
|
||||||
|
for _, a := range s.abstractions {
|
||||||
|
if a.GetFS() != fs {
|
||||||
|
newAbs = append(newAbs, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.abstractions = newAbs
|
||||||
|
sendAbstractionsCacheMetrics.count.Set(float64(len(s.abstractions)))
|
||||||
|
|
||||||
|
s.didLoadFS[fs] = sendAbstractionsCacheDidLoadFSStateNo
|
||||||
|
s.didLoadFSChanged.Broadcast()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// - logs errors in getting on-disk abstractions
|
||||||
|
// - only fetches on-disk abstractions once, but every time from the in-memory store
|
||||||
|
//
|
||||||
|
// That means that for precise results, all abstractions created by the endpoint must be .Put into this cache.
|
||||||
|
func (s *sendAbstractionsCache) GetAndDeleteByJobIDAndFS(ctx context.Context, jobID JobID, fs string) (ret []Abstraction) {
|
||||||
|
defer s.mtx.Lock().Unlock()
|
||||||
|
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
||||||
|
var zeroJobId JobID
|
||||||
|
if jobID == zeroJobId {
|
||||||
|
panic("must not pass zero-value job id")
|
||||||
|
}
|
||||||
|
if fs == "" {
|
||||||
|
panic("must not pass zero-value fs")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.tryLoadOnDiskSendAbstractions(ctx, fs)
|
||||||
|
|
||||||
|
// FIXME O(n)
|
||||||
|
var remaining []Abstraction
|
||||||
|
for _, a := range s.abstractions {
|
||||||
|
aJobId := *a.GetJobID()
|
||||||
|
aFS := a.GetFS()
|
||||||
|
if aJobId == jobID && aFS == fs {
|
||||||
|
ret = append(ret, a)
|
||||||
|
} else {
|
||||||
|
remaining = append(remaining, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.abstractions = remaining
|
||||||
|
sendAbstractionsCacheMetrics.count.Set(float64(len(s.abstractions)))
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller must hold s.mtx
|
||||||
|
func (s *sendAbstractionsCache) tryLoadOnDiskSendAbstractions(ctx context.Context, fs string) {
|
||||||
|
for s.didLoadFS[fs] != sendAbstractionsCacheDidLoadFSStateDone {
|
||||||
|
if s.didLoadFS[fs] == sendAbstractionsCacheDidLoadFSStateInProgress {
|
||||||
|
s.didLoadFSChanged.Wait()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.didLoadFS[fs] != sendAbstractionsCacheDidLoadFSStateNo {
|
||||||
|
panic(fmt.Sprintf("unreachable: %v", s.didLoadFS[fs]))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.didLoadFS[fs] = sendAbstractionsCacheDidLoadFSStateInProgress
|
||||||
|
defer s.didLoadFSChanged.Broadcast()
|
||||||
|
|
||||||
|
var onDiskAbs []Abstraction
|
||||||
|
var err error
|
||||||
|
s.mtx.DropWhile(func() {
|
||||||
|
onDiskAbs, err = s.tryLoadOnDiskSendAbstractionsImpl(ctx, fs) // no shadow
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.didLoadFS[fs] = sendAbstractionsCacheDidLoadFSStateNo
|
||||||
|
getLogger(ctx).WithField("fs", fs).WithError(err).Error("cannot list send step abstractions for filesystem")
|
||||||
|
} else {
|
||||||
|
s.didLoadFS[fs] = sendAbstractionsCacheDidLoadFSStateDone
|
||||||
|
s.abstractions = append(s.abstractions, onDiskAbs...)
|
||||||
|
getLogger(ctx).WithField("fs", fs).WithField("abstractions", onDiskAbs).Debug("loaded step abstractions for filesystem")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller should _not hold s.mtx
|
||||||
|
func (s *sendAbstractionsCache) tryLoadOnDiskSendAbstractionsImpl(ctx context.Context, fs string) ([]Abstraction, error) {
|
||||||
|
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
||||||
|
|
||||||
|
q := ListZFSHoldsAndBookmarksQuery{
|
||||||
|
FS: ListZFSHoldsAndBookmarksQueryFilesystemFilter{
|
||||||
|
FS: &fs,
|
||||||
|
},
|
||||||
|
JobID: nil,
|
||||||
|
What: AbstractionTypeSet{
|
||||||
|
AbstractionStepHold: true,
|
||||||
|
AbstractionStepBookmark: true,
|
||||||
|
AbstractionReplicationCursorBookmarkV2: true,
|
||||||
|
},
|
||||||
|
Concurrency: 1,
|
||||||
|
}
|
||||||
|
abs, absErrs, err := ListAbstractions(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// safe to ignore absErrs here, this is best-effort cleanup
|
||||||
|
if len(absErrs) > 0 {
|
||||||
|
return nil, ListAbstractionsErrors(absErrs)
|
||||||
|
}
|
||||||
|
return abs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sendAbstractionsCache) TryBatchDestroy(ctx context.Context, jobId JobID, fs string, keep func(a Abstraction) bool, check func(willDestroy []Abstraction)) {
|
||||||
|
// no s.mtx, we only use the public interface in this function
|
||||||
|
|
||||||
|
defer trace.WithSpanFromStackUpdateCtx(&ctx)()
|
||||||
|
|
||||||
|
allSendStepAbstractions := s.GetAndDeleteByJobIDAndFS(ctx, jobId, fs)
|
||||||
|
|
||||||
|
var obsoleteAbs []Abstraction
|
||||||
|
for _, a := range allSendStepAbstractions {
|
||||||
|
if !keep(a) {
|
||||||
|
obsoleteAbs = append(obsoleteAbs, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if check != nil {
|
||||||
|
check(obsoleteAbs)
|
||||||
|
}
|
||||||
|
|
||||||
|
hadErr := false
|
||||||
|
for res := range BatchDestroy(ctx, obsoleteAbs) {
|
||||||
|
if res.DestroyErr != nil {
|
||||||
|
hadErr = true
|
||||||
|
getLogger(ctx).
|
||||||
|
WithField("abstraction", res.Abstraction).
|
||||||
|
WithError(res.DestroyErr).
|
||||||
|
Error("cannot destroy stale send step abstraction")
|
||||||
|
} else {
|
||||||
|
getLogger(ctx).
|
||||||
|
WithField("abstraction", res.Abstraction).
|
||||||
|
Info("destroyed stale send step abstraction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hadErr {
|
||||||
|
s.InvalidateFSCache(fs)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -54,6 +54,30 @@ type Abstraction interface {
|
|||||||
json.Marshaler
|
json.Marshaler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AbstractionEquals(a, b Abstraction) bool {
|
||||||
|
if (a != nil) != (b != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var aJobId, bJobId JobID
|
||||||
|
if aJid := a.GetJobID(); aJid != nil {
|
||||||
|
aJobId = *aJid
|
||||||
|
}
|
||||||
|
if bJid := b.GetJobID(); bJid != nil {
|
||||||
|
bJobId = *bJid
|
||||||
|
}
|
||||||
|
return a.GetType() == b.GetType() &&
|
||||||
|
a.GetFS() == b.GetFS() &&
|
||||||
|
a.GetName() == b.GetName() &&
|
||||||
|
a.GetFullPath() == b.GetFullPath() &&
|
||||||
|
aJobId == bJobId &&
|
||||||
|
a.GetCreateTXG() == b.GetCreateTXG() &&
|
||||||
|
zfs.FilesystemVersionEqualIdentity(a.GetFilesystemVersion(), b.GetFilesystemVersion()) &&
|
||||||
|
a.String() == b.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (t AbstractionType) Validate() error {
|
func (t AbstractionType) Validate() error {
|
||||||
switch t {
|
switch t {
|
||||||
case AbstractionStepBookmark:
|
case AbstractionStepBookmark:
|
||||||
@@ -590,23 +614,23 @@ func listAbstractionsImplFS(ctx context.Context, fs string, query *ListZFSHoldsA
|
|||||||
panic("implementation error: extractors misconfigured for " + at)
|
panic("implementation error: extractors misconfigured for " + at)
|
||||||
}
|
}
|
||||||
for _, v := range fsvs {
|
for _, v := range fsvs {
|
||||||
var a Abstraction
|
|
||||||
if v.Type == zfs.Bookmark && bmE != nil {
|
if v.Type == zfs.Bookmark && bmE != nil {
|
||||||
a = bmE(fsp, v)
|
if a := bmE(fsp, v); a != nil {
|
||||||
|
emitCandidate(a)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if v.Type == zfs.Snapshot && holdE != nil && query.CreateTXG.Contains(v.GetCreateTXG()) && (!v.UserRefs.Valid || v.UserRefs.Value > 0) {
|
if v.Type == zfs.Snapshot && holdE != nil && query.CreateTXG.Contains(v.GetCreateTXG()) && (!v.UserRefs.Valid || v.UserRefs.Value > 0) { // FIXME review v.UserRefsValid
|
||||||
holds, err := zfs.ZFSHolds(ctx, fsp.ToString(), v.Name)
|
holds, err := zfs.ZFSHolds(ctx, fsp.ToString(), v.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCb(err, v.ToAbsPath(fsp), "get hold on snap")
|
errCb(err, v.ToAbsPath(fsp), "get hold on snap")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, tag := range holds {
|
for _, tag := range holds {
|
||||||
a = holdE(fsp, v, tag)
|
if a := holdE(fsp, v, tag); a != nil {
|
||||||
|
emitCandidate(a)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if a != nil {
|
|
||||||
emitCandidate(a)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,31 +118,32 @@ func GetReplicationCursors(ctx context.Context, dp *zfs.DatasetPath, jobID JobID
|
|||||||
return candidates, nil
|
return candidates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReplicationCursorTarget interface {
|
// idempotently create a replication cursor targeting `target`
|
||||||
IsSnapshot() bool
|
|
||||||
GetGuid() uint64
|
|
||||||
GetCreateTXG() uint64
|
|
||||||
ToSendArgVersion() zfs.ZFSSendArgVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// `target` is validated before replication cursor is set. if validation fails, the cursor is not moved.
|
|
||||||
//
|
//
|
||||||
// returns ErrBookmarkCloningNotSupported if version is a bookmark and bookmarking bookmarks is not supported by ZFS
|
// returns ErrBookmarkCloningNotSupported if version is a bookmark and bookmarking bookmarks is not supported by ZFS
|
||||||
func MoveReplicationCursor(ctx context.Context, fs string, target ReplicationCursorTarget, jobID JobID) (destroyedCursors []Abstraction, err error) {
|
func CreateReplicationCursor(ctx context.Context, fs string, target zfs.FilesystemVersion, jobID JobID) (a Abstraction, err error) {
|
||||||
|
|
||||||
if !target.IsSnapshot() {
|
|
||||||
return nil, zfs.ErrBookmarkCloningNotSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmarkname, err := ReplicationCursorBookmarkName(fs, target.GetGuid(), jobID)
|
bookmarkname, err := ReplicationCursorBookmarkName(fs, target.GetGuid(), jobID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "determine replication cursor name")
|
return nil, errors.Wrap(err, "determine replication cursor name")
|
||||||
}
|
}
|
||||||
|
|
||||||
// idempotently create bookmark (guid is encoded in it, hence we'll most likely add a new one
|
if target.IsBookmark() && target.GetName() == bookmarkname {
|
||||||
// cleanup the old one afterwards
|
return &bookmarkBasedAbstraction{
|
||||||
|
Type: AbstractionReplicationCursorBookmarkV2,
|
||||||
|
FS: fs,
|
||||||
|
FilesystemVersion: target,
|
||||||
|
JobID: jobID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
err = zfs.ZFSBookmark(ctx, fs, target.ToSendArgVersion(), bookmarkname)
|
if !target.IsSnapshot() {
|
||||||
|
return nil, zfs.ErrBookmarkCloningNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// idempotently create bookmark (guid is encoded in it)
|
||||||
|
|
||||||
|
cursorBookmark, err := zfs.ZFSBookmark(ctx, fs, target, bookmarkname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == zfs.ErrBookmarkCloningNotSupported {
|
if err == zfs.ErrBookmarkCloningNotSupported {
|
||||||
return nil, err // TODO go1.13 use wrapping
|
return nil, err // TODO go1.13 use wrapping
|
||||||
@@ -150,65 +151,17 @@ func MoveReplicationCursor(ctx context.Context, fs string, target ReplicationCur
|
|||||||
return nil, errors.Wrapf(err, "cannot create bookmark")
|
return nil, errors.Wrapf(err, "cannot create bookmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyedCursors, err = DestroyObsoleteReplicationCursors(ctx, fs, target, jobID)
|
return &bookmarkBasedAbstraction{
|
||||||
if err != nil {
|
Type: AbstractionReplicationCursorBookmarkV2,
|
||||||
return nil, errors.Wrap(err, "destroy obsolete replication cursors")
|
FS: fs,
|
||||||
}
|
FilesystemVersion: cursorBookmark,
|
||||||
|
JobID: jobID,
|
||||||
return destroyedCursors, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReplicationCursor interface {
|
const (
|
||||||
GetCreateTXG() uint64
|
ReplicationCursorBookmarkNamePrefix = "zrepl_last_received_J_"
|
||||||
}
|
)
|
||||||
|
|
||||||
func DestroyObsoleteReplicationCursors(ctx context.Context, fs string, current ReplicationCursor, jobID JobID) (_ []Abstraction, err error) {
|
|
||||||
|
|
||||||
q := ListZFSHoldsAndBookmarksQuery{
|
|
||||||
FS: ListZFSHoldsAndBookmarksQueryFilesystemFilter{
|
|
||||||
FS: &fs,
|
|
||||||
},
|
|
||||||
What: AbstractionTypeSet{
|
|
||||||
AbstractionReplicationCursorBookmarkV2: true,
|
|
||||||
},
|
|
||||||
JobID: &jobID,
|
|
||||||
CreateTXG: CreateTXGRange{
|
|
||||||
Since: nil,
|
|
||||||
Until: &CreateTXGRangeBound{
|
|
||||||
CreateTXG: current.GetCreateTXG(),
|
|
||||||
Inclusive: &zfs.NilBool{B: false},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Concurrency: 1,
|
|
||||||
}
|
|
||||||
abs, absErr, err := ListAbstractions(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "list abstractions")
|
|
||||||
}
|
|
||||||
if len(absErr) > 0 {
|
|
||||||
return nil, errors.Wrap(ListAbstractionsErrors(absErr), "list abstractions")
|
|
||||||
}
|
|
||||||
|
|
||||||
var destroyed []Abstraction
|
|
||||||
var errs []error
|
|
||||||
for res := range BatchDestroy(ctx, abs) {
|
|
||||||
log := getLogger(ctx).
|
|
||||||
WithField("replication_cursor_bookmark", res.Abstraction)
|
|
||||||
if res.DestroyErr != nil {
|
|
||||||
errs = append(errs, res.DestroyErr)
|
|
||||||
log.WithError(err).
|
|
||||||
Error("cannot destroy obsolete replication cursor bookmark")
|
|
||||||
} else {
|
|
||||||
destroyed = append(destroyed, res.Abstraction)
|
|
||||||
log.Info("destroyed obsolete replication cursor bookmark")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) == 0 {
|
|
||||||
return destroyed, nil
|
|
||||||
} else {
|
|
||||||
return destroyed, errorarray.Wrap(errs, "destroy obsolete replication cursor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastReceivedHoldTagRE = regexp.MustCompile("^zrepl_last_received_J_(.+)$")
|
var lastReceivedHoldTagRE = regexp.MustCompile("^zrepl_last_received_J_(.+)$")
|
||||||
|
|
||||||
@@ -230,22 +183,22 @@ func LastReceivedHoldTag(jobID JobID) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func lastReceivedHoldImpl(jobid string) (string, error) {
|
func lastReceivedHoldImpl(jobid string) (string, error) {
|
||||||
tag := fmt.Sprintf("zrepl_last_received_J_%s", jobid)
|
tag := fmt.Sprintf("%s%s", ReplicationCursorBookmarkNamePrefix, jobid)
|
||||||
if err := zfs.ValidHoldTag(tag); err != nil {
|
if err := zfs.ValidHoldTag(tag); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return tag, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MoveLastReceivedHold(ctx context.Context, fs string, to zfs.FilesystemVersion, jobID JobID) error {
|
func CreateLastReceivedHold(ctx context.Context, fs string, to zfs.FilesystemVersion, jobID JobID) (Abstraction, error) {
|
||||||
|
|
||||||
if !to.IsSnapshot() {
|
if !to.IsSnapshot() {
|
||||||
return errors.Errorf("last-received-hold: target must be a snapshot: %s", to.FullPath(fs))
|
return nil, errors.Errorf("last-received-hold: target must be a snapshot: %s", to.FullPath(fs))
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, err := LastReceivedHoldTag(jobID)
|
tag, err := LastReceivedHoldTag(jobID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "last-received-hold: hold tag")
|
return nil, errors.Wrap(err, "last-received-hold: hold tag")
|
||||||
}
|
}
|
||||||
|
|
||||||
// we never want to be without a hold
|
// we never want to be without a hold
|
||||||
@@ -253,7 +206,23 @@ func MoveLastReceivedHold(ctx context.Context, fs string, to zfs.FilesystemVersi
|
|||||||
|
|
||||||
err = zfs.ZFSHold(ctx, fs, to, tag)
|
err = zfs.ZFSHold(ctx, fs, to, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "last-received-hold: hold newly received")
|
return nil, errors.Wrap(err, "last-received-hold: hold newly received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &holdBasedAbstraction{
|
||||||
|
Type: AbstractionLastReceivedHold,
|
||||||
|
FS: fs,
|
||||||
|
FilesystemVersion: to,
|
||||||
|
JobID: jobID,
|
||||||
|
Tag: tag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoveLastReceivedHold(ctx context.Context, fs string, to zfs.FilesystemVersion, jobID JobID) error {
|
||||||
|
|
||||||
|
_, err := CreateLastReceivedHold(ctx, fs, to, jobID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
q := ListZFSHoldsAndBookmarksQuery{
|
q := ListZFSHoldsAndBookmarksQuery{
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/zrepl/zrepl/util/errorarray"
|
|
||||||
"github.com/zrepl/zrepl/zfs"
|
"github.com/zrepl/zrepl/zfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ func HoldStep(ctx context.Context, fs string, v zfs.FilesystemVersion, jobID Job
|
|||||||
return nil, errors.Wrap(err, "create step bookmark: determine bookmark name")
|
return nil, errors.Wrap(err, "create step bookmark: determine bookmark name")
|
||||||
}
|
}
|
||||||
// idempotently create bookmark
|
// idempotently create bookmark
|
||||||
err = zfs.ZFSBookmark(ctx, fs, v.ToSendArgVersion(), bmname)
|
stepBookmark, err := zfs.ZFSBookmark(ctx, fs, v, bmname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == zfs.ErrBookmarkCloningNotSupported {
|
if err == zfs.ErrBookmarkCloningNotSupported {
|
||||||
// TODO we could actually try to find a local snapshot that has the requested GUID
|
// TODO we could actually try to find a local snapshot that has the requested GUID
|
||||||
@@ -108,137 +107,11 @@ func HoldStep(ctx context.Context, fs string, v zfs.FilesystemVersion, jobID Job
|
|||||||
return &bookmarkBasedAbstraction{
|
return &bookmarkBasedAbstraction{
|
||||||
Type: AbstractionStepBookmark,
|
Type: AbstractionStepBookmark,
|
||||||
FS: fs,
|
FS: fs,
|
||||||
FilesystemVersion: v,
|
FilesystemVersion: stepBookmark,
|
||||||
JobID: jobID,
|
JobID: jobID,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// idempotently release the step-hold on v if v is a snapshot
|
|
||||||
// or idempotently destroy the step-bookmark of v if v is a bookmark
|
|
||||||
//
|
|
||||||
// note that this operation leaves v itself untouched, unless v is the step-bookmark itself, in which case v is destroyed
|
|
||||||
//
|
|
||||||
// returns an instance of *zfs.DatasetDoesNotExist if `v` does not exist
|
|
||||||
func ReleaseStep(ctx context.Context, fs string, v zfs.FilesystemVersion, jobID JobID) error {
|
|
||||||
|
|
||||||
if v.IsSnapshot() {
|
|
||||||
tag, err := StepHoldTag(jobID)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "step release tag")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := zfs.ZFSRelease(ctx, tag, v.FullPath(fs)); err != nil {
|
|
||||||
return errors.Wrap(err, "step release: zfs")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !v.IsBookmark() {
|
|
||||||
panic(fmt.Sprintf("impl error: expecting version to be a bookmark, got %#v", v))
|
|
||||||
}
|
|
||||||
|
|
||||||
bmname, err := StepBookmarkName(fs, v.Guid, jobID)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "step release: determine bookmark name")
|
|
||||||
}
|
|
||||||
// idempotently destroy bookmark
|
|
||||||
|
|
||||||
if err := zfs.ZFSDestroyIdempotent(ctx, bmname); err != nil {
|
|
||||||
return errors.Wrap(err, "step release: bookmark destroy: zfs")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// release {step holds, step bookmarks} earlier and including `mostRecent`
|
|
||||||
func ReleaseStepCummulativeInclusive(ctx context.Context, fs string, since *CreateTXGRangeBound, mostRecent zfs.FilesystemVersion, jobID JobID) error {
|
|
||||||
q := ListZFSHoldsAndBookmarksQuery{
|
|
||||||
What: AbstractionTypeSet{
|
|
||||||
AbstractionStepHold: true,
|
|
||||||
AbstractionStepBookmark: true,
|
|
||||||
},
|
|
||||||
FS: ListZFSHoldsAndBookmarksQueryFilesystemFilter{
|
|
||||||
FS: &fs,
|
|
||||||
},
|
|
||||||
JobID: &jobID,
|
|
||||||
CreateTXG: CreateTXGRange{
|
|
||||||
Since: since,
|
|
||||||
Until: &CreateTXGRangeBound{
|
|
||||||
CreateTXG: mostRecent.CreateTXG,
|
|
||||||
Inclusive: &zfs.NilBool{B: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Concurrency: 1,
|
|
||||||
}
|
|
||||||
abs, absErrs, err := ListAbstractions(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "step release cummulative: list")
|
|
||||||
}
|
|
||||||
if len(absErrs) > 0 {
|
|
||||||
return errors.Wrap(ListAbstractionsErrors(absErrs), "step release cummulative: list")
|
|
||||||
}
|
|
||||||
|
|
||||||
getLogger(ctx).WithField("step_holds_and_bookmarks", fmt.Sprintf("%s", abs)).Debug("releasing step holds and bookmarks")
|
|
||||||
|
|
||||||
var errs []error
|
|
||||||
for res := range BatchDestroy(ctx, abs) {
|
|
||||||
log := getLogger(ctx).
|
|
||||||
WithField("step_hold_or_bookmark", res.Abstraction)
|
|
||||||
if res.DestroyErr != nil {
|
|
||||||
errs = append(errs, res.DestroyErr)
|
|
||||||
log.WithError(err).
|
|
||||||
Error("cannot release step hold or bookmark")
|
|
||||||
} else {
|
|
||||||
log.Info("released step hold or bookmark")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) == 0 {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return errorarray.Wrap(errs, "step release cummulative: release")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TryReleaseStepStaleFS(ctx context.Context, fs string, jobID JobID) {
|
|
||||||
|
|
||||||
q := ListZFSHoldsAndBookmarksQuery{
|
|
||||||
FS: ListZFSHoldsAndBookmarksQueryFilesystemFilter{
|
|
||||||
FS: &fs,
|
|
||||||
},
|
|
||||||
JobID: &jobID,
|
|
||||||
What: AbstractionTypeSet{
|
|
||||||
AbstractionStepHold: true,
|
|
||||||
AbstractionStepBookmark: true,
|
|
||||||
AbstractionReplicationCursorBookmarkV2: true,
|
|
||||||
},
|
|
||||||
Concurrency: 1,
|
|
||||||
}
|
|
||||||
staleness, err := ListStale(ctx, q)
|
|
||||||
if _, ok := err.(*ListStaleQueryError); ok {
|
|
||||||
panic(err)
|
|
||||||
} else if err != nil {
|
|
||||||
getLogger(ctx).WithError(err).Error("cannot list stale step holds and bookmarks")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, s := range staleness.Stale {
|
|
||||||
getLogger(ctx).WithField("stale_step_hold_or_bookmark", s).Info("batch-destroying stale step hold or bookmark")
|
|
||||||
}
|
|
||||||
for res := range BatchDestroy(ctx, staleness.Stale) {
|
|
||||||
if res.DestroyErr != nil {
|
|
||||||
getLogger(ctx).
|
|
||||||
WithField("stale_step_hold_or_bookmark", res.Abstraction).
|
|
||||||
WithError(res.DestroyErr).
|
|
||||||
Error("cannot destroy stale step-hold or bookmark")
|
|
||||||
} else {
|
|
||||||
getLogger(ctx).
|
|
||||||
WithField("stale_step_hold_or_bookmark", res.Abstraction).
|
|
||||||
WithError(res.DestroyErr).
|
|
||||||
Info("destroyed stale step-hold or bookmark")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ BookmarkExtractor = StepBookmarkExtractor
|
var _ BookmarkExtractor = StepBookmarkExtractor
|
||||||
|
|
||||||
func StepBookmarkExtractor(fs *zfs.DatasetPath, v zfs.FilesystemVersion) (_ Abstraction) {
|
func StepBookmarkExtractor(fs *zfs.DatasetPath, v zfs.FilesystemVersion) (_ Abstraction) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
github.com/onsi/gomega v1.7.0 // indirect
|
github.com/onsi/gomega v1.7.0 // indirect
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/pkg/profile v1.2.1
|
github.com/pkg/profile v1.2.1
|
||||||
github.com/problame/go-netssh v0.0.0-20191209123953-18d8aa6923c7
|
github.com/problame/go-netssh v0.0.0-20200601114649-26439f9f0dc5
|
||||||
github.com/prometheus/client_golang v1.2.1
|
github.com/prometheus/client_golang v1.2.1
|
||||||
github.com/prometheus/common v0.7.0
|
github.com/prometheus/common v0.7.0
|
||||||
github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44 // go1.12 thinks it needs this
|
github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44 // go1.12 thinks it needs this
|
||||||
@@ -36,6 +36,7 @@ require (
|
|||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
|
||||||
|
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e
|
||||||
gonum.org/v1/gonum v0.7.0 // indirect
|
gonum.org/v1/gonum v0.7.0 // indirect
|
||||||
google.golang.org/grpc v1.17.0
|
google.golang.org/grpc v1.17.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -212,6 +212,8 @@ github.com/problame/go-netssh v0.0.0-20191026123024-f34099f4f6b1 h1:HH8yzlaZq/A8
|
|||||||
github.com/problame/go-netssh v0.0.0-20191026123024-f34099f4f6b1/go.mod h1:JRs3nyBjvOv/9YHC+Vlw9z1Jfzl5s5t0BNnTzmclj1c=
|
github.com/problame/go-netssh v0.0.0-20191026123024-f34099f4f6b1/go.mod h1:JRs3nyBjvOv/9YHC+Vlw9z1Jfzl5s5t0BNnTzmclj1c=
|
||||||
github.com/problame/go-netssh v0.0.0-20191209123953-18d8aa6923c7 h1:Oik8tLrLhoMq4fduDRuNNOAQ+q0M0dXkjAYLUf4+mAc=
|
github.com/problame/go-netssh v0.0.0-20191209123953-18d8aa6923c7 h1:Oik8tLrLhoMq4fduDRuNNOAQ+q0M0dXkjAYLUf4+mAc=
|
||||||
github.com/problame/go-netssh v0.0.0-20191209123953-18d8aa6923c7/go.mod h1:Ba6RaFpK1+IHWs1wLZ6sCBuXkt4iAVLeOG5GinXHusM=
|
github.com/problame/go-netssh v0.0.0-20191209123953-18d8aa6923c7/go.mod h1:Ba6RaFpK1+IHWs1wLZ6sCBuXkt4iAVLeOG5GinXHusM=
|
||||||
|
github.com/problame/go-netssh v0.0.0-20200601114649-26439f9f0dc5 h1:1eSrO2WAeSXjMol8WRKW9LekL1252VIMaKQwWkmEdvs=
|
||||||
|
github.com/problame/go-netssh v0.0.0-20200601114649-26439f9f0dc5/go.mod h1:Ba6RaFpK1+IHWs1wLZ6sCBuXkt4iAVLeOG5GinXHusM=
|
||||||
github.com/prometheus/client_golang v0.0.0-20180410130117-e11c6ff8170b/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.0.0-20180410130117-e11c6ff8170b/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
@@ -351,6 +353,7 @@ golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e h1:Io7mpb+aUAGF0MKxbyQ7HQl1VgB+cL6ZJZUFaFNqVV4=
|
||||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190213192042-740235f6c0d8/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190213192042-740235f6c0d8/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ godep() {
|
|||||||
go build -v -mod=readonly -o "$GOPATH/bin/enumer" github.com/alvaroloes/enumer
|
go build -v -mod=readonly -o "$GOPATH/bin/enumer" github.com/alvaroloes/enumer
|
||||||
go build -v -mod=readonly -o "$GOPATH/bin/goimports" golang.org/x/tools/cmd/goimports
|
go build -v -mod=readonly -o "$GOPATH/bin/goimports" golang.org/x/tools/cmd/goimports
|
||||||
go build -v -mod=readonly -o "$GOPATH/bin/golangci-lint" github.com/golangci/golangci-lint/cmd/golangci-lint
|
go build -v -mod=readonly -o "$GOPATH/bin/golangci-lint" github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||||
|
go build -v -mod=readonly -o "$GOPATH/bin/gocovmerge" github.com/wadey/gocovmerge
|
||||||
set +x
|
set +x
|
||||||
popd
|
popd
|
||||||
if ! type stringer || ! type protoc-gen-go || ! type enumer || ! type goimports || ! type golangci-lint; then
|
if ! type stringer || ! type protoc-gen-go || ! type enumer || ! type goimports || ! type golangci-lint || ! type gocovmerge; then
|
||||||
echo "Installed dependencies but can't find them in \$PATH, adjust it to contain \$GOPATH/bin" 1>&2
|
echo "Installed dependencies but can't find them in \$PATH, adjust it to contain \$GOPATH/bin" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -24,33 +24,36 @@ var bold = color.New(color.Bold)
|
|||||||
var boldRed = color.New(color.Bold, color.FgHiRed)
|
var boldRed = color.New(color.Bold, color.FgHiRed)
|
||||||
var boldGreen = color.New(color.Bold, color.FgHiGreen)
|
var boldGreen = color.New(color.Bold, color.FgHiGreen)
|
||||||
|
|
||||||
var args struct {
|
const DefaultPoolImageSize = 200 * (1 << 20)
|
||||||
createArgs platformtest.ZpoolCreateArgs
|
|
||||||
stopAndKeepPoolOnFail bool
|
|
||||||
|
|
||||||
run string
|
|
||||||
runRE *regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := doMain(); err != nil {
|
|
||||||
|
var args HarnessArgs
|
||||||
|
|
||||||
|
flag.StringVar(&args.CreateArgs.PoolName, "poolname", "", "")
|
||||||
|
flag.StringVar(&args.CreateArgs.ImagePath, "imagepath", "", "")
|
||||||
|
flag.Int64Var(&args.CreateArgs.ImageSize, "imagesize", DefaultPoolImageSize, "")
|
||||||
|
flag.StringVar(&args.CreateArgs.Mountpoint, "mountpoint", "", "")
|
||||||
|
flag.BoolVar(&args.StopAndKeepPoolOnFail, "failure.stop-and-keep-pool", false, "if a test case fails, stop test execution and keep pool as it was when the test failed")
|
||||||
|
flag.StringVar(&args.Run, "run", "", "")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := HarnessRun(args); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitWithErr = fmt.Errorf("exit with error")
|
var exitWithErr = fmt.Errorf("exit with error")
|
||||||
|
|
||||||
func doMain() error {
|
type HarnessArgs struct {
|
||||||
|
CreateArgs platformtest.ZpoolCreateArgs
|
||||||
|
StopAndKeepPoolOnFail bool
|
||||||
|
Run string
|
||||||
|
}
|
||||||
|
|
||||||
flag.StringVar(&args.createArgs.PoolName, "poolname", "", "")
|
func HarnessRun(args HarnessArgs) error {
|
||||||
flag.StringVar(&args.createArgs.ImagePath, "imagepath", "", "")
|
|
||||||
flag.Int64Var(&args.createArgs.ImageSize, "imagesize", 200*(1<<20), "")
|
|
||||||
flag.StringVar(&args.createArgs.Mountpoint, "mountpoint", "", "")
|
|
||||||
flag.BoolVar(&args.stopAndKeepPoolOnFail, "failure.stop-and-keep-pool", false, "if a test case fails, stop test execution and keep pool as it was when the test failed")
|
|
||||||
flag.StringVar(&args.run, "run", "", "")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
args.runRE = regexp.MustCompile(args.run)
|
runRE := regexp.MustCompile(args.Run)
|
||||||
|
|
||||||
outlets := logger.NewOutlets()
|
outlets := logger.NewOutlets()
|
||||||
outlet, level, err := logging.ParseOutlet(config.LoggingOutletEnum{Ret: &config.StdoutLoggingOutlet{
|
outlet, level, err := logging.ParseOutlet(config.LoggingOutletEnum{Ret: &config.StdoutLoggingOutlet{
|
||||||
@@ -65,7 +68,7 @@ func doMain() error {
|
|||||||
outlets.Add(outlet, level)
|
outlets.Add(outlet, level)
|
||||||
logger := logger.NewLogger(outlets, 1*time.Second)
|
logger := logger.NewLogger(outlets, 1*time.Second)
|
||||||
|
|
||||||
if err := args.createArgs.Validate(); err != nil {
|
if err := args.CreateArgs.Validate(); err != nil {
|
||||||
logger.Error(err.Error())
|
logger.Error(err.Error())
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -81,7 +84,7 @@ func doMain() error {
|
|||||||
|
|
||||||
invocations := make([]*invocation, 0, len(tests.Cases))
|
invocations := make([]*invocation, 0, len(tests.Cases))
|
||||||
for _, c := range tests.Cases {
|
for _, c := range tests.Cases {
|
||||||
if args.runRE.MatchString(c.String()) {
|
if runRE.MatchString(c.String()) {
|
||||||
invocations = append(invocations, &invocation{runFunc: c})
|
invocations = append(invocations, &invocation{runFunc: c})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +93,7 @@ func doMain() error {
|
|||||||
|
|
||||||
bold.Printf("BEGIN TEST CASE %s\n", inv.runFunc.String())
|
bold.Printf("BEGIN TEST CASE %s\n", inv.runFunc.String())
|
||||||
|
|
||||||
pool, err := platformtest.CreateOrReplaceZpool(ctx, ex, args.createArgs)
|
pool, err := platformtest.CreateOrReplaceZpool(ctx, ex, args.CreateArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(errors.Wrap(err, "create test pool"))
|
panic(errors.Wrap(err, "create test pool"))
|
||||||
}
|
}
|
||||||
@@ -106,7 +109,7 @@ func doMain() error {
|
|||||||
fmt.Printf("%+v\n", res.failedStack) // print with stack trace
|
fmt.Printf("%+v\n", res.failedStack) // print with stack trace
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.failed && args.stopAndKeepPoolOnFail {
|
if res.failed && args.StopAndKeepPoolOnFail {
|
||||||
boldRed.Printf("STOPPING TEST RUN AT FAILING TEST PER USER REQUEST\n")
|
boldRed.Printf("STOPPING TEST RUN AT FAILING TEST PER USER REQUEST\n")
|
||||||
return exitWithErr
|
return exitWithErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -21,22 +22,29 @@ import (
|
|||||||
// https://github.com/wadey/gocovmerge
|
// https://github.com/wadey/gocovmerge
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
|
fmt.Println("incoming args: ", os.Args)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
args []string
|
args []string
|
||||||
run bool
|
run bool
|
||||||
|
startCaptureArgs bool
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, arg := range os.Args {
|
for i, arg := range os.Args {
|
||||||
switch {
|
switch {
|
||||||
case arg == "__DEVEL--i-heard-you-like-tests":
|
case arg == "__DEVEL--i-heard-you-like-tests":
|
||||||
run = true
|
run = true
|
||||||
|
startCaptureArgs = true
|
||||||
case strings.HasPrefix(arg, "-test"):
|
case strings.HasPrefix(arg, "-test"):
|
||||||
case strings.HasPrefix(arg, "__DEVEL"):
|
case strings.HasPrefix(arg, "__DEVEL"):
|
||||||
default:
|
case i == 0:
|
||||||
|
args = append(args, arg)
|
||||||
|
case startCaptureArgs:
|
||||||
args = append(args, arg)
|
args = append(args, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
os.Args = args
|
os.Args = args
|
||||||
|
fmt.Println("using args: ", os.Args)
|
||||||
|
|
||||||
if run {
|
if run {
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ func CreateOrReplaceZpool(ctx context.Context, e Execer, args ZpoolCreateArgs) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear the mountpoint dir
|
||||||
|
if err := os.RemoveAll(args.Mountpoint); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "remove mountpoint dir %q", args.Mountpoint)
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(args.Mountpoint, 0700); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "create mountpoint dir %q", args.Mountpoint)
|
||||||
|
}
|
||||||
|
|
||||||
// idempotently (re)create the pool image
|
// idempotently (re)create the pool image
|
||||||
image, err := os.OpenFile(args.ImagePath, os.O_CREATE|os.O_RDWR, 0600)
|
image, err := os.OpenFile(args.ImagePath, os.O_CREATE|os.O_RDWR, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -93,5 +101,9 @@ func (p *Zpool) Destroy(ctx context.Context, e Execer) error {
|
|||||||
return errors.Wrapf(err, "remove pool image")
|
return errors.Wrapf(err, "remove pool image")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(p.args.Mountpoint); err != nil {
|
||||||
|
return errors.Wrapf(err, "remove mountpoint dir %q", p.args.Mountpoint)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
func check(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type platformtestFuncDeclFinder struct {
|
||||||
|
pkg *packages.Package
|
||||||
|
testFuncs []*ast.FuncDecl
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPlatformtestFunc(n *ast.FuncDecl) bool {
|
||||||
|
if !n.Name.IsExported() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if n.Recv != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if n.Type.Results.NumFields() != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if n.Type.Params.NumFields() != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
se, ok := n.Type.Params.List[0].Type.(*ast.StarExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sel, ok := se.X.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
x, ok := sel.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if x.Name != "platformtest" || sel.Sel.Name != "Context" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *platformtestFuncDeclFinder) Visit(n2 ast.Node) ast.Visitor {
|
||||||
|
switch n := n2.(type) {
|
||||||
|
case *ast.File:
|
||||||
|
return e
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if isPlatformtestFunc(n) {
|
||||||
|
e.testFuncs = append(e.testFuncs, n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// TODO safeguards that prevent us from deleting non-generated generated_cases.go
|
||||||
|
os.Remove("generated_cases.go")
|
||||||
|
// (no error handling to easily cover the case where the file doesn't exist)
|
||||||
|
|
||||||
|
pkgs, err := packages.Load(
|
||||||
|
&packages.Config{
|
||||||
|
Mode: packages.LoadFiles,
|
||||||
|
Tests: false,
|
||||||
|
},
|
||||||
|
os.Args[1],
|
||||||
|
)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
if len(pkgs) != 1 {
|
||||||
|
panic(pkgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := pkgs[0]
|
||||||
|
|
||||||
|
var tests []*ast.FuncDecl
|
||||||
|
|
||||||
|
for _, f := range p.GoFiles {
|
||||||
|
s := token.NewFileSet()
|
||||||
|
a, err := parser.ParseFile(s, f, nil, parser.AllErrors)
|
||||||
|
check(err)
|
||||||
|
finder := &platformtestFuncDeclFinder{
|
||||||
|
pkg: p,
|
||||||
|
}
|
||||||
|
ast.Walk(finder, a)
|
||||||
|
tests = append(tests, finder.testFuncs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(tests, func(i, j int) bool {
|
||||||
|
return strings.Compare(tests[i].Name.Name, tests[j].Name.Name) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
casesTemplate := `
|
||||||
|
// Code generated by zrepl tooling; DO NOT EDIT.
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
var Cases = []Case {
|
||||||
|
{{- range . -}}
|
||||||
|
{{ .Name }},
|
||||||
|
{{ end -}}
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
t, err := template.New("CaseFunc").Parse(casesTemplate)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = t.Execute(&buf, tests)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
formatted, err := format.Source(buf.Bytes())
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile("generated_cases.go", formatted, 0664)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Code generated by zrepl tooling; DO NOT EDIT.
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
var Cases = []Case{BatchDestroy,
|
||||||
|
CreateReplicationCursor,
|
||||||
|
GetNonexistent,
|
||||||
|
HoldsWork,
|
||||||
|
IdempotentBookmark,
|
||||||
|
IdempotentDestroy,
|
||||||
|
IdempotentHold,
|
||||||
|
ListFilesystemVersionsFilesystemNotExist,
|
||||||
|
ListFilesystemVersionsTypeFilteringAndPrefix,
|
||||||
|
ListFilesystemVersionsUserrefs,
|
||||||
|
ListFilesystemVersionsZeroExistIsNotAnError,
|
||||||
|
ListFilesystemsNoFilter,
|
||||||
|
ReceiveForceIntoEncryptedErr,
|
||||||
|
ReceiveForceRollbackWorksUnencrypted,
|
||||||
|
ReplicationIncrementalCleansUpStaleAbstractionsWithCacheOnSecondReplication,
|
||||||
|
ReplicationIncrementalCleansUpStaleAbstractionsWithoutCacheOnSecondReplication,
|
||||||
|
ReplicationIncrementalIsPossibleIfCommonSnapshotIsDestroyed,
|
||||||
|
ReplicationIsResumableFullSend__DisableIncrementalStepHolds_False,
|
||||||
|
ReplicationIsResumableFullSend__DisableIncrementalStepHolds_True,
|
||||||
|
ResumableRecvAndTokenHandling,
|
||||||
|
ResumeTokenParsing,
|
||||||
|
SendArgsValidationEncryptedSendOfUnencryptedDatasetForbidden,
|
||||||
|
SendArgsValidationResumeTokenDifferentFilesystemForbidden,
|
||||||
|
SendArgsValidationResumeTokenEncryptionMismatchForbidden,
|
||||||
|
UndestroyableSnapshotParsing,
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zrepl/zrepl/platformtest"
|
||||||
|
"github.com/zrepl/zrepl/zfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HoldsWork(ctx *platformtest.Context) {
|
||||||
|
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
||||||
|
DESTROYROOT
|
||||||
|
CREATEROOT
|
||||||
|
+ "foo bar"
|
||||||
|
+ "foo bar@snap name"
|
||||||
|
`)
|
||||||
|
|
||||||
|
fs := path.Join(ctx.RootDataset, "foo bar")
|
||||||
|
|
||||||
|
err := zfs.ZFSHold(ctx, fs, fsversion(ctx, fs, "@snap name"), "tag 1")
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
err = zfs.ZFSHold(ctx, fs, fsversion(ctx, fs, "@snap name"), "tag 2")
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
holds, err := zfs.ZFSHolds(ctx, fs, "snap name")
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
require.Len(ctx, holds, 2)
|
||||||
|
require.Contains(ctx, holds, "tag 1")
|
||||||
|
require.Contains(ctx, holds, "tag 2")
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package tests
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zrepl/zrepl/platformtest"
|
"github.com/zrepl/zrepl/platformtest"
|
||||||
"github.com/zrepl/zrepl/zfs"
|
"github.com/zrepl/zrepl/zfs"
|
||||||
)
|
)
|
||||||
@@ -19,22 +20,23 @@ func IdempotentBookmark(ctx *platformtest.Context) {
|
|||||||
|
|
||||||
fs := fmt.Sprintf("%s/foo bar", ctx.RootDataset)
|
fs := fmt.Sprintf("%s/foo bar", ctx.RootDataset)
|
||||||
|
|
||||||
asnap := sendArgVersion(ctx, fs, "@a snap")
|
asnap := fsversion(ctx, fs, "@a snap")
|
||||||
anotherSnap := sendArgVersion(ctx, fs, "@another snap")
|
anotherSnap := fsversion(ctx, fs, "@another snap")
|
||||||
|
|
||||||
err := zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
aBookmark, err := zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// do it again, should be idempotent
|
// do it again, should be idempotent
|
||||||
err = zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
aBookmarkIdemp, err := zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
assert.Equal(ctx, aBookmark, aBookmarkIdemp)
|
||||||
|
|
||||||
// should fail for another snapshot
|
// should fail for another snapshot
|
||||||
err = zfs.ZFSBookmark(ctx, fs, anotherSnap, "a bookmark")
|
_, err = zfs.ZFSBookmark(ctx, fs, anotherSnap, "a bookmark")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -48,7 +50,7 @@ func IdempotentBookmark(ctx *platformtest.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do it again, should fail with special error type
|
// do it again, should fail with special error type
|
||||||
err = zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
_, err = zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ func IdempotentDestroy(ctx *platformtest.Context) {
|
|||||||
`)
|
`)
|
||||||
|
|
||||||
fs := fmt.Sprintf("%s/foo bar", ctx.RootDataset)
|
fs := fmt.Sprintf("%s/foo bar", ctx.RootDataset)
|
||||||
asnap := sendArgVersion(ctx, fs, "@a snap")
|
asnap := fsversion(ctx, fs, "@a snap")
|
||||||
err := zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
_, err := zfs.ZFSBookmark(ctx, fs, asnap, "a bookmark")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,9 +85,6 @@ func ListFilesystemVersionsZeroExistIsNotAnError(t *platformtest.Context) {
|
|||||||
vs, err := zfs.ZFSListFilesystemVersions(t, mustDatasetPath(fs), zfs.ListFilesystemVersionsOptions{})
|
vs, err := zfs.ZFSListFilesystemVersions(t, mustDatasetPath(fs), zfs.ListFilesystemVersionsOptions{})
|
||||||
require.Empty(t, vs)
|
require.Empty(t, vs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dsne, ok := err.(*zfs.DatasetDoesNotExist)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, fs, dsne.Path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListFilesystemVersionsFilesystemNotExist(t *platformtest.Context) {
|
func ListFilesystemVersionsFilesystemNotExist(t *platformtest.Context) {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zrepl/zrepl/platformtest"
|
||||||
|
"github.com/zrepl/zrepl/zfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReceiveForceIntoEncryptedErr(ctx *platformtest.Context) {
|
||||||
|
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
||||||
|
DESTROYROOT
|
||||||
|
CREATEROOT
|
||||||
|
+ "foo bar" encrypted
|
||||||
|
+ "sender" encrypted
|
||||||
|
+ "sender@1"
|
||||||
|
`)
|
||||||
|
|
||||||
|
rfs := fmt.Sprintf("%s/foo bar", ctx.RootDataset)
|
||||||
|
sfs := fmt.Sprintf("%s/sender", ctx.RootDataset)
|
||||||
|
sfsSnap1 := sendArgVersion(ctx, sfs, "@1")
|
||||||
|
|
||||||
|
sendArgs, err := zfs.ZFSSendArgsUnvalidated{
|
||||||
|
FS: sfs,
|
||||||
|
Encrypted: &zfs.NilBool{B: false},
|
||||||
|
From: nil,
|
||||||
|
To: &sfsSnap1,
|
||||||
|
ResumeToken: "",
|
||||||
|
}.Validate(ctx)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
sendStream, err := zfs.ZFSSend(ctx, sendArgs)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
recvOpts := zfs.RecvOptions{
|
||||||
|
RollbackAndForceRecv: true,
|
||||||
|
SavePartialRecvState: false,
|
||||||
|
}
|
||||||
|
err = zfs.ZFSRecv(ctx, rfs, &zfs.ZFSSendArgVersion{RelName: "@1", GUID: sfsSnap1.GUID}, sendStream, recvOpts)
|
||||||
|
require.Error(ctx, err)
|
||||||
|
re, ok := err.(*zfs.RecvDestroyOrOverwriteEncryptedErr)
|
||||||
|
require.True(ctx, ok)
|
||||||
|
require.Contains(ctx, re.Error(), "zfs receive -F cannot be used to destroy an encrypted filesystem or overwrite an unencrypted one with an encrypted on")
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zrepl/zrepl/platformtest"
|
||||||
|
"github.com/zrepl/zrepl/zfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReceiveForceRollbackWorksUnencrypted(ctx *platformtest.Context) {
|
||||||
|
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
||||||
|
DESTROYROOT
|
||||||
|
CREATEROOT
|
||||||
|
+ "foo bar"
|
||||||
|
+ "foo bar@a snap"
|
||||||
|
+ "foo bar@another snap"
|
||||||
|
+ "foo bar@snap3"
|
||||||
|
+ "sender"
|
||||||
|
+ "sender@1"
|
||||||
|
`)
|
||||||
|
|
||||||
|
rfs := fmt.Sprintf("%s/foo bar", ctx.RootDataset)
|
||||||
|
sfs := fmt.Sprintf("%s/sender", ctx.RootDataset)
|
||||||
|
sfsSnap1 := sendArgVersion(ctx, sfs, "@1")
|
||||||
|
|
||||||
|
sendArgs, err := zfs.ZFSSendArgsUnvalidated{
|
||||||
|
FS: sfs,
|
||||||
|
Encrypted: &zfs.NilBool{B: false},
|
||||||
|
From: nil,
|
||||||
|
To: &sfsSnap1,
|
||||||
|
ResumeToken: "",
|
||||||
|
}.Validate(ctx)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
sendStream, err := zfs.ZFSSend(ctx, sendArgs)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
recvOpts := zfs.RecvOptions{
|
||||||
|
RollbackAndForceRecv: true,
|
||||||
|
SavePartialRecvState: false,
|
||||||
|
}
|
||||||
|
err = zfs.ZFSRecv(ctx, rfs, &zfs.ZFSSendArgVersion{RelName: "@1", GUID: sfsSnap1.GUID}, sendStream, recvOpts)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
// assert exists on receiver
|
||||||
|
rfsSnap1 := fsversion(ctx, rfs, "@1")
|
||||||
|
// assert it's the only one (rollback and force-recv should be blowing away the other filesystems)
|
||||||
|
rfsVersions, err := zfs.ZFSListFilesystemVersions(ctx, mustDatasetPath(rfs), zfs.ListFilesystemVersionsOptions{})
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
assert.Len(ctx, rfsVersions, 1)
|
||||||
|
assert.Equal(ctx, rfsVersions[0], rfsSnap1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/kr/pretty"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zrepl/zrepl/daemon/filters"
|
||||||
|
"github.com/zrepl/zrepl/endpoint"
|
||||||
|
"github.com/zrepl/zrepl/platformtest"
|
||||||
|
"github.com/zrepl/zrepl/replication"
|
||||||
|
"github.com/zrepl/zrepl/replication/logic"
|
||||||
|
"github.com/zrepl/zrepl/replication/logic/pdu"
|
||||||
|
"github.com/zrepl/zrepl/replication/report"
|
||||||
|
"github.com/zrepl/zrepl/util/limitio"
|
||||||
|
"github.com/zrepl/zrepl/zfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mimics the replication invocations of an active-side job
|
||||||
|
// for a single sender-receiver filesystem pair
|
||||||
|
//
|
||||||
|
// each invocation of method Do results in the construction
|
||||||
|
// of a new sender and receiver instance and one blocking invocation
|
||||||
|
// of the replication engine without encryption
|
||||||
|
type replicationInvocation struct {
|
||||||
|
sjid, rjid endpoint.JobID
|
||||||
|
sfs string
|
||||||
|
rfsRoot string
|
||||||
|
interceptSender func(e *endpoint.Sender) logic.Sender
|
||||||
|
disableIncrementalStepHolds bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i replicationInvocation) Do(ctx *platformtest.Context) *report.Report {
|
||||||
|
|
||||||
|
if i.interceptSender == nil {
|
||||||
|
i.interceptSender = func(e *endpoint.Sender) logic.Sender { return e }
|
||||||
|
}
|
||||||
|
|
||||||
|
sfilter := filters.NewDatasetMapFilter(1, true)
|
||||||
|
err := sfilter.Add(i.sfs, "ok")
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
sender := i.interceptSender(endpoint.NewSender(endpoint.SenderConfig{
|
||||||
|
FSF: sfilter.AsFilter(),
|
||||||
|
Encrypt: &zfs.NilBool{B: false},
|
||||||
|
DisableIncrementalStepHolds: i.disableIncrementalStepHolds,
|
||||||
|
JobID: i.sjid,
|
||||||
|
}))
|
||||||
|
receiver := endpoint.NewReceiver(endpoint.ReceiverConfig{
|
||||||
|
JobID: i.rjid,
|
||||||
|
AppendClientIdentity: false,
|
||||||
|
RootWithoutClientComponent: mustDatasetPath(i.rfsRoot),
|
||||||
|
UpdateLastReceivedHold: true,
|
||||||
|
})
|
||||||
|
plannerPolicy := logic.PlannerPolicy{
|
||||||
|
EncryptedSend: logic.TriFromBool(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
report, wait := replication.Do(
|
||||||
|
ctx,
|
||||||
|
logic.NewPlanner(nil, nil, sender, receiver, plannerPolicy),
|
||||||
|
)
|
||||||
|
wait(true)
|
||||||
|
return report()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i replicationInvocation) ReceiveSideFilesystem() string {
|
||||||
|
return path.Join(i.rfsRoot, i.sfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplicationIncrementalIsPossibleIfCommonSnapshotIsDestroyed(ctx *platformtest.Context) {
|
||||||
|
|
||||||
|
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
||||||
|
CREATEROOT
|
||||||
|
+ "sender"
|
||||||
|
+ "sender@1"
|
||||||
|
+ "receiver"
|
||||||
|
R zfs create -p "${ROOTDS}/receiver/${ROOTDS}"
|
||||||
|
`)
|
||||||
|
|
||||||
|
sjid := endpoint.MustMakeJobID("sender-job")
|
||||||
|
rjid := endpoint.MustMakeJobID("receiver-job")
|
||||||
|
|
||||||
|
sfs := ctx.RootDataset + "/sender"
|
||||||
|
rfsRoot := ctx.RootDataset + "/receiver"
|
||||||
|
snap1 := fsversion(ctx, sfs, "@1")
|
||||||
|
|
||||||
|
rep := replicationInvocation{
|
||||||
|
sjid: sjid,
|
||||||
|
rjid: rjid,
|
||||||
|
sfs: sfs,
|
||||||
|
rfsRoot: rfsRoot,
|
||||||
|
disableIncrementalStepHolds: false,
|
||||||
|
}
|
||||||
|
rfs := rep.ReceiveSideFilesystem()
|
||||||
|
|
||||||
|
// first replication
|
||||||
|
report := rep.Do(ctx)
|
||||||
|
ctx.Logf("\n%s", pretty.Sprint(report))
|
||||||
|
|
||||||
|
// assert @1 exists on receiver
|
||||||
|
_ = fsversion(ctx, rfs, "@1")
|
||||||
|
|
||||||
|
// cut off the common base between sender and receiver
|
||||||
|
// (replication engine guarantees resumability through bookmarks)
|
||||||
|
err := zfs.ZFSDestroy(ctx, snap1.FullPath(sfs))
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
// assert that the replication cursor has been created
|
||||||
|
snap1CursorName, err := endpoint.ReplicationCursorBookmarkName(sfs, snap1.Guid, sjid)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
snap1CursorInfo, err := zfs.ZFSGetFilesystemVersion(ctx, sfs+"#"+snap1CursorName)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
require.True(ctx, snap1CursorInfo.IsBookmark())
|
||||||
|
|
||||||
|
// second replication of a new snapshot, should use the cursor
|
||||||
|
mustSnapshot(ctx, sfs+"@2")
|
||||||
|
report = rep.Do(ctx)
|
||||||
|
ctx.Logf("\n%s", pretty.Sprint(report))
|
||||||
|
_ = fsversion(ctx, rfs, "@2")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplicationIncrementalCleansUpStaleAbstractionsWithCacheOnSecondReplication(ctx *platformtest.Context) {
|
||||||
|
implReplicationIncrementalCleansUpStaleAbstractions(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplicationIncrementalCleansUpStaleAbstractionsWithoutCacheOnSecondReplication(ctx *platformtest.Context) {
|
||||||
|
implReplicationIncrementalCleansUpStaleAbstractions(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func implReplicationIncrementalCleansUpStaleAbstractions(ctx *platformtest.Context, invalidateCacheBeforeSecondReplication bool) {
|
||||||
|
|
||||||
|
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
||||||
|
CREATEROOT
|
||||||
|
+ "sender"
|
||||||
|
+ "sender@1"
|
||||||
|
+ "sender@2"
|
||||||
|
+ "sender#2" "sender@2"
|
||||||
|
+ "sender@3"
|
||||||
|
+ "receiver"
|
||||||
|
R zfs create -p "${ROOTDS}/receiver/${ROOTDS}"
|
||||||
|
`)
|
||||||
|
|
||||||
|
sjid := endpoint.MustMakeJobID("sender-job")
|
||||||
|
ojid := endpoint.MustMakeJobID("other-job")
|
||||||
|
rjid := endpoint.MustMakeJobID("receiver-job")
|
||||||
|
|
||||||
|
sfs := ctx.RootDataset + "/sender"
|
||||||
|
rfsRoot := ctx.RootDataset + "/receiver"
|
||||||
|
|
||||||
|
rep := replicationInvocation{
|
||||||
|
sjid: sjid,
|
||||||
|
rjid: rjid,
|
||||||
|
sfs: sfs,
|
||||||
|
rfsRoot: rfsRoot,
|
||||||
|
disableIncrementalStepHolds: false,
|
||||||
|
}
|
||||||
|
rfs := rep.ReceiveSideFilesystem()
|
||||||
|
|
||||||
|
// first replication
|
||||||
|
report := rep.Do(ctx)
|
||||||
|
ctx.Logf("\n%s", pretty.Sprint(report))
|
||||||
|
|
||||||
|
// assert most recent send-side version @3 exists on receiver (=replication succeeded)
|
||||||
|
rSnap3 := fsversion(ctx, rfs, "@3")
|
||||||
|
// assert the source-side versions not managed by zrepl still exist
|
||||||
|
snap1 := fsversion(ctx, sfs, "@1")
|
||||||
|
snap2 := fsversion(ctx, sfs, "@2")
|
||||||
|
_ = fsversion(ctx, sfs, "#2") // non-replicationc-cursor bookmarks should not be affected
|
||||||
|
snap3 := fsversion(ctx, sfs, "@3")
|
||||||
|
// assert a replication cursor is in place
|
||||||
|
snap3CursorName, err := endpoint.ReplicationCursorBookmarkName(sfs, snap3.Guid, sjid)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
_ = fsversion(ctx, sfs, "#"+snap3CursorName)
|
||||||
|
// assert a last-received hold is in place
|
||||||
|
expectRjidHoldTag, err := endpoint.LastReceivedHoldTag(rjid)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
holds, err := zfs.ZFSHolds(ctx, rfs, rSnap3.Name)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
require.Contains(ctx, holds, expectRjidHoldTag)
|
||||||
|
|
||||||
|
// create artifical stale replication cursors
|
||||||
|
createArtificalStaleAbstractions := func(jobId endpoint.JobID) []endpoint.Abstraction {
|
||||||
|
snap2Cursor, err := endpoint.CreateReplicationCursor(ctx, sfs, snap2, jobId) // no shadow
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
// create artifical stale step holds jobId
|
||||||
|
snap1Hold, err := endpoint.HoldStep(ctx, sfs, snap1, jobId) // no shadow
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
snap2Hold, err := endpoint.HoldStep(ctx, sfs, snap2, jobId) // no shadow
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
return []endpoint.Abstraction{snap2Cursor, snap1Hold, snap2Hold}
|
||||||
|
}
|
||||||
|
createArtificalStaleAbstractions(sjid)
|
||||||
|
ojidSendAbstractions := createArtificalStaleAbstractions(ojid)
|
||||||
|
|
||||||
|
snap3ojidLastReceivedHold, err := endpoint.CreateLastReceivedHold(ctx, rfs, fsversion(ctx, rfs, "@3"), ojid)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
require.True(ctx, zfs.FilesystemVersionEqualIdentity(fsversion(ctx, rfs, "@3"), snap3ojidLastReceivedHold.GetFilesystemVersion()))
|
||||||
|
|
||||||
|
// take another 2 snapshots
|
||||||
|
mustSnapshot(ctx, sfs+"@4")
|
||||||
|
mustSnapshot(ctx, sfs+"@5")
|
||||||
|
snap5 := fsversion(ctx, sfs, "@5")
|
||||||
|
|
||||||
|
if invalidateCacheBeforeSecondReplication {
|
||||||
|
endpoint.SendAbstractionsCacheInvalidate(sfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do another replication
|
||||||
|
// - ojid's abstractions should not be affected on either side
|
||||||
|
// - stale abstractions of sjid and rjid should be cleaned up
|
||||||
|
// - 1 replication cursors and 1 last-received hold should be present
|
||||||
|
|
||||||
|
checkOjidAbstractionsExist := func() {
|
||||||
|
var expectedOjidAbstractions []endpoint.Abstraction
|
||||||
|
expectedOjidAbstractions = append(expectedOjidAbstractions, ojidSendAbstractions...)
|
||||||
|
expectedOjidAbstractions = append(expectedOjidAbstractions, snap3ojidLastReceivedHold)
|
||||||
|
|
||||||
|
sfsAndRfsFilter := filters.NewDatasetMapFilter(2, true)
|
||||||
|
require.NoError(ctx, sfsAndRfsFilter.Add(sfs, "ok"))
|
||||||
|
require.NoError(ctx, sfsAndRfsFilter.Add(rfs, "ok"))
|
||||||
|
rAbs, rAbsErrs, err := endpoint.ListAbstractions(ctx, endpoint.ListZFSHoldsAndBookmarksQuery{
|
||||||
|
FS: endpoint.ListZFSHoldsAndBookmarksQueryFilesystemFilter{Filter: sfsAndRfsFilter},
|
||||||
|
JobID: &ojid,
|
||||||
|
What: endpoint.AbstractionTypesAll,
|
||||||
|
Concurrency: 1,
|
||||||
|
})
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
require.Len(ctx, rAbsErrs, 0)
|
||||||
|
ctx.Logf("rAbs=%s", rAbs)
|
||||||
|
ctx.Logf("expectedOjidAbstractions=%s", expectedOjidAbstractions)
|
||||||
|
require.Equal(ctx, len(expectedOjidAbstractions), len(rAbs))
|
||||||
|
for _, ea := range expectedOjidAbstractions {
|
||||||
|
ctx.Logf("looking for %s %#v", ea, ea.GetFilesystemVersion())
|
||||||
|
found := false
|
||||||
|
for _, a := range rAbs {
|
||||||
|
eq := endpoint.AbstractionEquals(ea, a)
|
||||||
|
ctx.Logf("comp=%v for %s %#v", eq, a, a.GetFilesystemVersion())
|
||||||
|
found = found || eq
|
||||||
|
}
|
||||||
|
require.True(ctx, found, "%s", ea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkOjidAbstractionsExist()
|
||||||
|
|
||||||
|
report = rep.Do(ctx)
|
||||||
|
ctx.Logf("\n%s", pretty.Sprint(report))
|
||||||
|
|
||||||
|
checkOjidAbstractionsExist()
|
||||||
|
|
||||||
|
_ = fsversion(ctx, sfs, "@1")
|
||||||
|
_ = fsversion(ctx, sfs, "@2")
|
||||||
|
_ = fsversion(ctx, sfs, "#2")
|
||||||
|
_ = fsversion(ctx, sfs, "@3")
|
||||||
|
_ = fsversion(ctx, sfs, "@4")
|
||||||
|
_ = fsversion(ctx, sfs, "@5")
|
||||||
|
|
||||||
|
_ = fsversion(ctx, rfs, "@3")
|
||||||
|
_ = fsversion(ctx, rfs, "@4")
|
||||||
|
_ = fsversion(ctx, rfs, "@5")
|
||||||
|
|
||||||
|
// check bookmark situation
|
||||||
|
{
|
||||||
|
sBms, err := zfs.ZFSListFilesystemVersions(ctx, mustDatasetPath(sfs), zfs.ListFilesystemVersionsOptions{
|
||||||
|
Types: zfs.Bookmarks,
|
||||||
|
})
|
||||||
|
ctx.Logf("sbms=%s", sBms)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
|
||||||
|
snap5SjidCursorName, err := endpoint.ReplicationCursorBookmarkName(sfs, snap5.Guid, sjid)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
snap2SjidCursorName, err := endpoint.ReplicationCursorBookmarkName(sfs, snap2.Guid, sjid)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
snap2OjidCursorName, err := endpoint.ReplicationCursorBookmarkName(sfs, snap2.Guid, ojid)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
var bmNames []string
|
||||||
|
for _, bm := range sBms {
|
||||||
|
bmNames = append(bmNames, bm.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if invalidateCacheBeforeSecondReplication {
|
||||||
|
require.Len(ctx, sBms, 3)
|
||||||
|
require.Contains(ctx, bmNames, snap5SjidCursorName)
|
||||||
|
require.Contains(ctx, bmNames, snap2OjidCursorName)
|
||||||
|
require.Contains(ctx, bmNames, "2")
|
||||||
|
} else {
|
||||||
|
require.Len(ctx, sBms, 4)
|
||||||
|
require.Contains(ctx, bmNames, snap5SjidCursorName)
|
||||||
|
require.Contains(ctx, bmNames, snap2SjidCursorName)
|
||||||
|
require.Contains(ctx, bmNames, snap2OjidCursorName)
|
||||||
|
require.Contains(ctx, bmNames, "2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check last-received hold moved
|
||||||
|
{
|
||||||
|
rAbs, rAbsErrs, err := endpoint.ListAbstractions(ctx, endpoint.ListZFSHoldsAndBookmarksQuery{
|
||||||
|
FS: endpoint.ListZFSHoldsAndBookmarksQueryFilesystemFilter{FS: &rfs},
|
||||||
|
JobID: &rjid,
|
||||||
|
What: endpoint.AbstractionTypesAll,
|
||||||
|
Concurrency: 1,
|
||||||
|
})
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
require.Len(ctx, rAbsErrs, 0)
|
||||||
|
require.Len(ctx, rAbs, 1)
|
||||||
|
require.Equal(ctx, rAbs[0].GetType(), endpoint.AbstractionLastReceivedHold)
|
||||||
|
require.Equal(ctx, *rAbs[0].GetJobID(), rjid)
|
||||||
|
require.Equal(ctx, rAbs[0].GetFilesystemVersion().GetGuid(), snap5.GetGuid())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type PartialSender struct {
|
||||||
|
*endpoint.Sender
|
||||||
|
failAfterByteCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ logic.Sender = (*PartialSender)(nil)
|
||||||
|
|
||||||
|
func (s *PartialSender) Send(ctx context.Context, r *pdu.SendReq) (r1 *pdu.SendRes, r2 io.ReadCloser, r3 error) {
|
||||||
|
r1, r2, r3 = s.Sender.Send(ctx, r)
|
||||||
|
r2 = limitio.ReadCloser(r2, s.failAfterByteCount)
|
||||||
|
return r1, r2, r3
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplicationIsResumableFullSend__DisableIncrementalStepHolds_False(ctx *platformtest.Context) {
|
||||||
|
implReplicationIsResumableFullSend(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplicationIsResumableFullSend__DisableIncrementalStepHolds_True(ctx *platformtest.Context) {
|
||||||
|
implReplicationIsResumableFullSend(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func implReplicationIsResumableFullSend(ctx *platformtest.Context, disableIncrementalStepHolds bool) {
|
||||||
|
|
||||||
|
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
||||||
|
CREATEROOT
|
||||||
|
+ "sender"
|
||||||
|
+ "receiver"
|
||||||
|
R zfs create -p "${ROOTDS}/receiver/${ROOTDS}"
|
||||||
|
`)
|
||||||
|
|
||||||
|
sjid := endpoint.MustMakeJobID("sender-job")
|
||||||
|
rjid := endpoint.MustMakeJobID("receiver-job")
|
||||||
|
|
||||||
|
sfs := ctx.RootDataset + "/sender"
|
||||||
|
rfsRoot := ctx.RootDataset + "/receiver"
|
||||||
|
|
||||||
|
sfsmp, err := zfs.ZFSGetMountpoint(ctx, sfs)
|
||||||
|
require.NoError(ctx, err)
|
||||||
|
require.True(ctx, sfsmp.Mounted)
|
||||||
|
|
||||||
|
writeDummyData(path.Join(sfsmp.Mountpoint, "dummy.data"), 1<<22)
|
||||||
|
mustSnapshot(ctx, sfs+"@1")
|
||||||
|
snap1 := fsversion(ctx, sfs, "@1")
|
||||||
|
|
||||||
|
rep := replicationInvocation{
|
||||||
|
sjid: sjid,
|
||||||
|
rjid: rjid,
|
||||||
|
sfs: sfs,
|
||||||
|
rfsRoot: rfsRoot,
|
||||||
|
interceptSender: func(e *endpoint.Sender) logic.Sender {
|
||||||
|
return &PartialSender{Sender: e, failAfterByteCount: 1 << 20}
|
||||||
|
},
|
||||||
|
disableIncrementalStepHolds: disableIncrementalStepHolds,
|
||||||
|
}
|
||||||
|
rfs := rep.ReceiveSideFilesystem()
|
||||||
|
|
||||||
|
for i := 2; i < 10; i++ {
|
||||||
|
report := rep.Do(ctx)
|
||||||
|
ctx.Logf("\n%s", pretty.Sprint(report))
|
||||||
|
|
||||||
|
// always attempt to destroy the incremental source
|
||||||
|
err := zfs.ZFSDestroy(ctx, snap1.FullPath(sfs))
|
||||||
|
if i < 4 {
|
||||||
|
// we configured the PartialSender to fail after 1<<20 bytes
|
||||||
|
// and we wrote dummy data 1<<22 bytes, thus at least
|
||||||
|
// for the first 4 times this should not be possible
|
||||||
|
// due to step holds
|
||||||
|
require.Error(ctx, err)
|
||||||
|
require.Contains(ctx, err.Error(), "dataset is busy")
|
||||||
|
}
|
||||||
|
|
||||||
|
// and create some additional snapshots that could
|
||||||
|
// confuse a naive implementation that doesn't take into
|
||||||
|
// account resume state when planning replication
|
||||||
|
if i == 2 || i == 3 {
|
||||||
|
// no significant size to avoid making this test run longer than necessary
|
||||||
|
mustSnapshot(ctx, fmt.Sprintf("%s@%d", sfs, i))
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(ctx, report.Attempts, 1)
|
||||||
|
require.Nil(ctx, report.Attempts[0].PlanError)
|
||||||
|
require.Len(ctx, report.Attempts[0].Filesystems, 1)
|
||||||
|
if len(report.Attempts[0].Filesystems[0].Steps) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure all the filesystem versions we created
|
||||||
|
// were replicated by the replication loop
|
||||||
|
_ = fsversion(ctx, rfs, "@1")
|
||||||
|
_ = fsversion(ctx, rfs, "@2")
|
||||||
|
_ = fsversion(ctx, rfs, "@3")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/zrepl/zrepl/zfs"
|
"github.com/zrepl/zrepl/zfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReplicationCursor(ctx *platformtest.Context) {
|
func CreateReplicationCursor(ctx *platformtest.Context) {
|
||||||
|
|
||||||
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
platformtest.Run(ctx, platformtest.PanicErr, ctx.RootDataset, `
|
||||||
CREATEROOT
|
CREATEROOT
|
||||||
@@ -19,9 +19,9 @@ func ReplicationCursor(ctx *platformtest.Context) {
|
|||||||
+ "foo bar@1 with space"
|
+ "foo bar@1 with space"
|
||||||
R zfs bookmark "${ROOTDS}/foo bar@1 with space" "${ROOTDS}/foo bar#1 with space"
|
R zfs bookmark "${ROOTDS}/foo bar@1 with space" "${ROOTDS}/foo bar#1 with space"
|
||||||
+ "foo bar@2 with space"
|
+ "foo bar@2 with space"
|
||||||
R zfs bookmark "${ROOTDS}/foo bar@1 with space" "${ROOTDS}/foo bar#2 with space"
|
R zfs bookmark "${ROOTDS}/foo bar@2 with space" "${ROOTDS}/foo bar#2 with space"
|
||||||
+ "foo bar@3 with space"
|
+ "foo bar@3 with space"
|
||||||
R zfs bookmark "${ROOTDS}/foo bar@1 with space" "${ROOTDS}/foo bar#3 with space"
|
R zfs bookmark "${ROOTDS}/foo bar@3 with space" "${ROOTDS}/foo bar#3 with space"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
jobid := endpoint.MustMakeJobID("zreplplatformtest")
|
jobid := endpoint.MustMakeJobID("zreplplatformtest")
|
||||||
@@ -32,13 +32,29 @@ func ReplicationCursor(ctx *platformtest.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs := ds.ToString()
|
fs := ds.ToString()
|
||||||
snap := fsversion(ctx, fs, "@1 with space")
|
|
||||||
|
|
||||||
destroyed, err := endpoint.MoveReplicationCursor(ctx, fs, &snap, jobid)
|
checkCreateCursor := func(createErr error, c endpoint.Abstraction, references zfs.FilesystemVersion) {
|
||||||
if err != nil {
|
assert.NoError(ctx, createErr)
|
||||||
panic(err)
|
expectName, err := endpoint.ReplicationCursorBookmarkName(fs, references.Guid, jobid)
|
||||||
|
assert.NoError(ctx, err)
|
||||||
|
require.Equal(ctx, expectName, c.GetFilesystemVersion().Name)
|
||||||
}
|
}
|
||||||
assert.Empty(ctx, destroyed)
|
|
||||||
|
snap := fsversion(ctx, fs, "@1 with space")
|
||||||
|
book := fsversion(ctx, fs, "#1 with space")
|
||||||
|
|
||||||
|
// create first cursor
|
||||||
|
cursorOfSnap, err := endpoint.CreateReplicationCursor(ctx, fs, snap, jobid)
|
||||||
|
checkCreateCursor(err, cursorOfSnap, snap)
|
||||||
|
// check CreateReplicationCursor is idempotent (for snapshot target)
|
||||||
|
cursorOfSnapIdemp, err := endpoint.CreateReplicationCursor(ctx, fs, snap, jobid)
|
||||||
|
checkCreateCursor(err, cursorOfSnap, snap)
|
||||||
|
// ... for target = non-cursor bookmark
|
||||||
|
_, err = endpoint.CreateReplicationCursor(ctx, fs, book, jobid)
|
||||||
|
assert.Equal(ctx, zfs.ErrBookmarkCloningNotSupported, err)
|
||||||
|
// ... for target = replication cursor bookmark to be created
|
||||||
|
cursorOfCursor, err := endpoint.CreateReplicationCursor(ctx, fs, cursorOfSnapIdemp.GetFilesystemVersion(), jobid)
|
||||||
|
checkCreateCursor(err, cursorOfCursor, cursorOfSnap.GetFilesystemVersion())
|
||||||
|
|
||||||
snapProps, err := zfs.ZFSGetFilesystemVersion(ctx, snap.FullPath(fs))
|
snapProps, err := zfs.ZFSGetFilesystemVersion(ctx, snap.FullPath(fs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -56,14 +72,4 @@ func ReplicationCursor(ctx *platformtest.Context) {
|
|||||||
panic(fmt.Sprintf("guids do not match: %v != %v", bm.Guid, snapProps.Guid))
|
panic(fmt.Sprintf("guids do not match: %v != %v", bm.Guid, snapProps.Guid))
|
||||||
}
|
}
|
||||||
|
|
||||||
// try moving
|
|
||||||
cursor1BookmarkName, err := endpoint.ReplicationCursorBookmarkName(fs, snap.Guid, jobid)
|
|
||||||
require.NoError(ctx, err)
|
|
||||||
|
|
||||||
snap2 := fsversion(ctx, fs, "@2 with space")
|
|
||||||
destroyed, err = endpoint.MoveReplicationCursor(ctx, fs, &snap2, jobid)
|
|
||||||
require.NoError(ctx, err)
|
|
||||||
require.Equal(ctx, 1, len(destroyed))
|
|
||||||
require.Equal(ctx, endpoint.AbstractionReplicationCursorBookmarkV2, destroyed[0].GetType())
|
|
||||||
require.Equal(ctx, cursor1BookmarkName, destroyed[0].GetName())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,22 +13,4 @@ func (c Case) String() string {
|
|||||||
return runtime.FuncForPC(reflect.ValueOf(c).Pointer()).Name()
|
return runtime.FuncForPC(reflect.ValueOf(c).Pointer()).Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
var Cases = []Case{
|
//go:generate ../../artifacts/generate-platform-test-list github.com/zrepl/zrepl/platformtest/tests
|
||||||
BatchDestroy,
|
|
||||||
UndestroyableSnapshotParsing,
|
|
||||||
GetNonexistent,
|
|
||||||
ReplicationCursor,
|
|
||||||
IdempotentHold,
|
|
||||||
IdempotentBookmark,
|
|
||||||
IdempotentDestroy,
|
|
||||||
ResumeTokenParsing,
|
|
||||||
ResumableRecvAndTokenHandling,
|
|
||||||
SendArgsValidationEncryptedSendOfUnencryptedDatasetForbidden,
|
|
||||||
SendArgsValidationResumeTokenEncryptionMismatchForbidden,
|
|
||||||
SendArgsValidationResumeTokenDifferentFilesystemForbidden,
|
|
||||||
ListFilesystemVersionsTypeFilteringAndPrefix,
|
|
||||||
ListFilesystemVersionsFilesystemNotExist,
|
|
||||||
ListFilesystemVersionsFilesystemNotExist,
|
|
||||||
ListFilesystemVersionsUserrefs,
|
|
||||||
ListFilesystemsNoFilter,
|
|
||||||
}
|
|
||||||
|
|||||||
+78
-201
@@ -46,7 +46,7 @@ func (x Tri) String() string {
|
|||||||
return proto.EnumName(Tri_name, int32(x))
|
return proto.EnumName(Tri_name, int32(x))
|
||||||
}
|
}
|
||||||
func (Tri) EnumDescriptor() ([]byte, []int) {
|
func (Tri) EnumDescriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{0}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilesystemVersion_VersionType int32
|
type FilesystemVersion_VersionType int32
|
||||||
@@ -69,7 +69,7 @@ func (x FilesystemVersion_VersionType) String() string {
|
|||||||
return proto.EnumName(FilesystemVersion_VersionType_name, int32(x))
|
return proto.EnumName(FilesystemVersion_VersionType_name, int32(x))
|
||||||
}
|
}
|
||||||
func (FilesystemVersion_VersionType) EnumDescriptor() ([]byte, []int) {
|
func (FilesystemVersion_VersionType) EnumDescriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{5, 0}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{5, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListFilesystemReq struct {
|
type ListFilesystemReq struct {
|
||||||
@@ -82,7 +82,7 @@ func (m *ListFilesystemReq) Reset() { *m = ListFilesystemReq{} }
|
|||||||
func (m *ListFilesystemReq) String() string { return proto.CompactTextString(m) }
|
func (m *ListFilesystemReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ListFilesystemReq) ProtoMessage() {}
|
func (*ListFilesystemReq) ProtoMessage() {}
|
||||||
func (*ListFilesystemReq) Descriptor() ([]byte, []int) {
|
func (*ListFilesystemReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{0}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{0}
|
||||||
}
|
}
|
||||||
func (m *ListFilesystemReq) XXX_Unmarshal(b []byte) error {
|
func (m *ListFilesystemReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ListFilesystemReq.Unmarshal(m, b)
|
return xxx_messageInfo_ListFilesystemReq.Unmarshal(m, b)
|
||||||
@@ -113,7 +113,7 @@ func (m *ListFilesystemRes) Reset() { *m = ListFilesystemRes{} }
|
|||||||
func (m *ListFilesystemRes) String() string { return proto.CompactTextString(m) }
|
func (m *ListFilesystemRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ListFilesystemRes) ProtoMessage() {}
|
func (*ListFilesystemRes) ProtoMessage() {}
|
||||||
func (*ListFilesystemRes) Descriptor() ([]byte, []int) {
|
func (*ListFilesystemRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{1}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{1}
|
||||||
}
|
}
|
||||||
func (m *ListFilesystemRes) XXX_Unmarshal(b []byte) error {
|
func (m *ListFilesystemRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ListFilesystemRes.Unmarshal(m, b)
|
return xxx_messageInfo_ListFilesystemRes.Unmarshal(m, b)
|
||||||
@@ -154,7 +154,7 @@ func (m *Filesystem) Reset() { *m = Filesystem{} }
|
|||||||
func (m *Filesystem) String() string { return proto.CompactTextString(m) }
|
func (m *Filesystem) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Filesystem) ProtoMessage() {}
|
func (*Filesystem) ProtoMessage() {}
|
||||||
func (*Filesystem) Descriptor() ([]byte, []int) {
|
func (*Filesystem) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{2}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{2}
|
||||||
}
|
}
|
||||||
func (m *Filesystem) XXX_Unmarshal(b []byte) error {
|
func (m *Filesystem) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_Filesystem.Unmarshal(m, b)
|
return xxx_messageInfo_Filesystem.Unmarshal(m, b)
|
||||||
@@ -213,7 +213,7 @@ func (m *ListFilesystemVersionsReq) Reset() { *m = ListFilesystemVersion
|
|||||||
func (m *ListFilesystemVersionsReq) String() string { return proto.CompactTextString(m) }
|
func (m *ListFilesystemVersionsReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ListFilesystemVersionsReq) ProtoMessage() {}
|
func (*ListFilesystemVersionsReq) ProtoMessage() {}
|
||||||
func (*ListFilesystemVersionsReq) Descriptor() ([]byte, []int) {
|
func (*ListFilesystemVersionsReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{3}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{3}
|
||||||
}
|
}
|
||||||
func (m *ListFilesystemVersionsReq) XXX_Unmarshal(b []byte) error {
|
func (m *ListFilesystemVersionsReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ListFilesystemVersionsReq.Unmarshal(m, b)
|
return xxx_messageInfo_ListFilesystemVersionsReq.Unmarshal(m, b)
|
||||||
@@ -251,7 +251,7 @@ func (m *ListFilesystemVersionsRes) Reset() { *m = ListFilesystemVersion
|
|||||||
func (m *ListFilesystemVersionsRes) String() string { return proto.CompactTextString(m) }
|
func (m *ListFilesystemVersionsRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ListFilesystemVersionsRes) ProtoMessage() {}
|
func (*ListFilesystemVersionsRes) ProtoMessage() {}
|
||||||
func (*ListFilesystemVersionsRes) Descriptor() ([]byte, []int) {
|
func (*ListFilesystemVersionsRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{4}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{4}
|
||||||
}
|
}
|
||||||
func (m *ListFilesystemVersionsRes) XXX_Unmarshal(b []byte) error {
|
func (m *ListFilesystemVersionsRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ListFilesystemVersionsRes.Unmarshal(m, b)
|
return xxx_messageInfo_ListFilesystemVersionsRes.Unmarshal(m, b)
|
||||||
@@ -293,7 +293,7 @@ func (m *FilesystemVersion) Reset() { *m = FilesystemVersion{} }
|
|||||||
func (m *FilesystemVersion) String() string { return proto.CompactTextString(m) }
|
func (m *FilesystemVersion) String() string { return proto.CompactTextString(m) }
|
||||||
func (*FilesystemVersion) ProtoMessage() {}
|
func (*FilesystemVersion) ProtoMessage() {}
|
||||||
func (*FilesystemVersion) Descriptor() ([]byte, []int) {
|
func (*FilesystemVersion) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{5}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{5}
|
||||||
}
|
}
|
||||||
func (m *FilesystemVersion) XXX_Unmarshal(b []byte) error {
|
func (m *FilesystemVersion) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_FilesystemVersion.Unmarshal(m, b)
|
return xxx_messageInfo_FilesystemVersion.Unmarshal(m, b)
|
||||||
@@ -371,7 +371,7 @@ func (m *SendReq) Reset() { *m = SendReq{} }
|
|||||||
func (m *SendReq) String() string { return proto.CompactTextString(m) }
|
func (m *SendReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SendReq) ProtoMessage() {}
|
func (*SendReq) ProtoMessage() {}
|
||||||
func (*SendReq) Descriptor() ([]byte, []int) {
|
func (*SendReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{6}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{6}
|
||||||
}
|
}
|
||||||
func (m *SendReq) XXX_Unmarshal(b []byte) error {
|
func (m *SendReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_SendReq.Unmarshal(m, b)
|
return xxx_messageInfo_SendReq.Unmarshal(m, b)
|
||||||
@@ -445,7 +445,7 @@ func (m *Property) Reset() { *m = Property{} }
|
|||||||
func (m *Property) String() string { return proto.CompactTextString(m) }
|
func (m *Property) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Property) ProtoMessage() {}
|
func (*Property) ProtoMessage() {}
|
||||||
func (*Property) Descriptor() ([]byte, []int) {
|
func (*Property) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{7}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{7}
|
||||||
}
|
}
|
||||||
func (m *Property) XXX_Unmarshal(b []byte) error {
|
func (m *Property) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_Property.Unmarshal(m, b)
|
return xxx_messageInfo_Property.Unmarshal(m, b)
|
||||||
@@ -496,7 +496,7 @@ func (m *SendRes) Reset() { *m = SendRes{} }
|
|||||||
func (m *SendRes) String() string { return proto.CompactTextString(m) }
|
func (m *SendRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SendRes) ProtoMessage() {}
|
func (*SendRes) ProtoMessage() {}
|
||||||
func (*SendRes) Descriptor() ([]byte, []int) {
|
func (*SendRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{8}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{8}
|
||||||
}
|
}
|
||||||
func (m *SendRes) XXX_Unmarshal(b []byte) error {
|
func (m *SendRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_SendRes.Unmarshal(m, b)
|
return xxx_messageInfo_SendRes.Unmarshal(m, b)
|
||||||
@@ -548,7 +548,7 @@ func (m *SendCompletedReq) Reset() { *m = SendCompletedReq{} }
|
|||||||
func (m *SendCompletedReq) String() string { return proto.CompactTextString(m) }
|
func (m *SendCompletedReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SendCompletedReq) ProtoMessage() {}
|
func (*SendCompletedReq) ProtoMessage() {}
|
||||||
func (*SendCompletedReq) Descriptor() ([]byte, []int) {
|
func (*SendCompletedReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{9}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{9}
|
||||||
}
|
}
|
||||||
func (m *SendCompletedReq) XXX_Unmarshal(b []byte) error {
|
func (m *SendCompletedReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_SendCompletedReq.Unmarshal(m, b)
|
return xxx_messageInfo_SendCompletedReq.Unmarshal(m, b)
|
||||||
@@ -585,7 +585,7 @@ func (m *SendCompletedRes) Reset() { *m = SendCompletedRes{} }
|
|||||||
func (m *SendCompletedRes) String() string { return proto.CompactTextString(m) }
|
func (m *SendCompletedRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SendCompletedRes) ProtoMessage() {}
|
func (*SendCompletedRes) ProtoMessage() {}
|
||||||
func (*SendCompletedRes) Descriptor() ([]byte, []int) {
|
func (*SendCompletedRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{10}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{10}
|
||||||
}
|
}
|
||||||
func (m *SendCompletedRes) XXX_Unmarshal(b []byte) error {
|
func (m *SendCompletedRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_SendCompletedRes.Unmarshal(m, b)
|
return xxx_messageInfo_SendCompletedRes.Unmarshal(m, b)
|
||||||
@@ -620,7 +620,7 @@ func (m *ReceiveReq) Reset() { *m = ReceiveReq{} }
|
|||||||
func (m *ReceiveReq) String() string { return proto.CompactTextString(m) }
|
func (m *ReceiveReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ReceiveReq) ProtoMessage() {}
|
func (*ReceiveReq) ProtoMessage() {}
|
||||||
func (*ReceiveReq) Descriptor() ([]byte, []int) {
|
func (*ReceiveReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{11}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{11}
|
||||||
}
|
}
|
||||||
func (m *ReceiveReq) XXX_Unmarshal(b []byte) error {
|
func (m *ReceiveReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ReceiveReq.Unmarshal(m, b)
|
return xxx_messageInfo_ReceiveReq.Unmarshal(m, b)
|
||||||
@@ -671,7 +671,7 @@ func (m *ReceiveRes) Reset() { *m = ReceiveRes{} }
|
|||||||
func (m *ReceiveRes) String() string { return proto.CompactTextString(m) }
|
func (m *ReceiveRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ReceiveRes) ProtoMessage() {}
|
func (*ReceiveRes) ProtoMessage() {}
|
||||||
func (*ReceiveRes) Descriptor() ([]byte, []int) {
|
func (*ReceiveRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{12}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{12}
|
||||||
}
|
}
|
||||||
func (m *ReceiveRes) XXX_Unmarshal(b []byte) error {
|
func (m *ReceiveRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ReceiveRes.Unmarshal(m, b)
|
return xxx_messageInfo_ReceiveRes.Unmarshal(m, b)
|
||||||
@@ -704,7 +704,7 @@ func (m *DestroySnapshotsReq) Reset() { *m = DestroySnapshotsReq{} }
|
|||||||
func (m *DestroySnapshotsReq) String() string { return proto.CompactTextString(m) }
|
func (m *DestroySnapshotsReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*DestroySnapshotsReq) ProtoMessage() {}
|
func (*DestroySnapshotsReq) ProtoMessage() {}
|
||||||
func (*DestroySnapshotsReq) Descriptor() ([]byte, []int) {
|
func (*DestroySnapshotsReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{13}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{13}
|
||||||
}
|
}
|
||||||
func (m *DestroySnapshotsReq) XXX_Unmarshal(b []byte) error {
|
func (m *DestroySnapshotsReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_DestroySnapshotsReq.Unmarshal(m, b)
|
return xxx_messageInfo_DestroySnapshotsReq.Unmarshal(m, b)
|
||||||
@@ -750,7 +750,7 @@ func (m *DestroySnapshotRes) Reset() { *m = DestroySnapshotRes{} }
|
|||||||
func (m *DestroySnapshotRes) String() string { return proto.CompactTextString(m) }
|
func (m *DestroySnapshotRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*DestroySnapshotRes) ProtoMessage() {}
|
func (*DestroySnapshotRes) ProtoMessage() {}
|
||||||
func (*DestroySnapshotRes) Descriptor() ([]byte, []int) {
|
func (*DestroySnapshotRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{14}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{14}
|
||||||
}
|
}
|
||||||
func (m *DestroySnapshotRes) XXX_Unmarshal(b []byte) error {
|
func (m *DestroySnapshotRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_DestroySnapshotRes.Unmarshal(m, b)
|
return xxx_messageInfo_DestroySnapshotRes.Unmarshal(m, b)
|
||||||
@@ -795,7 +795,7 @@ func (m *DestroySnapshotsRes) Reset() { *m = DestroySnapshotsRes{} }
|
|||||||
func (m *DestroySnapshotsRes) String() string { return proto.CompactTextString(m) }
|
func (m *DestroySnapshotsRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*DestroySnapshotsRes) ProtoMessage() {}
|
func (*DestroySnapshotsRes) ProtoMessage() {}
|
||||||
func (*DestroySnapshotsRes) Descriptor() ([]byte, []int) {
|
func (*DestroySnapshotsRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{15}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{15}
|
||||||
}
|
}
|
||||||
func (m *DestroySnapshotsRes) XXX_Unmarshal(b []byte) error {
|
func (m *DestroySnapshotsRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_DestroySnapshotsRes.Unmarshal(m, b)
|
return xxx_messageInfo_DestroySnapshotsRes.Unmarshal(m, b)
|
||||||
@@ -833,7 +833,7 @@ func (m *ReplicationCursorReq) Reset() { *m = ReplicationCursorReq{} }
|
|||||||
func (m *ReplicationCursorReq) String() string { return proto.CompactTextString(m) }
|
func (m *ReplicationCursorReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ReplicationCursorReq) ProtoMessage() {}
|
func (*ReplicationCursorReq) ProtoMessage() {}
|
||||||
func (*ReplicationCursorReq) Descriptor() ([]byte, []int) {
|
func (*ReplicationCursorReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{16}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{16}
|
||||||
}
|
}
|
||||||
func (m *ReplicationCursorReq) XXX_Unmarshal(b []byte) error {
|
func (m *ReplicationCursorReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ReplicationCursorReq.Unmarshal(m, b)
|
return xxx_messageInfo_ReplicationCursorReq.Unmarshal(m, b)
|
||||||
@@ -874,7 +874,7 @@ func (m *ReplicationCursorRes) Reset() { *m = ReplicationCursorRes{} }
|
|||||||
func (m *ReplicationCursorRes) String() string { return proto.CompactTextString(m) }
|
func (m *ReplicationCursorRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*ReplicationCursorRes) ProtoMessage() {}
|
func (*ReplicationCursorRes) ProtoMessage() {}
|
||||||
func (*ReplicationCursorRes) Descriptor() ([]byte, []int) {
|
func (*ReplicationCursorRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{17}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{17}
|
||||||
}
|
}
|
||||||
func (m *ReplicationCursorRes) XXX_Unmarshal(b []byte) error {
|
func (m *ReplicationCursorRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_ReplicationCursorRes.Unmarshal(m, b)
|
return xxx_messageInfo_ReplicationCursorRes.Unmarshal(m, b)
|
||||||
@@ -1010,7 +1010,7 @@ func (m *PingReq) Reset() { *m = PingReq{} }
|
|||||||
func (m *PingReq) String() string { return proto.CompactTextString(m) }
|
func (m *PingReq) String() string { return proto.CompactTextString(m) }
|
||||||
func (*PingReq) ProtoMessage() {}
|
func (*PingReq) ProtoMessage() {}
|
||||||
func (*PingReq) Descriptor() ([]byte, []int) {
|
func (*PingReq) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{18}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{18}
|
||||||
}
|
}
|
||||||
func (m *PingReq) XXX_Unmarshal(b []byte) error {
|
func (m *PingReq) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_PingReq.Unmarshal(m, b)
|
return xxx_messageInfo_PingReq.Unmarshal(m, b)
|
||||||
@@ -1049,7 +1049,7 @@ func (m *PingRes) Reset() { *m = PingRes{} }
|
|||||||
func (m *PingRes) String() string { return proto.CompactTextString(m) }
|
func (m *PingRes) String() string { return proto.CompactTextString(m) }
|
||||||
func (*PingRes) ProtoMessage() {}
|
func (*PingRes) ProtoMessage() {}
|
||||||
func (*PingRes) Descriptor() ([]byte, []int) {
|
func (*PingRes) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{19}
|
return fileDescriptor_pdu_483c6918b7b3d747, []int{19}
|
||||||
}
|
}
|
||||||
func (m *PingRes) XXX_Unmarshal(b []byte) error {
|
func (m *PingRes) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_PingRes.Unmarshal(m, b)
|
return xxx_messageInfo_PingRes.Unmarshal(m, b)
|
||||||
@@ -1076,91 +1076,6 @@ func (m *PingRes) GetEcho() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
type HintMostRecentCommonAncestorReq struct {
|
|
||||||
Filesystem string `protobuf:"bytes,1,opt,name=Filesystem,proto3" json:"Filesystem,omitempty"`
|
|
||||||
// A copy of the FilesystemVersion on the sending side that the replication
|
|
||||||
// algorithm identified as a shared most recent common version between sending
|
|
||||||
// and receiving side.
|
|
||||||
//
|
|
||||||
// If nil, this is an indication that the replication algorithm could not
|
|
||||||
// find a common ancestor between the two sides.
|
|
||||||
// NOTE: nilness does not mean that replication never happened - there could
|
|
||||||
// as well be a replication conflict. thus, dont' jump to conclusions too
|
|
||||||
// rapidly here.
|
|
||||||
SenderVersion *FilesystemVersion `protobuf:"bytes,2,opt,name=SenderVersion,proto3" json:"SenderVersion,omitempty"`
|
|
||||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
XXX_sizecache int32 `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) Reset() { *m = HintMostRecentCommonAncestorReq{} }
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*HintMostRecentCommonAncestorReq) ProtoMessage() {}
|
|
||||||
func (*HintMostRecentCommonAncestorReq) Descriptor() ([]byte, []int) {
|
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{20}
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) XXX_Unmarshal(b []byte) error {
|
|
||||||
return xxx_messageInfo_HintMostRecentCommonAncestorReq.Unmarshal(m, b)
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
|
||||||
return xxx_messageInfo_HintMostRecentCommonAncestorReq.Marshal(b, m, deterministic)
|
|
||||||
}
|
|
||||||
func (dst *HintMostRecentCommonAncestorReq) XXX_Merge(src proto.Message) {
|
|
||||||
xxx_messageInfo_HintMostRecentCommonAncestorReq.Merge(dst, src)
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) XXX_Size() int {
|
|
||||||
return xxx_messageInfo_HintMostRecentCommonAncestorReq.Size(m)
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) XXX_DiscardUnknown() {
|
|
||||||
xxx_messageInfo_HintMostRecentCommonAncestorReq.DiscardUnknown(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
var xxx_messageInfo_HintMostRecentCommonAncestorReq proto.InternalMessageInfo
|
|
||||||
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) GetFilesystem() string {
|
|
||||||
if m != nil {
|
|
||||||
return m.Filesystem
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *HintMostRecentCommonAncestorReq) GetSenderVersion() *FilesystemVersion {
|
|
||||||
if m != nil {
|
|
||||||
return m.SenderVersion
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HintMostRecentCommonAncestorRes struct {
|
|
||||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
XXX_sizecache int32 `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *HintMostRecentCommonAncestorRes) Reset() { *m = HintMostRecentCommonAncestorRes{} }
|
|
||||||
func (m *HintMostRecentCommonAncestorRes) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*HintMostRecentCommonAncestorRes) ProtoMessage() {}
|
|
||||||
func (*HintMostRecentCommonAncestorRes) Descriptor() ([]byte, []int) {
|
|
||||||
return fileDescriptor_pdu_e59763dc61674a79, []int{21}
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorRes) XXX_Unmarshal(b []byte) error {
|
|
||||||
return xxx_messageInfo_HintMostRecentCommonAncestorRes.Unmarshal(m, b)
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
|
||||||
return xxx_messageInfo_HintMostRecentCommonAncestorRes.Marshal(b, m, deterministic)
|
|
||||||
}
|
|
||||||
func (dst *HintMostRecentCommonAncestorRes) XXX_Merge(src proto.Message) {
|
|
||||||
xxx_messageInfo_HintMostRecentCommonAncestorRes.Merge(dst, src)
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorRes) XXX_Size() int {
|
|
||||||
return xxx_messageInfo_HintMostRecentCommonAncestorRes.Size(m)
|
|
||||||
}
|
|
||||||
func (m *HintMostRecentCommonAncestorRes) XXX_DiscardUnknown() {
|
|
||||||
xxx_messageInfo_HintMostRecentCommonAncestorRes.DiscardUnknown(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
var xxx_messageInfo_HintMostRecentCommonAncestorRes proto.InternalMessageInfo
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proto.RegisterType((*ListFilesystemReq)(nil), "ListFilesystemReq")
|
proto.RegisterType((*ListFilesystemReq)(nil), "ListFilesystemReq")
|
||||||
proto.RegisterType((*ListFilesystemRes)(nil), "ListFilesystemRes")
|
proto.RegisterType((*ListFilesystemRes)(nil), "ListFilesystemRes")
|
||||||
@@ -1182,8 +1097,6 @@ func init() {
|
|||||||
proto.RegisterType((*ReplicationCursorRes)(nil), "ReplicationCursorRes")
|
proto.RegisterType((*ReplicationCursorRes)(nil), "ReplicationCursorRes")
|
||||||
proto.RegisterType((*PingReq)(nil), "PingReq")
|
proto.RegisterType((*PingReq)(nil), "PingReq")
|
||||||
proto.RegisterType((*PingRes)(nil), "PingRes")
|
proto.RegisterType((*PingRes)(nil), "PingRes")
|
||||||
proto.RegisterType((*HintMostRecentCommonAncestorReq)(nil), "HintMostRecentCommonAncestorReq")
|
|
||||||
proto.RegisterType((*HintMostRecentCommonAncestorRes)(nil), "HintMostRecentCommonAncestorRes")
|
|
||||||
proto.RegisterEnum("Tri", Tri_name, Tri_value)
|
proto.RegisterEnum("Tri", Tri_name, Tri_value)
|
||||||
proto.RegisterEnum("FilesystemVersion_VersionType", FilesystemVersion_VersionType_name, FilesystemVersion_VersionType_value)
|
proto.RegisterEnum("FilesystemVersion_VersionType", FilesystemVersion_VersionType_name, FilesystemVersion_VersionType_value)
|
||||||
}
|
}
|
||||||
@@ -1206,7 +1119,6 @@ type ReplicationClient interface {
|
|||||||
DestroySnapshots(ctx context.Context, in *DestroySnapshotsReq, opts ...grpc.CallOption) (*DestroySnapshotsRes, error)
|
DestroySnapshots(ctx context.Context, in *DestroySnapshotsReq, opts ...grpc.CallOption) (*DestroySnapshotsRes, error)
|
||||||
ReplicationCursor(ctx context.Context, in *ReplicationCursorReq, opts ...grpc.CallOption) (*ReplicationCursorRes, error)
|
ReplicationCursor(ctx context.Context, in *ReplicationCursorReq, opts ...grpc.CallOption) (*ReplicationCursorRes, error)
|
||||||
SendCompleted(ctx context.Context, in *SendCompletedReq, opts ...grpc.CallOption) (*SendCompletedRes, error)
|
SendCompleted(ctx context.Context, in *SendCompletedReq, opts ...grpc.CallOption) (*SendCompletedRes, error)
|
||||||
HintMostRecentCommonAncestor(ctx context.Context, in *HintMostRecentCommonAncestorReq, opts ...grpc.CallOption) (*HintMostRecentCommonAncestorRes, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type replicationClient struct {
|
type replicationClient struct {
|
||||||
@@ -1271,15 +1183,6 @@ func (c *replicationClient) SendCompleted(ctx context.Context, in *SendCompleted
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *replicationClient) HintMostRecentCommonAncestor(ctx context.Context, in *HintMostRecentCommonAncestorReq, opts ...grpc.CallOption) (*HintMostRecentCommonAncestorRes, error) {
|
|
||||||
out := new(HintMostRecentCommonAncestorRes)
|
|
||||||
err := c.cc.Invoke(ctx, "/Replication/HintMostRecentCommonAncestor", in, out, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplicationServer is the server API for Replication service.
|
// ReplicationServer is the server API for Replication service.
|
||||||
type ReplicationServer interface {
|
type ReplicationServer interface {
|
||||||
Ping(context.Context, *PingReq) (*PingRes, error)
|
Ping(context.Context, *PingReq) (*PingRes, error)
|
||||||
@@ -1288,7 +1191,6 @@ type ReplicationServer interface {
|
|||||||
DestroySnapshots(context.Context, *DestroySnapshotsReq) (*DestroySnapshotsRes, error)
|
DestroySnapshots(context.Context, *DestroySnapshotsReq) (*DestroySnapshotsRes, error)
|
||||||
ReplicationCursor(context.Context, *ReplicationCursorReq) (*ReplicationCursorRes, error)
|
ReplicationCursor(context.Context, *ReplicationCursorReq) (*ReplicationCursorRes, error)
|
||||||
SendCompleted(context.Context, *SendCompletedReq) (*SendCompletedRes, error)
|
SendCompleted(context.Context, *SendCompletedReq) (*SendCompletedRes, error)
|
||||||
HintMostRecentCommonAncestor(context.Context, *HintMostRecentCommonAncestorReq) (*HintMostRecentCommonAncestorRes, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterReplicationServer(s *grpc.Server, srv ReplicationServer) {
|
func RegisterReplicationServer(s *grpc.Server, srv ReplicationServer) {
|
||||||
@@ -1403,24 +1305,6 @@ func _Replication_SendCompleted_Handler(srv interface{}, ctx context.Context, de
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _Replication_HintMostRecentCommonAncestor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(HintMostRecentCommonAncestorReq)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ReplicationServer).HintMostRecentCommonAncestor(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/Replication/HintMostRecentCommonAncestor",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ReplicationServer).HintMostRecentCommonAncestor(ctx, req.(*HintMostRecentCommonAncestorReq))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _Replication_serviceDesc = grpc.ServiceDesc{
|
var _Replication_serviceDesc = grpc.ServiceDesc{
|
||||||
ServiceName: "Replication",
|
ServiceName: "Replication",
|
||||||
HandlerType: (*ReplicationServer)(nil),
|
HandlerType: (*ReplicationServer)(nil),
|
||||||
@@ -1449,73 +1333,66 @@ var _Replication_serviceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "SendCompleted",
|
MethodName: "SendCompleted",
|
||||||
Handler: _Replication_SendCompleted_Handler,
|
Handler: _Replication_SendCompleted_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "HintMostRecentCommonAncestor",
|
|
||||||
Handler: _Replication_HintMostRecentCommonAncestor_Handler,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "pdu.proto",
|
Metadata: "pdu.proto",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { proto.RegisterFile("pdu.proto", fileDescriptor_pdu_e59763dc61674a79) }
|
func init() { proto.RegisterFile("pdu.proto", fileDescriptor_pdu_483c6918b7b3d747) }
|
||||||
|
|
||||||
var fileDescriptor_pdu_e59763dc61674a79 = []byte{
|
var fileDescriptor_pdu_483c6918b7b3d747 = []byte{
|
||||||
// 892 bytes of a gzipped FileDescriptorProto
|
// 833 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xdf, 0x6f, 0xdb, 0x36,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0x5f, 0x6f, 0xe3, 0x44,
|
||||||
0x10, 0x8e, 0x6c, 0x39, 0x91, 0xcf, 0xe9, 0xea, 0x5c, 0xb2, 0x42, 0x13, 0xba, 0xce, 0xe3, 0x86,
|
0x10, 0xaf, 0x13, 0xa7, 0x75, 0x26, 0x3d, 0x2e, 0x9d, 0x96, 0x93, 0xb1, 0xe0, 0x54, 0x2d, 0x08,
|
||||||
0xc1, 0x0d, 0x30, 0x61, 0xc8, 0x7e, 0x60, 0xc3, 0x80, 0x02, 0x8d, 0x93, 0x34, 0xc5, 0xd6, 0xce,
|
0xe5, 0x2a, 0x61, 0xa1, 0xf2, 0x47, 0x42, 0x48, 0x27, 0xd1, 0xb4, 0xbd, 0x3b, 0x01, 0x47, 0xb4,
|
||||||
0x60, 0xbc, 0x62, 0xe8, 0x9b, 0x6a, 0x1f, 0x12, 0x21, 0xb2, 0xa8, 0x90, 0xf4, 0x50, 0x6f, 0x7b,
|
0x35, 0x27, 0x74, 0x6f, 0x26, 0x19, 0xb5, 0x56, 0x1d, 0xaf, 0xbb, 0xe3, 0xa0, 0x0b, 0xe2, 0x89,
|
||||||
0xda, 0xe3, 0xfe, 0xbd, 0xe5, 0x0f, 0x2a, 0x44, 0x4b, 0xb6, 0x6c, 0xc9, 0x89, 0x9f, 0xcc, 0xfb,
|
0x47, 0xbe, 0x1e, 0x7c, 0x10, 0x3e, 0x02, 0xf2, 0xc6, 0x4e, 0x9c, 0xd8, 0x41, 0x79, 0xca, 0xce,
|
||||||
0x78, 0x14, 0xef, 0xbe, 0xfb, 0xee, 0x68, 0x68, 0x26, 0xa3, 0x89, 0x9f, 0x48, 0xa1, 0x05, 0xdb,
|
0x6f, 0x66, 0x77, 0x67, 0x7f, 0xf3, 0x9b, 0x71, 0xa0, 0x9b, 0x4e, 0x66, 0x7e, 0xaa, 0x55, 0xa6,
|
||||||
0x87, 0xbd, 0x5f, 0x43, 0xa5, 0xcf, 0xc2, 0x88, 0xd4, 0x54, 0x69, 0x1a, 0x73, 0xba, 0x61, 0xc7,
|
0xc4, 0x31, 0x1c, 0xfd, 0x10, 0x71, 0x76, 0x1d, 0xc5, 0xc4, 0x73, 0xce, 0x68, 0x2a, 0xe9, 0x41,
|
||||||
0x65, 0x50, 0xe1, 0xd7, 0xd0, 0x5a, 0x00, 0xca, 0xb5, 0x3a, 0xf5, 0x6e, 0xeb, 0xa8, 0xe5, 0x17,
|
0x5c, 0xd4, 0x41, 0xc6, 0xcf, 0xa0, 0xb7, 0x02, 0xd8, 0xb5, 0x4e, 0xdb, 0x83, 0xde, 0x79, 0xcf,
|
||||||
0x9c, 0x8a, 0xfb, 0xec, 0x3f, 0x0b, 0x60, 0x61, 0x23, 0x82, 0xdd, 0x0f, 0xf4, 0x95, 0x6b, 0x75,
|
0xaf, 0x04, 0x55, 0xfd, 0xe2, 0x2f, 0x0b, 0x60, 0x65, 0x23, 0x82, 0x3d, 0x0a, 0xb3, 0x3b, 0xd7,
|
||||||
0xac, 0x6e, 0x93, 0x9b, 0x35, 0x76, 0xa0, 0xc5, 0x49, 0x4d, 0xc6, 0x34, 0x10, 0xd7, 0x14, 0xbb,
|
0x3a, 0xb5, 0x06, 0x5d, 0x69, 0xd6, 0x78, 0x0a, 0x3d, 0x49, 0x3c, 0x9b, 0x52, 0xa0, 0xee, 0x29,
|
||||||
0x35, 0xb3, 0x55, 0x84, 0xf0, 0x4b, 0x78, 0xf0, 0x52, 0xf5, 0xa3, 0x60, 0x48, 0x57, 0x22, 0x1a,
|
0x71, 0x5b, 0xc6, 0x55, 0x85, 0xf0, 0x13, 0x78, 0xf4, 0x8a, 0x47, 0x71, 0x38, 0xa6, 0x3b, 0x15,
|
||||||
0x91, 0x74, 0xeb, 0x1d, 0xab, 0xeb, 0xf0, 0x65, 0x30, 0xfd, 0xce, 0x4b, 0x75, 0x1a, 0x0f, 0xe5,
|
0x4f, 0x48, 0xbb, 0xed, 0x53, 0x6b, 0xe0, 0xc8, 0x75, 0x30, 0x3f, 0xe7, 0x15, 0x5f, 0x25, 0x63,
|
||||||
0x34, 0xd1, 0x34, 0x72, 0x6d, 0xe3, 0x53, 0x84, 0xd8, 0xcf, 0xf0, 0xc9, 0x72, 0x42, 0x6f, 0x48,
|
0x3d, 0x4f, 0x33, 0x9a, 0xb8, 0xb6, 0x89, 0xa9, 0x42, 0xe2, 0x5b, 0xf8, 0x60, 0xfd, 0x41, 0x6f,
|
||||||
0xaa, 0x50, 0xc4, 0x8a, 0xd3, 0x0d, 0x3e, 0x29, 0x06, 0x9a, 0x05, 0x58, 0x40, 0xd8, 0x2f, 0xeb,
|
0x48, 0x73, 0xa4, 0x12, 0x96, 0xf4, 0x80, 0x4f, 0xab, 0x89, 0x16, 0x09, 0x56, 0x10, 0xf1, 0xfd,
|
||||||
0x0f, 0x2b, 0xf4, 0xc1, 0xc9, 0xcd, 0x8c, 0x12, 0xf4, 0x4b, 0x9e, 0x7c, 0xee, 0xc3, 0x6e, 0x2d,
|
0xf6, 0xcd, 0x8c, 0x3e, 0x38, 0xa5, 0x59, 0x50, 0x82, 0x7e, 0x2d, 0x52, 0x2e, 0x63, 0xc4, 0x3f,
|
||||||
0xd8, 0x2b, 0xed, 0xe3, 0x11, 0xd8, 0x83, 0x69, 0x42, 0xe6, 0xf2, 0x8f, 0x8e, 0x9e, 0x94, 0xbf,
|
0x16, 0x1c, 0xd5, 0xfc, 0x78, 0x0e, 0x76, 0x30, 0x4f, 0xc9, 0x5c, 0xfe, 0xde, 0xf9, 0xd3, 0xfa,
|
||||||
0xe0, 0x67, 0xbf, 0xa9, 0x17, 0x37, 0xbe, 0x29, 0xa3, 0xaf, 0x83, 0x31, 0x65, 0xb4, 0x99, 0x75,
|
0x09, 0x7e, 0xf1, 0x9b, 0x47, 0x49, 0x13, 0x9b, 0x33, 0xfa, 0x3a, 0x9c, 0x52, 0x41, 0x9b, 0x59,
|
||||||
0x8a, 0xbd, 0x98, 0x84, 0x23, 0x43, 0x93, 0xcd, 0xcd, 0x1a, 0x1f, 0x43, 0xb3, 0x27, 0x29, 0xd0,
|
0xe7, 0xd8, 0x8b, 0x59, 0x34, 0x31, 0x34, 0xd9, 0xd2, 0xac, 0xf1, 0x43, 0xe8, 0x0e, 0x35, 0x85,
|
||||||
0x34, 0xf8, 0xe3, 0x85, 0xe1, 0xc6, 0xe6, 0x0b, 0x00, 0x3d, 0x70, 0x8c, 0x11, 0x8a, 0xd8, 0x6d,
|
0x19, 0x05, 0xbf, 0xbc, 0x30, 0xdc, 0xd8, 0x72, 0x05, 0xa0, 0x07, 0x8e, 0x31, 0x22, 0x95, 0xb8,
|
||||||
0x98, 0x2f, 0xcd, 0x6d, 0xf6, 0x14, 0x5a, 0x85, 0x6b, 0x71, 0x17, 0x9c, 0x8b, 0x38, 0x48, 0xd4,
|
0x1d, 0x73, 0xd2, 0xd2, 0x16, 0xcf, 0xa0, 0x57, 0xb9, 0x16, 0x0f, 0xc1, 0xb9, 0x49, 0xc2, 0x94,
|
||||||
0x95, 0xd0, 0xed, 0xad, 0xd4, 0x3a, 0x16, 0xe2, 0x7a, 0x1c, 0xc8, 0xeb, 0xb6, 0xc5, 0xfe, 0xb7,
|
0xef, 0x54, 0xd6, 0xdf, 0xcb, 0xad, 0x0b, 0xa5, 0xee, 0xa7, 0xa1, 0xbe, 0xef, 0x5b, 0xe2, 0x6f,
|
||||||
0x60, 0xe7, 0x82, 0xe2, 0xd1, 0x06, 0x7c, 0xe2, 0x57, 0x60, 0x9f, 0x49, 0x31, 0x36, 0x81, 0x57,
|
0x0b, 0x0e, 0x6e, 0x28, 0x99, 0xec, 0xc0, 0x27, 0x7e, 0x0a, 0xf6, 0xb5, 0x56, 0x53, 0x93, 0x78,
|
||||||
0xd3, 0x65, 0xf6, 0x91, 0x41, 0x6d, 0x20, 0x4c, 0x2a, 0xd5, 0x5e, 0xb5, 0x81, 0x58, 0x95, 0x90,
|
0x33, 0x5d, 0xc6, 0x8f, 0x02, 0x5a, 0x81, 0x32, 0x4f, 0x69, 0x8e, 0x6a, 0x05, 0x6a, 0x53, 0x42,
|
||||||
0x5d, 0x96, 0x10, 0x83, 0xe6, 0x42, 0x1a, 0x0d, 0xc3, 0xaf, 0xed, 0x0f, 0x64, 0xc8, 0x17, 0x30,
|
0x76, 0x5d, 0x42, 0x02, 0xba, 0x2b, 0x69, 0x74, 0x0c, 0xbf, 0xb6, 0x1f, 0xe8, 0x48, 0xae, 0x60,
|
||||||
0x3e, 0x82, 0xed, 0x13, 0x39, 0xe5, 0x93, 0xd8, 0xdd, 0x36, 0xda, 0xc9, 0x2c, 0xf6, 0x1d, 0x38,
|
0x7c, 0x02, 0xfb, 0x97, 0x7a, 0x2e, 0x67, 0x89, 0xbb, 0x6f, 0xb4, 0x53, 0x58, 0xe2, 0x4b, 0x70,
|
||||||
0x7d, 0x29, 0x12, 0x92, 0x7a, 0x3a, 0xa7, 0xdb, 0x2a, 0xd0, 0x7d, 0x00, 0x8d, 0x37, 0x41, 0x34,
|
0x46, 0x5a, 0xa5, 0xa4, 0xb3, 0xf9, 0x92, 0x6e, 0xab, 0x42, 0xf7, 0x09, 0x74, 0xde, 0x84, 0xf1,
|
||||||
0xc9, 0x6b, 0x30, 0x33, 0xd8, 0xbf, 0x73, 0x2e, 0x14, 0x76, 0xe1, 0xe1, 0xef, 0x8a, 0x46, 0xab,
|
0xac, 0xac, 0xc1, 0xc2, 0x10, 0x7f, 0x2e, 0xb9, 0x60, 0x1c, 0xc0, 0xe3, 0x9f, 0x99, 0x26, 0x9b,
|
||||||
0x32, 0x77, 0xf8, 0x2a, 0x8c, 0x0c, 0x76, 0x4f, 0xdf, 0x27, 0x34, 0xd4, 0x34, 0xba, 0x08, 0xff,
|
0x32, 0x77, 0xe4, 0x26, 0x8c, 0x02, 0x0e, 0xaf, 0xde, 0xa5, 0x34, 0xce, 0x68, 0x72, 0x13, 0xfd,
|
||||||
0x22, 0x93, 0x77, 0x9d, 0x2f, 0x61, 0xf8, 0x14, 0x20, 0x8b, 0x27, 0x24, 0xe5, 0xda, 0x46, 0x6e,
|
0x4e, 0xe6, 0xdd, 0x6d, 0xb9, 0x86, 0xe1, 0x33, 0x80, 0x22, 0x9f, 0x88, 0xd8, 0xb5, 0x8d, 0xdc,
|
||||||
0x4d, 0x3f, 0x0f, 0x91, 0x17, 0x36, 0xd9, 0x33, 0x68, 0xa7, 0x31, 0xf4, 0xc4, 0x38, 0x89, 0x48,
|
0xba, 0x7e, 0x99, 0xa2, 0xac, 0x38, 0xc5, 0x73, 0xe8, 0xe7, 0x39, 0x0c, 0xd5, 0x34, 0x8d, 0x29,
|
||||||
0x93, 0x29, 0xcc, 0x21, 0xb4, 0x7e, 0x93, 0xe1, 0x65, 0x18, 0x07, 0x11, 0xa7, 0x9b, 0x8c, 0x7f,
|
0x23, 0x53, 0x98, 0x33, 0xe8, 0xfd, 0xa4, 0xa3, 0xdb, 0x28, 0x09, 0x63, 0x49, 0x0f, 0x05, 0xff,
|
||||||
0xc7, 0xcf, 0xea, 0xc6, 0x8b, 0x9b, 0x0c, 0x4b, 0xe7, 0x15, 0xfb, 0x07, 0x80, 0xd3, 0x90, 0xc2,
|
0x8e, 0x5f, 0xd4, 0x4d, 0x56, 0x9d, 0x02, 0x6b, 0xfb, 0x59, 0xfc, 0x01, 0x20, 0x69, 0x4c, 0xd1,
|
||||||
0x3f, 0x69, 0x93, 0x32, 0xcf, 0xca, 0x57, 0xbb, 0xb3, 0x7c, 0x87, 0xd0, 0xee, 0x45, 0x14, 0xc8,
|
0x6f, 0xb4, 0x4b, 0x99, 0x17, 0xe5, 0x6b, 0xfd, 0x6f, 0xf9, 0xce, 0xa0, 0x3f, 0x8c, 0x29, 0xd4,
|
||||||
0x22, 0x3f, 0xb3, 0x16, 0x2f, 0xe1, 0x6c, 0xb7, 0x70, 0xbb, 0x62, 0x97, 0xb0, 0x7f, 0x42, 0x4a,
|
0x55, 0x7e, 0x16, 0x2d, 0x5e, 0xc3, 0xc5, 0x61, 0xe5, 0x76, 0x16, 0xb7, 0x70, 0x7c, 0x49, 0x9c,
|
||||||
0x4b, 0x31, 0xcd, 0x35, 0xb9, 0x49, 0x2f, 0xe3, 0x37, 0xd0, 0x9c, 0xfb, 0xbb, 0xb5, 0xb5, 0xfd,
|
0x69, 0x35, 0x2f, 0x35, 0xb9, 0x4b, 0x2f, 0xe3, 0xe7, 0xd0, 0x5d, 0xc6, 0xbb, 0xad, 0xad, 0xfd,
|
||||||
0xba, 0x70, 0x62, 0x6f, 0x01, 0x57, 0x2e, 0xca, 0xda, 0x3e, 0x37, 0xcd, 0x2d, 0x6b, 0xda, 0x3e,
|
0xba, 0x0a, 0x12, 0x6f, 0x01, 0x37, 0x2e, 0x2a, 0xda, 0xbe, 0x34, 0xcd, 0x2d, 0x5b, 0xda, 0xbe,
|
||||||
0xf7, 0x49, 0x95, 0x72, 0x2a, 0xa5, 0x90, 0xb9, 0x52, 0x8c, 0xc1, 0x4e, 0xaa, 0x92, 0x48, 0x27,
|
0x8c, 0xc9, 0x95, 0x72, 0xa5, 0xb5, 0xd2, 0xa5, 0x52, 0x8c, 0x21, 0x2e, 0x9b, 0x1e, 0x91, 0x4f,
|
||||||
0xed, 0x4e, 0x9a, 0x78, 0xa4, 0xf3, 0x91, 0xb2, 0xef, 0x97, 0x43, 0xe0, 0xb9, 0x0f, 0xfb, 0x01,
|
0xda, 0x83, 0xfc, 0xe1, 0x71, 0x56, 0x8e, 0x94, 0x63, 0xbf, 0x9e, 0x82, 0x2c, 0x63, 0xc4, 0xd7,
|
||||||
0x0e, 0x38, 0x25, 0x51, 0x38, 0x34, 0x5d, 0xdb, 0x9b, 0x48, 0x25, 0xe4, 0x26, 0x73, 0x6d, 0x50,
|
0x70, 0x22, 0x29, 0x8d, 0xa3, 0xb1, 0xe9, 0xda, 0xe1, 0x4c, 0xb3, 0xd2, 0xbb, 0xcc, 0xb5, 0xa0,
|
||||||
0x79, 0x4e, 0xe1, 0x41, 0x36, 0x44, 0xd2, 0x13, 0xf6, 0xf9, 0xd6, 0x7c, 0x8c, 0x38, 0xaf, 0x85,
|
0x71, 0x1f, 0xe3, 0x49, 0x31, 0x44, 0xf2, 0x1d, 0xf6, 0xcb, 0xbd, 0xe5, 0x18, 0x71, 0x5e, 0xab,
|
||||||
0xa6, 0xf7, 0xa1, 0xd2, 0x33, 0x09, 0x9f, 0x6f, 0xf1, 0x39, 0x72, 0xec, 0xc0, 0xf6, 0x2c, 0x1c,
|
0x8c, 0xde, 0x45, 0x9c, 0x2d, 0x24, 0xfc, 0x72, 0x4f, 0x2e, 0x91, 0x0b, 0x07, 0xf6, 0x17, 0xe9,
|
||||||
0xf6, 0x05, 0xec, 0xf4, 0xc3, 0xf8, 0x32, 0x0d, 0xc0, 0x85, 0x9d, 0x57, 0xa4, 0x54, 0x70, 0x99,
|
0x88, 0x8f, 0xe1, 0x60, 0x14, 0x25, 0xb7, 0x79, 0x02, 0x2e, 0x1c, 0xfc, 0x48, 0xcc, 0xe1, 0x6d,
|
||||||
0x77, 0x4d, 0x6e, 0xb2, 0x4f, 0x73, 0x27, 0x95, 0xf6, 0xd5, 0xe9, 0xf0, 0x4a, 0xe4, 0x7d, 0x95,
|
0xd9, 0x35, 0xa5, 0x29, 0x3e, 0x2a, 0x83, 0x38, 0xef, 0xab, 0xab, 0xf1, 0x9d, 0x2a, 0xfb, 0x2a,
|
||||||
0xae, 0xd9, 0xdf, 0xf0, 0xd9, 0x79, 0x18, 0xeb, 0x57, 0x42, 0xe9, 0xb4, 0xe4, 0xb1, 0xee, 0x89,
|
0x5f, 0x9f, 0x0d, 0xa0, 0x1d, 0xe8, 0x28, 0x1f, 0x31, 0x97, 0x2a, 0xc9, 0x86, 0xa1, 0xa6, 0xfe,
|
||||||
0xf1, 0x58, 0xc4, 0xcf, 0xe3, 0x21, 0x29, 0xbd, 0x51, 0x72, 0xf8, 0x23, 0x3c, 0x48, 0xf5, 0x4b,
|
0x1e, 0x76, 0xa1, 0x73, 0x1d, 0xc6, 0x4c, 0x7d, 0x0b, 0x1d, 0xb0, 0x03, 0x3d, 0xa3, 0x7e, 0xeb,
|
||||||
0x32, 0xab, 0xc5, 0x1d, 0x42, 0x5c, 0x76, 0x64, 0x9f, 0xdf, 0x77, 0xb9, 0x3a, 0xec, 0x42, 0x7d,
|
0xfc, 0xdf, 0x56, 0x3e, 0x00, 0x96, 0x8f, 0x40, 0x0f, 0xec, 0xfc, 0x60, 0x74, 0xfc, 0x22, 0x09,
|
||||||
0x20, 0xc3, 0x74, 0x04, 0x9e, 0x88, 0x58, 0xf7, 0x02, 0x49, 0xed, 0x2d, 0x6c, 0x42, 0xe3, 0x2c,
|
0xaf, 0x5c, 0x31, 0x7e, 0x03, 0x8f, 0xd7, 0xe7, 0x38, 0x23, 0xfa, 0xb5, 0x8f, 0x9f, 0x57, 0xc7,
|
||||||
0x88, 0x14, 0xb5, 0x2d, 0x74, 0xc0, 0x1e, 0xc8, 0x09, 0xb5, 0x6b, 0x47, 0xb7, 0xf5, 0x74, 0x40,
|
0x18, 0x47, 0xf0, 0xa4, 0xf9, 0x13, 0x80, 0x9e, 0xbf, 0xf5, 0xc3, 0xe2, 0x6d, 0xf7, 0x31, 0x3e,
|
||||||
0xcd, 0x49, 0x46, 0x0f, 0xec, 0x34, 0x71, 0x74, 0xfc, 0x8c, 0x24, 0x2f, 0x5f, 0x29, 0xfc, 0x09,
|
0x87, 0xfe, 0x66, 0xe9, 0xf1, 0xc4, 0x6f, 0x90, 0xb4, 0xd7, 0x84, 0x32, 0x7e, 0x07, 0x47, 0xb5,
|
||||||
0x1e, 0x2e, 0xbf, 0x33, 0x0a, 0xd1, 0x2f, 0x3d, 0xce, 0x5e, 0x19, 0x53, 0xd8, 0x87, 0x47, 0xd5,
|
0xe2, 0xe1, 0xfb, 0x7e, 0x93, 0x10, 0xbc, 0x46, 0x98, 0xf1, 0x2b, 0x78, 0xb4, 0xd6, 0xe2, 0x78,
|
||||||
0x4f, 0x14, 0x7a, 0xfe, 0xda, 0x87, 0xcf, 0x5b, 0xbf, 0xa7, 0xf0, 0x19, 0xb4, 0x57, 0xa5, 0x89,
|
0xe4, 0x6f, 0x8e, 0x0c, 0xaf, 0x06, 0xf1, 0x45, 0xe7, 0x6d, 0x3b, 0x9d, 0xcc, 0x7e, 0xdd, 0x37,
|
||||||
0x07, 0x7e, 0x45, 0xcb, 0x79, 0x55, 0xa8, 0xc2, 0xe7, 0xb0, 0x57, 0x12, 0x17, 0x7e, 0xec, 0x57,
|
0xff, 0x1f, 0xbe, 0xf8, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x27, 0x95, 0xc1, 0x78, 0x4c, 0x08, 0x00,
|
||||||
0x09, 0xd5, 0xab, 0x84, 0x15, 0x7e, 0x3f, 0x2b, 0xe1, 0x7c, 0x04, 0xe1, 0x9e, 0xbf, 0x3a, 0xd2,
|
0x00,
|
||||||
0xbc, 0x12, 0xa4, 0xf0, 0x1d, 0x3c, 0xbe, 0xab, 0x7e, 0xd8, 0xf1, 0xef, 0xd1, 0x96, 0x77, 0x9f,
|
|
||||||
0x87, 0x3a, 0x6e, 0xbc, 0xad, 0x27, 0xa3, 0xc9, 0xbb, 0x6d, 0xf3, 0x1f, 0xea, 0xdb, 0x0f, 0x01,
|
|
||||||
0x00, 0x00, 0xff, 0xff, 0xad, 0x4e, 0x98, 0x29, 0x50, 0x09, 0x00, 0x00,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ service Replication {
|
|||||||
rpc DestroySnapshots(DestroySnapshotsReq) returns (DestroySnapshotsRes);
|
rpc DestroySnapshots(DestroySnapshotsReq) returns (DestroySnapshotsRes);
|
||||||
rpc ReplicationCursor(ReplicationCursorReq) returns (ReplicationCursorRes);
|
rpc ReplicationCursor(ReplicationCursorReq) returns (ReplicationCursorRes);
|
||||||
rpc SendCompleted(SendCompletedReq) returns (SendCompletedRes);
|
rpc SendCompleted(SendCompletedReq) returns (SendCompletedRes);
|
||||||
rpc HintMostRecentCommonAncestor(HintMostRecentCommonAncestorReq) returns (HintMostRecentCommonAncestorRes);
|
|
||||||
// for Send and Recv, see package rpc
|
// for Send and Recv, see package rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,19 +125,3 @@ message PingRes {
|
|||||||
// Echo must be PingReq.Message
|
// Echo must be PingReq.Message
|
||||||
string Echo = 1;
|
string Echo = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message HintMostRecentCommonAncestorReq {
|
|
||||||
string Filesystem = 1;
|
|
||||||
|
|
||||||
// A copy of the FilesystemVersion on the sending side that the replication
|
|
||||||
// algorithm identified as a shared most recent common version between sending
|
|
||||||
// and receiving side.
|
|
||||||
//
|
|
||||||
// If nil, this is an indication that the replication algorithm could not
|
|
||||||
// find a common ancestor between the two sides.
|
|
||||||
// NOTE: nilness does not mean that replication never happened - there could
|
|
||||||
// as well be a replication conflict. thus, dont' jump to conclusions too
|
|
||||||
// rapidly here.
|
|
||||||
FilesystemVersion SenderVersion = 2;
|
|
||||||
}
|
|
||||||
message HintMostRecentCommonAncestorRes {}
|
|
||||||
@@ -33,7 +33,6 @@ type Endpoint interface {
|
|||||||
ListFilesystemVersions(ctx context.Context, req *pdu.ListFilesystemVersionsReq) (*pdu.ListFilesystemVersionsRes, error)
|
ListFilesystemVersions(ctx context.Context, req *pdu.ListFilesystemVersionsReq) (*pdu.ListFilesystemVersionsRes, error)
|
||||||
DestroySnapshots(ctx context.Context, req *pdu.DestroySnapshotsReq) (*pdu.DestroySnapshotsRes, error)
|
DestroySnapshots(ctx context.Context, req *pdu.DestroySnapshotsReq) (*pdu.DestroySnapshotsRes, error)
|
||||||
WaitForConnectivity(ctx context.Context) error
|
WaitForConnectivity(ctx context.Context) error
|
||||||
HintMostRecentCommonAncestor(context.Context, *pdu.HintMostRecentCommonAncestorReq) (*pdu.HintMostRecentCommonAncestorRes, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sender interface {
|
type Sender interface {
|
||||||
@@ -288,7 +287,10 @@ func (p *Planner) doPlanning(ctx context.Context) ([]*Filesystem, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctr := p.promBytesReplicated.WithLabelValues(fs.Path)
|
var ctr prometheus.Counter
|
||||||
|
if p.promBytesReplicated != nil {
|
||||||
|
ctr = p.promBytesReplicated.WithLabelValues(fs.Path)
|
||||||
|
}
|
||||||
|
|
||||||
q = append(q, &Filesystem{
|
q = append(q, &Filesystem{
|
||||||
sender: p.sender,
|
sender: p.sender,
|
||||||
@@ -359,43 +361,6 @@ func (fs *Filesystem) doPlanning(ctx context.Context) ([]*Step, error) {
|
|||||||
log(ctx).WithField("token", resumeToken).Debug("decode resume token")
|
log(ctx).WithField("token", resumeToken).Debug("decode resume token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// give both sides a hint about how far prior replication attempts got
|
|
||||||
// This serves as a cummulative variant of SendCompleted and can be useful
|
|
||||||
// for example to release stale holds from an earlier (interrupted) replication.
|
|
||||||
// TODO FIXME: enqueue this as a replication step instead of doing it here during planning
|
|
||||||
// then again, the step should run regardless of planning success
|
|
||||||
// so maybe a separate phase before PLANNING, then?
|
|
||||||
path, conflict := IncrementalPath(rfsvs, sfsvs)
|
|
||||||
var sender_mrca *pdu.FilesystemVersion
|
|
||||||
if conflict == nil && len(path) > 0 {
|
|
||||||
sender_mrca = path[0] // shadow
|
|
||||||
}
|
|
||||||
// yes, sender_mrca may be nil, indicating that we do not have an mrca
|
|
||||||
{
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
doHint := func(ep Endpoint, name string) {
|
|
||||||
defer wg.Done()
|
|
||||||
ctx, endTask := trace.WithTask(ctx, "hint-mrca-"+name)
|
|
||||||
defer endTask()
|
|
||||||
|
|
||||||
log := log(ctx).WithField("to_side", name).
|
|
||||||
WithField("sender_mrca", sender_mrca.String())
|
|
||||||
log.Debug("hint most recent common ancestor")
|
|
||||||
hint := &pdu.HintMostRecentCommonAncestorReq{
|
|
||||||
Filesystem: fs.Path,
|
|
||||||
SenderVersion: sender_mrca,
|
|
||||||
}
|
|
||||||
_, err := ep.HintMostRecentCommonAncestor(ctx, hint)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("error hinting most recent common ancestor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Add(2)
|
|
||||||
go doHint(fs.sender, "sender")
|
|
||||||
go doHint(fs.receiver, "receiver")
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
var steps []*Step
|
var steps []*Step
|
||||||
// build the list of replication steps
|
// build the list of replication steps
|
||||||
//
|
//
|
||||||
@@ -632,7 +597,9 @@ func (s *Step) doReplication(ctx context.Context) error {
|
|||||||
s.byteCounterMtx.Unlock()
|
s.byteCounterMtx.Unlock()
|
||||||
defer func() {
|
defer func() {
|
||||||
defer s.byteCounterMtx.Lock().Unlock()
|
defer s.byteCounterMtx.Lock().Unlock()
|
||||||
s.parent.promBytesReplicated.Add(float64(s.byteCounter.Count()))
|
if s.parent.promBytesReplicated != nil {
|
||||||
|
s.parent.promBytesReplicated.Add(float64(s.byteCounter.Count()))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rr := &pdu.ReceiveReq{
|
rr := &pdu.ReceiveReq{
|
||||||
|
|||||||
@@ -142,13 +142,6 @@ func (c *Client) SendCompleted(ctx context.Context, in *pdu.SendCompletedReq) (*
|
|||||||
return c.controlClient.SendCompleted(ctx, in)
|
return c.controlClient.SendCompleted(ctx, in)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) HintMostRecentCommonAncestor(ctx context.Context, in *pdu.HintMostRecentCommonAncestorReq) (*pdu.HintMostRecentCommonAncestorRes, error) {
|
|
||||||
ctx, endSpan := trace.WithSpan(ctx, "rpc.client.HintMostRecentCommonAncestor")
|
|
||||||
defer endSpan()
|
|
||||||
|
|
||||||
return c.controlClient.HintMostRecentCommonAncestor(ctx, in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WaitForConnectivity(ctx context.Context) error {
|
func (c *Client) WaitForConnectivity(ctx context.Context) error {
|
||||||
ctx, endSpan := trace.WithSpan(ctx, "rpc.client.WaitForConnectivity")
|
ctx, endSpan := trace.WithSpan(ctx, "rpc.client.WaitForConnectivity")
|
||||||
defer endSpan()
|
defer endSpan()
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ func (m *HandshakeMessage) DecodeReader(r io.Reader, maxLen int) error {
|
|||||||
|
|
||||||
func DoHandshakeCurrentVersion(conn net.Conn, deadline time.Time) *HandshakeError {
|
func DoHandshakeCurrentVersion(conn net.Conn, deadline time.Time) *HandshakeError {
|
||||||
// current protocol version is hardcoded here
|
// current protocol version is hardcoded here
|
||||||
return DoHandshakeVersion(conn, deadline, 3)
|
return DoHandshakeVersion(conn, deadline, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
const HandshakeMessageMaxLen = 16 * 4096
|
const HandshakeMessageMaxLen = 16 * 4096
|
||||||
|
|||||||
+80
-40
@@ -12,84 +12,100 @@ import (
|
|||||||
|
|
||||||
var cache sync.Map
|
var cache sync.Map
|
||||||
|
|
||||||
func Duration(varname string, def time.Duration) time.Duration {
|
func Reset() {
|
||||||
|
cache.Range(func(key, _ interface{}) bool {
|
||||||
|
cache.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Duration(varname string, def time.Duration) (d time.Duration) {
|
||||||
|
var err error
|
||||||
if v, ok := cache.Load(varname); ok {
|
if v, ok := cache.Load(varname); ok {
|
||||||
return v.(time.Duration)
|
return v.(time.Duration)
|
||||||
}
|
}
|
||||||
e := os.Getenv(varname)
|
e := os.Getenv(varname)
|
||||||
if e == "" {
|
if e == "" {
|
||||||
return def
|
d = def
|
||||||
}
|
} else {
|
||||||
d, err := time.ParseDuration(e)
|
d, err = time.ParseDuration(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cache.Store(varname, d)
|
cache.Store(varname, d)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func Int(varname string, def int) int {
|
func Int(varname string, def int) (d int) {
|
||||||
if v, ok := cache.Load(varname); ok {
|
if v, ok := cache.Load(varname); ok {
|
||||||
return v.(int)
|
return v.(int)
|
||||||
}
|
}
|
||||||
e := os.Getenv(varname)
|
e := os.Getenv(varname)
|
||||||
if e == "" {
|
if e == "" {
|
||||||
return def
|
d = def
|
||||||
|
} else {
|
||||||
|
d64, err := strconv.ParseInt(e, 10, strconv.IntSize)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
d = int(d64)
|
||||||
}
|
}
|
||||||
d64, err := strconv.ParseInt(e, 10, strconv.IntSize)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
d := int(d64)
|
|
||||||
cache.Store(varname, d)
|
cache.Store(varname, d)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func Int64(varname string, def int64) int64 {
|
func Int64(varname string, def int64) (d int64) {
|
||||||
|
var err error
|
||||||
if v, ok := cache.Load(varname); ok {
|
if v, ok := cache.Load(varname); ok {
|
||||||
return v.(int64)
|
return v.(int64)
|
||||||
}
|
}
|
||||||
e := os.Getenv(varname)
|
e := os.Getenv(varname)
|
||||||
if e == "" {
|
if e == "" {
|
||||||
return def
|
d = def
|
||||||
}
|
} else {
|
||||||
d, err := strconv.ParseInt(e, 10, 64)
|
d, err = strconv.ParseInt(e, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cache.Store(varname, d)
|
cache.Store(varname, d)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func Bool(varname string, def bool) bool {
|
func Bool(varname string, def bool) (d bool) {
|
||||||
|
var err error
|
||||||
if v, ok := cache.Load(varname); ok {
|
if v, ok := cache.Load(varname); ok {
|
||||||
return v.(bool)
|
return v.(bool)
|
||||||
}
|
}
|
||||||
e := os.Getenv(varname)
|
e := os.Getenv(varname)
|
||||||
if e == "" {
|
if e == "" {
|
||||||
return def
|
d = def
|
||||||
}
|
} else {
|
||||||
d, err := strconv.ParseBool(e)
|
d, err = strconv.ParseBool(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cache.Store(varname, d)
|
cache.Store(varname, d)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func String(varname string, def string) string {
|
func String(varname string, def string) (d string) {
|
||||||
if v, ok := cache.Load(varname); ok {
|
if v, ok := cache.Load(varname); ok {
|
||||||
return v.(string)
|
return v.(string)
|
||||||
}
|
}
|
||||||
e := os.Getenv(varname)
|
e := os.Getenv(varname)
|
||||||
if e == "" {
|
if e == "" {
|
||||||
return def
|
d = def
|
||||||
|
} else {
|
||||||
|
d = e
|
||||||
}
|
}
|
||||||
cache.Store(varname, e)
|
cache.Store(varname, d)
|
||||||
return e
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func Var(varname string, def flag.Value) interface{} {
|
func Var(varname string, def flag.Value) (d interface{}) {
|
||||||
|
|
||||||
// use def's type to instantiate a new object of that same type
|
// use def's type to instantiate a new object of that same type
|
||||||
// and call flag.Value.Set() on it
|
// and call flag.Value.Set() on it
|
||||||
@@ -100,19 +116,43 @@ func Var(varname string, def flag.Value) interface{} {
|
|||||||
defElemType := defType.Elem()
|
defElemType := defType.Elem()
|
||||||
|
|
||||||
if v, ok := cache.Load(varname); ok {
|
if v, ok := cache.Load(varname); ok {
|
||||||
return v.(string)
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
e := os.Getenv(varname)
|
e := os.Getenv(varname)
|
||||||
if e == "" {
|
if e == "" {
|
||||||
return def
|
d = def
|
||||||
|
} else {
|
||||||
|
newInstance := reflect.New(defElemType)
|
||||||
|
if err := newInstance.Interface().(flag.Value).Set(e); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
d = newInstance.Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
newInstance := reflect.New(defElemType)
|
cache.Store(varname, d)
|
||||||
if err := newInstance.Interface().(flag.Value).Set(e); err != nil {
|
return d
|
||||||
panic(err)
|
}
|
||||||
}
|
|
||||||
|
type Report struct {
|
||||||
res := newInstance.Interface()
|
Entries []EntryReport
|
||||||
cache.Store(varname, res)
|
}
|
||||||
return res
|
|
||||||
|
type EntryReport struct {
|
||||||
|
Var string
|
||||||
|
Value string
|
||||||
|
ValueGoType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetReport() *Report {
|
||||||
|
var r Report
|
||||||
|
cache.Range(func(key, value interface{}) bool {
|
||||||
|
r.Entries = append(r.Entries, EntryReport{
|
||||||
|
Var: key.(string),
|
||||||
|
Value: fmt.Sprintf("%v", value),
|
||||||
|
ValueGoType: fmt.Sprintf("%T", value),
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return &r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,19 +32,27 @@ func (m *ExampleVarType) Set(s string) error {
|
|||||||
|
|
||||||
const EnvVarName = "ZREPL_ENVCONST_UNIT_TEST_VAR"
|
const EnvVarName = "ZREPL_ENVCONST_UNIT_TEST_VAR"
|
||||||
|
|
||||||
func TestVar(t *testing.T) {
|
func TestVarDefaultValue(t *testing.T) {
|
||||||
|
envconst.Reset()
|
||||||
_, set := os.LookupEnv(EnvVarName)
|
_, set := os.LookupEnv(EnvVarName)
|
||||||
require.False(t, set)
|
require.False(t, set)
|
||||||
defer os.Unsetenv(EnvVarName)
|
defer os.Unsetenv(EnvVarName)
|
||||||
|
|
||||||
val := envconst.Var(EnvVarName, &Var1)
|
val := envconst.Var(EnvVarName, &Var1)
|
||||||
if &Var1 != val {
|
if &Var1 != val {
|
||||||
t.Errorf("default value shut be same address")
|
t.Errorf("default value should be same address")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVarOverriddenValue(t *testing.T) {
|
||||||
|
envconst.Reset()
|
||||||
|
_, set := os.LookupEnv(EnvVarName)
|
||||||
|
require.False(t, set)
|
||||||
|
defer os.Unsetenv(EnvVarName)
|
||||||
|
|
||||||
err := os.Setenv(EnvVarName, "var2")
|
err := os.Setenv(EnvVarName, "var2")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
val = envconst.Var(EnvVarName, &Var1)
|
val := envconst.Var(EnvVarName, &Var1)
|
||||||
require.Equal(t, &Var2, val, "only structural identity is required for non-default vars")
|
require.Equal(t, &Var2, val, "only structural identity is required for non-default vars")
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -116,6 +115,13 @@ func (v FilesystemVersion) RelName() string {
|
|||||||
}
|
}
|
||||||
func (v FilesystemVersion) String() string { return v.RelName() }
|
func (v FilesystemVersion) String() string { return v.RelName() }
|
||||||
|
|
||||||
|
// Only takes into account those attributes of FilesystemVersion that
|
||||||
|
// are immutable over time in ZFS.
|
||||||
|
func FilesystemVersionEqualIdentity(a, b FilesystemVersion) bool {
|
||||||
|
// .Name is mutable
|
||||||
|
return a.Guid == b.Guid && a.CreateTXG == b.CreateTXG && a.Creation == b.Creation
|
||||||
|
}
|
||||||
|
|
||||||
func (v FilesystemVersion) ToAbsPath(p *DatasetPath) string {
|
func (v FilesystemVersion) ToAbsPath(p *DatasetPath) string {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
b.WriteString(p.ToString())
|
b.WriteString(p.ToString())
|
||||||
@@ -224,10 +230,6 @@ func ZFSListFilesystemVersions(ctx context.Context, fs *DatasetPath, options Lis
|
|||||||
res = make([]FilesystemVersion, 0)
|
res = make([]FilesystemVersion, 0)
|
||||||
for listResult := range listResults {
|
for listResult := range listResults {
|
||||||
if listResult.Err != nil {
|
if listResult.Err != nil {
|
||||||
if listResult.Err == io.ErrUnexpectedEOF {
|
|
||||||
// Since we specified the fs on the command line, we'll treat this like the filesystem doesn't exist
|
|
||||||
return []FilesystemVersion{}, nil
|
|
||||||
}
|
|
||||||
return nil, listResult.Err
|
return nil, listResult.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-19
@@ -454,7 +454,7 @@ func (s *SendStream) killAndWait(precedingReadErr error) error {
|
|||||||
WaitErr: exitErr,
|
WaitErr: exitErr,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.opErr = fmt.Errorf("zfs send exited with status code 0")
|
s.opErr = precedingReadErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect the edge where we're called from s.Read
|
// detect the edge where we're called from s.Read
|
||||||
@@ -727,13 +727,13 @@ func (a ZFSSendArgsUnvalidated) validateCorrespondsToResumeToken(ctx context.Con
|
|||||||
|
|
||||||
if (a.From != nil) != t.HasFromGUID { // existence must be same
|
if (a.From != nil) != t.HasFromGUID { // existence must be same
|
||||||
if t.HasFromGUID {
|
if t.HasFromGUID {
|
||||||
return gen.fmt("resume token not expected to be incremental, but `fromguid` = %q", t.FromGUID)
|
return gen.fmt("resume token not expected to be incremental, but `fromguid` = %v", t.FromGUID)
|
||||||
} else {
|
} else {
|
||||||
return gen.fmt("resume token expected to be incremental, but `fromguid` not present")
|
return gen.fmt("resume token expected to be incremental, but `fromguid` not present")
|
||||||
}
|
}
|
||||||
} else if t.HasFromGUID { // if exists (which is same, we checked above), they must match
|
} else if t.HasFromGUID { // if exists (which is same, we checked above), they must match
|
||||||
if t.FromGUID != a.From.GUID {
|
if t.FromGUID != a.From.GUID {
|
||||||
return gen.fmt("resume token `fromguid` != expected: %q != %q", t.FromGUID, a.From.GUID)
|
return gen.fmt("resume token `fromguid` != expected: %v != %v", t.FromGUID, a.From.GUID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = struct{}{} // both empty, ok
|
_ = struct{}{} // both empty, ok
|
||||||
@@ -744,7 +744,7 @@ func (a ZFSSendArgsUnvalidated) validateCorrespondsToResumeToken(ctx context.Con
|
|||||||
return gen.fmt("resume token does not have `toguid`")
|
return gen.fmt("resume token does not have `toguid`")
|
||||||
}
|
}
|
||||||
if t.ToGUID != a.To.GUID { // a.To != nil because Validate checks for that
|
if t.ToGUID != a.To.GUID { // a.To != nil because Validate checks for that
|
||||||
return gen.fmt("resume token `toguid` != expected: %q != %q", t.ToGUID, a.To.GUID)
|
return gen.fmt("resume token `toguid` != expected: %v != %v", t.ToGUID, a.To.GUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Encrypted.B {
|
if a.Encrypted.B {
|
||||||
@@ -1014,7 +1014,9 @@ func ZFSRecv(ctx context.Context, fs string, v *ZFSSendArgVersion, stream io.Rea
|
|||||||
snaps, err := ZFSListFilesystemVersions(ctx, fsdp, ListFilesystemVersionsOptions{
|
snaps, err := ZFSListFilesystemVersions(ctx, fsdp, ListFilesystemVersionsOptions{
|
||||||
Types: Snapshots,
|
Types: Snapshots,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if _, ok := err.(*DatasetDoesNotExist); ok {
|
||||||
|
snaps = []FilesystemVersion{}
|
||||||
|
} else if err != nil {
|
||||||
return fmt.Errorf("cannot list versions for rollback for forced receive: %s", err)
|
return fmt.Errorf("cannot list versions for rollback for forced receive: %s", err)
|
||||||
}
|
}
|
||||||
sort.Slice(snaps, func(i, j int) bool {
|
sort.Slice(snaps, func(i, j int) bool {
|
||||||
@@ -1595,22 +1597,32 @@ var ErrBookmarkCloningNotSupported = fmt.Errorf("bookmark cloning feature is not
|
|||||||
//
|
//
|
||||||
// does not destroy an existing bookmark, returns
|
// does not destroy an existing bookmark, returns
|
||||||
//
|
//
|
||||||
func ZFSBookmark(ctx context.Context, fs string, v ZFSSendArgVersion, bookmark string) (err error) {
|
func ZFSBookmark(ctx context.Context, fs string, v FilesystemVersion, bookmark string) (bm FilesystemVersion, err error) {
|
||||||
|
|
||||||
|
bm = FilesystemVersion{
|
||||||
|
Type: Bookmark,
|
||||||
|
Name: bookmark,
|
||||||
|
UserRefs: OptionUint64{Valid: false},
|
||||||
|
// bookmarks have the same createtxg, guid and creation as their origin
|
||||||
|
CreateTXG: v.CreateTXG,
|
||||||
|
Guid: v.Guid,
|
||||||
|
Creation: v.Creation,
|
||||||
|
}
|
||||||
|
|
||||||
promTimer := prometheus.NewTimer(prom.ZFSBookmarkDuration.WithLabelValues(fs))
|
promTimer := prometheus.NewTimer(prom.ZFSBookmarkDuration.WithLabelValues(fs))
|
||||||
defer promTimer.ObserveDuration()
|
defer promTimer.ObserveDuration()
|
||||||
|
|
||||||
if !v.IsSnapshot() {
|
if !v.IsSnapshot() {
|
||||||
return ErrBookmarkCloningNotSupported // TODO This is work in progress: https://github.com/zfsonlinux/zfs/pull/9571
|
return bm, ErrBookmarkCloningNotSupported // TODO This is work in progress: https://github.com/zfsonlinux/zfs/pull/9571
|
||||||
}
|
}
|
||||||
|
|
||||||
snapname := v.FullPath(fs)
|
snapname := v.FullPath(fs)
|
||||||
if err := EntityNamecheck(snapname, EntityTypeSnapshot); err != nil {
|
if err := EntityNamecheck(snapname, EntityTypeSnapshot); err != nil {
|
||||||
return err
|
return bm, err
|
||||||
}
|
}
|
||||||
bookmarkname := fmt.Sprintf("%s#%s", fs, bookmark)
|
bookmarkname := fmt.Sprintf("%s#%s", fs, bookmark)
|
||||||
if err := EntityNamecheck(bookmarkname, EntityTypeBookmark); err != nil {
|
if err := EntityNamecheck(bookmarkname, EntityTypeBookmark); err != nil {
|
||||||
return err
|
return bm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
debug("bookmark: %q %q", snapname, bookmarkname)
|
debug("bookmark: %q %q", snapname, bookmarkname)
|
||||||
@@ -1619,27 +1631,27 @@ func ZFSBookmark(ctx context.Context, fs string, v ZFSSendArgVersion, bookmark s
|
|||||||
stdio, err := cmd.CombinedOutput()
|
stdio, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ddne := tryDatasetDoesNotExist(snapname, stdio); ddne != nil {
|
if ddne := tryDatasetDoesNotExist(snapname, stdio); ddne != nil {
|
||||||
return ddne
|
return bm, ddne
|
||||||
} else if zfsBookmarkExistsRegex.Match(stdio) {
|
} else if zfsBookmarkExistsRegex.Match(stdio) {
|
||||||
|
|
||||||
// check if this was idempotent
|
// check if this was idempotent
|
||||||
bookGuid, err := ZFSGetGUID(ctx, fs, "#"+bookmark)
|
bookGuid, err := ZFSGetGUID(ctx, fs, "#"+bookmark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "bookmark idempotency check") // guid error expressive enough
|
return bm, errors.Wrap(err, "bookmark idempotency check") // guid error expressive enough
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.GUID == bookGuid {
|
if v.Guid == bookGuid {
|
||||||
debug("bookmark: %q %q was idempotent: {snap,book}guid %d == %d", snapname, bookmarkname, v.GUID, bookGuid)
|
debug("bookmark: %q %q was idempotent: {snap,book}guid %d == %d", snapname, bookmarkname, v.Guid, bookGuid)
|
||||||
return nil
|
return bm, nil
|
||||||
}
|
}
|
||||||
return &BookmarkExists{
|
return bm, &BookmarkExists{
|
||||||
fs: fs, bookmarkOrigin: v, bookmark: bookmark,
|
fs: fs, bookmarkOrigin: v.ToSendArgVersion(), bookmark: bookmark,
|
||||||
zfsMsg: string(stdio),
|
zfsMsg: string(stdio),
|
||||||
bookGuid: bookGuid,
|
bookGuid: bookGuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return &ZFSError{
|
return bm, &ZFSError{
|
||||||
Stderr: stdio,
|
Stderr: stdio,
|
||||||
WaitErr: err,
|
WaitErr: err,
|
||||||
}
|
}
|
||||||
@@ -1647,8 +1659,7 @@ func ZFSBookmark(ctx context.Context, fs string, v ZFSSendArgVersion, bookmark s
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return bm, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ZFSRollback(ctx context.Context, fs *DatasetPath, snapshot FilesystemVersion, rollbackArgs ...string) (err error) {
|
func ZFSRollback(ctx context.Context, fs *DatasetPath, snapshot FilesystemVersion, rollbackArgs ...string) (err error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user