Compare commits

...

21 Commits

Author SHA1 Message Date
Christian Schwarz bdd6cf0395 Revert "DROP THIS don't run tests in CI"
This reverts commit a3c6173b85dede625f51ca7172a53a5aff024fae.
2020-06-07 12:29:08 +02:00
InsanePrawn 5d67fe9d00 Specify ubuntu image in gitlab ci config 2020-06-07 12:29:08 +02:00
Christian Schwarz 197c731a49 build: always create artifacts directory 2020-06-07 12:29:08 +02:00
Christian Schwarz 1fb612030e DROP THIS don't run tests in CI 2020-06-07 12:29:08 +02:00
Christian Schwarz 6d518e4a86 add .gitlab-ci.yml for continuous platform tests 2020-06-07 12:29:08 +02:00
Christian Schwarz fb5ae0ecca [#299] envconst: queryable report of resolved variables + integration inot zrepl status --raw 2020-06-07 12:29:08 +02:00
Christian Schwarz 69a718c14b [#322] transport/ssh: bump go-netssh version to fix ssh client process leaks
fixes #322
2020-06-07 11:34:00 +02:00
Christian Schwarz 79e69550c9 docs: 0.3 changelog 2020-06-01 21:28:38 +02:00
Christian Schwarz 5b564a3e28 [#288] option to disable step holds for incremental sends
This is a stop-gap solution until we re-write the pruner to support
rules for removing step holds.

Note that disabling step holds for incremental sends does not affect
zrepl's guarantee that incremental replication is always possible:

Suppose you yank the external drive during an incremental @from -> @to step:

* restarting that step or future incrementals @from -> @to_later` will be possible
  because the replication cursor bookmark points to @from until the step is complete
* resuming @from -> @to will work as long as the pruner on your internal pool doesn't come around to destroy @to.
    * in that case, the replication algorithm should determine that the resumable state
      on the receiving side isuseless because @to no longer exists on the sending side,
      and consequently clear it, and restart an incremental step @from -> @to_later

refs #288
2020-06-01 21:28:38 +02:00
Christian Schwarz b6b2d7b615 docs: update & extend replication overview wrt step holds + bookmarks 2020-06-01 21:28:38 +02:00
Christian Schwarz 356ee40276 docs/configuration/overview.rst: fix wrong headline hierarchy 2020-06-01 21:28:38 +02:00
Christian Schwarz f0146d03d3 docs: update supporters 2020-05-24 18:39:02 +02:00
Christian Schwarz 742e28d2a3 zfs: ZFSListFilesystemVersions: remove handling of io.ErrUnexpectedEOF
ZFSListChan returns (*DatasetDoesNotExist) for the case mentioned in the comment
2020-05-24 18:39:02 +02:00
Christian Schwarz 58f364dc04 zfs: fix error message formatting for send args validation 2020-05-24 18:39:02 +02:00
Christian Schwarz a5c9450b2d [#321] platformtest: add test for zfs.ZFSHolds 2020-05-24 18:39:02 +02:00
Christian Schwarz b222574ff3 [#321] endpoint: ListAbstractions: acutally emit one Abstraction per matching hold 2020-05-24 18:39:02 +02:00
Christian Schwarz 28e1c486f5 [#321] platformtest: minimal integration tests for package replication
# Conflicts:
#	platformtest/tests/generated_cases.go
2020-05-24 18:39:02 +02:00
Christian Schwarz 82370f3555 [#321] platformtest: generate test case list + coverage tooling 2020-05-24 18:39:02 +02:00
Christian Schwarz 4f6c57fbba [#321] platformtest: fix test ListFilesystemVersionsZeroExistIsNotAnError 2020-05-24 18:39:02 +02:00
Christian Schwarz 5a558e3b71 [#316] endpoint: delete unreachable code 2020-05-24 18:09:33 +02:00
Christian Schwarz b33f670b9d [#316] endpoint / replication protocol: more robust step-holds and replication cursor management
- drop HintMostRecentCommonAncestor rpc call
    - it is wrong to put faith into the active side of the replication to always make that call
      (we might not trust it, ref pull setup)
- clean up step holds + step bookmarks + replication cursor bookmarks on
  send RPC instead
    - this makes it symmetric with Receive RPC
- use a cache (endpoint.sendAbstractionsCache) to avoid the cost of
  listing the on-disk endpoint abstractions state on every step

The "create" methods for endpoint abstractions (CreateReplicationCursor, HoldStep) are now fully
idempotent and return an Abstraction.

Notes about endpoint.sendAbstractionsCache:
- fills lazily from disk state on first `Get` operation
- fill from disk is generally only attempted once
    - unless the `ListAbstractions` fails, in which case the fill from
      disk is retried on next `Get` (the current `Get` will observe a
      subset of the actual on-disk abstractions)
    - the `Invalidate` method is called
- it is a global (zrepl process-wide) cache

fixes #316
2020-05-19 11:30:02 +02:00
52 changed files with 1722 additions and 888 deletions
+28 -2
View File
@@ -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: |
+17
View File
@@ -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"
+16
View File
@@ -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"
+53 -18
View File
@@ -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
+10 -2
View File
@@ -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
``` ```
+1
View File
@@ -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
+2
View File
@@ -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=
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+19
View File
@@ -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)
}
+4 -3
View File
@@ -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 {
+2
View File
@@ -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
View File
@@ -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
----- -----
+5 -5
View File
@@ -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``
+83 -20
View File
@@ -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.
+13
View File
@@ -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
+3
View File
@@ -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
View File
@@ -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)()
+7
View File
@@ -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)
}
}
+31 -7
View File
@@ -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{
+2 -129
View File
@@ -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) {
+2 -1
View File
@@ -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
) )
+3
View File
@@ -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=
+2 -1
View File
@@ -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 -21
View File
@@ -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
} }
+12 -4
View File
@@ -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()
+12
View File
@@ -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
} }
+140
View File
@@ -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)
}
}
+30
View File
@@ -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,
}
+32
View File
@@ -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")
}
+8 -6
View File
@@ -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)
} }
+2 -2
View File
@@ -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")
}
+54
View File
@@ -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)
}
+409
View File
@@ -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")
}
+24 -18
View File
@@ -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())
} }
+1 -19
View File
@@ -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
View File
@@ -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,
} }
-17
View File
@@ -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 {}
+7 -40
View File
@@ -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{
-7
View File
@@ -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()
+1 -1
View File
@@ -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
View File
@@ -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
} }
+11 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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) {