mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-05-02 05:15:45 +00:00
Compare commits
17 Commits
v1.14.0-rc
...
v1.13.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76670e940c | ||
|
|
25d977e5bc | ||
|
|
94c7d4b6d4 | ||
|
|
09401c8454 | ||
|
|
981d64a1b8 | ||
|
|
16b8b8da72 | ||
|
|
9fd73b2d13 | ||
|
|
c377e472e8 | ||
|
|
f5714cb636 | ||
|
|
5ffa12189b | ||
|
|
1882be763e | ||
|
|
42bbf87197 | ||
|
|
8aa6a8e59d | ||
|
|
fdb29819b4 | ||
|
|
74f225037c | ||
|
|
6e90e628aa | ||
|
|
46f64f2f98 |
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@@ -1,14 +1,5 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Dependencies listed in .github/workflows
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "Dependencies"
|
||||
- "github_actions"
|
||||
- "kind/changelog-not-required"
|
||||
# Dependencies listed in go.mod
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/" # Location of package manifests
|
||||
|
||||
33
.github/labeler.yml
vendored
33
.github/labeler.yml
vendored
@@ -1,33 +0,0 @@
|
||||
# This file is used by Auto Label PRs action.
|
||||
# Works with https://github.com/actions/labeler/
|
||||
# Below this line, the keys are labels to be applied, and the values are the file globs to match against.
|
||||
# Anything in the `design` directory gets the `Design` label.
|
||||
Area/Design:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: design/*
|
||||
# Anything that has plugin infra will be labeled.
|
||||
# Individual plugins don't necessarily live here, though
|
||||
Area/Plugins:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: pkg/plugins/**/*
|
||||
Dependencies:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: go.mod
|
||||
Documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: site/content/docs/**/*
|
||||
# Anything in the site directory gets the website label *EXCEPT* docs
|
||||
Website:
|
||||
- all:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: site/**/*
|
||||
- all-globs-to-all-files: '!site/content/docs/**/*'
|
||||
has-changelog:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: changelogs/**
|
||||
has-e2e-2tests:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: test/e2e/**/*
|
||||
has-unit-tests:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: pkg/**/*_test.go
|
||||
43
.github/labels.yaml
vendored
43
.github/labels.yaml
vendored
@@ -1,43 +0,0 @@
|
||||
# This file is used by [prow github action](https://github.com/jpmcb/prow-github-actions/) in .github/workflows/prow-action.yml.
|
||||
# This file only has values for kind and area commands.
|
||||
area:
|
||||
- CLI
|
||||
- CSI
|
||||
- Cloud/AWS
|
||||
- Cloud/Azure
|
||||
- Cloud/DigitalOcean
|
||||
- Cloud/GCP
|
||||
- Cloud/vSphere
|
||||
- Design
|
||||
- Documentation
|
||||
- Filters
|
||||
- Plugins
|
||||
- Process
|
||||
- Storage/Minio
|
||||
- Storage/Cinder
|
||||
- WindowsSupport
|
||||
- datamover
|
||||
- fs-backup
|
||||
- fs-backup/deletion
|
||||
- fs-backup/file-selectable
|
||||
- fs-uploader
|
||||
- kopia-integration
|
||||
- migration
|
||||
- multi-tenancy
|
||||
- progress-monitoring
|
||||
- resilience
|
||||
- schedule
|
||||
- storage/IBM-ObjectStorage
|
||||
- upgrade
|
||||
- volume-snapshot-dm
|
||||
kind:
|
||||
- changelog-not-required
|
||||
- question
|
||||
- refactor
|
||||
- requirement
|
||||
- release-note
|
||||
- release-blocker
|
||||
- spike
|
||||
- tech-debt
|
||||
- usage-error
|
||||
- voting
|
||||
41
.github/labels.yml
vendored
Normal file
41
.github/labels.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
area:
|
||||
- "Cloud/AWS"
|
||||
- "Cloud/GCP"
|
||||
- "Cloud/Azure"
|
||||
- "Design"
|
||||
- "Plugins"
|
||||
|
||||
# Labels that can be applied to PRs with the /kind command
|
||||
kind:
|
||||
- "changelog-not-required"
|
||||
- "tech-debt"
|
||||
|
||||
# Works with https://github.com/actions/labeler/
|
||||
# Below this line, the keys are labels to be applied, and the values are the file globs to match against.
|
||||
# Anything in the `design` directory gets the `Design` label.
|
||||
Area/Design:
|
||||
- design/*
|
||||
|
||||
# Anything in the site directory gets the website label *EXCEPT* docs
|
||||
Website:
|
||||
- any: ["site/**/*", "!site/content/docs/**/*"]
|
||||
|
||||
Documentation:
|
||||
- site/content/docs/**/*
|
||||
|
||||
Dependencies:
|
||||
- go.mod
|
||||
|
||||
# Anything that has plugin infra will be labeled.
|
||||
# Individual plugins don't necessarily live here, though
|
||||
Area/Plugins:
|
||||
- "pkg/plugins/**/*"
|
||||
|
||||
has-unit-tests:
|
||||
- "pkg/**/*_test.go"
|
||||
|
||||
has-e2e-2tests:
|
||||
- "test/e2e/**/*"
|
||||
|
||||
has-changelog:
|
||||
- "changelogs/**"
|
||||
2
.github/workflows/auto_assign_prs.yml
vendored
2
.github/workflows/auto_assign_prs.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set the author of a PR as the assignee
|
||||
uses: kentaro-m/auto-assign-action@v2.0.0
|
||||
uses: kentaro-m/auto-assign-action@v1.1.1
|
||||
with:
|
||||
configuration-path: ".github/auto-assignees.yml"
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
4
.github/workflows/auto_label_prs.yml
vendored
4
.github/workflows/auto_label_prs.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: .github/labeler.yml
|
||||
configuration-path: .github/labels.yml
|
||||
|
||||
2
.github/workflows/auto_request_review.yml
vendored
2
.github/workflows/auto_request_review.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Request a PR review based on files types/paths, and/or groups the author belongs to
|
||||
uses: necojackarc/auto-request-review@v0.13.0
|
||||
uses: necojackarc/auto-request-review@v0.7.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config: .github/auto-assignees.yml
|
||||
|
||||
28
.github/workflows/crds-verify-kind.yaml
vendored
28
.github/workflows/crds-verify-kind.yaml
vendored
@@ -12,14 +12,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22.2'
|
||||
go-version: '1.21.6'
|
||||
id: go
|
||||
# Look for a CLI that's made for this PR
|
||||
- name: Fetch built CLI
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-velero-cli
|
||||
with:
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
velero-${{ github.event.pull_request.number }}-
|
||||
|
||||
- name: Fetch cached go modules
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
|
||||
# If no binaries were built for this PR, build it now.
|
||||
@@ -57,19 +57,19 @@ jobs:
|
||||
matrix:
|
||||
# Latest k8s versions. There's no series-based tag, nor is there a latest tag.
|
||||
k8s:
|
||||
- 1.23.17
|
||||
- 1.24.17
|
||||
- 1.25.16
|
||||
- 1.26.13
|
||||
- 1.27.10
|
||||
- 1.28.6
|
||||
- 1.29.1
|
||||
- 1.19.7
|
||||
- 1.20.2
|
||||
- 1.21.1
|
||||
- 1.22.0
|
||||
- 1.23.6
|
||||
- 1.24.2
|
||||
- 1.25.3
|
||||
# All steps run in parallel unless otherwise specified.
|
||||
# See https://docs.github.com/en/actions/learn-github-actions/managing-complex-workflows#creating-dependent-jobs
|
||||
steps:
|
||||
- name: Fetch built CLI
|
||||
id: cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-velero-cli
|
||||
with:
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
velero-${{ github.event.pull_request.number }}-
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
version: "v0.21.0"
|
||||
version: "v0.17.0"
|
||||
image: "kindest/node:v${{ matrix.k8s }}"
|
||||
- name: Install CRDs
|
||||
run: |
|
||||
|
||||
57
.github/workflows/e2e-test-kind.yaml
vendored
57
.github/workflows/e2e-test-kind.yaml
vendored
@@ -12,27 +12,27 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22.2'
|
||||
go-version: '1.21.6'
|
||||
id: go
|
||||
# Look for a CLI that's made for this PR
|
||||
- name: Fetch built CLI
|
||||
id: cli-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./_output/bin/linux/amd64/velero
|
||||
# The cache key a combination of the current PR number and the commit SHA
|
||||
key: velero-cli-${{ github.event.pull_request.number }}-${{ github.sha }}
|
||||
- name: Fetch built image
|
||||
id: image-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./velero.tar
|
||||
# The cache key a combination of the current PR number and the commit SHA
|
||||
key: velero-image-${{ github.event.pull_request.number }}-${{ github.sha }}
|
||||
- name: Fetch cached go modules
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
if: steps.cli-cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
if: steps.cli-cache.outputs.cache-hit != 'true' || steps.image-cache.outputs.cache-hit != 'true'
|
||||
# If no binaries were built for this PR, build it now.
|
||||
- name: Build Velero CLI
|
||||
@@ -60,48 +60,38 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
k8s:
|
||||
- 1.23.17
|
||||
- 1.24.17
|
||||
- 1.25.16
|
||||
- 1.26.13
|
||||
- 1.27.10
|
||||
- 1.28.6
|
||||
- 1.29.1
|
||||
focus:
|
||||
# tests to focus on, use `|` to concatenate multiple regexes to run on the same job
|
||||
# ordered according to e2e_suite_test.go order
|
||||
- Basic\]\[ClusterResource
|
||||
- ResourceFiltering
|
||||
- ResourceModifier|Backups|PrivilegesMgmt\]\[SSR
|
||||
- Schedule\]\[OrderedResources
|
||||
- NamespaceMapping\]\[Single\]\[Restic|NamespaceMapping\]\[Multiple\]\[Restic
|
||||
- Basic\]\[Nodeport
|
||||
- Basic\]\[StorageClass
|
||||
- 1.19.16
|
||||
- 1.20.15
|
||||
- 1.21.12
|
||||
- 1.22.9
|
||||
- 1.23.6
|
||||
- 1.24.0
|
||||
- 1.25.3
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22.2'
|
||||
go-version: '1.21.6'
|
||||
id: go
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
- name: Install MinIO
|
||||
run:
|
||||
docker run -d --rm -p 9000:9000 -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -e "MINIO_DEFAULT_BUCKETS=bucket,additional-bucket" bitnami/minio:2021.6.17-debian-10-r7
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
with:
|
||||
version: "v0.21.0"
|
||||
version: "v0.17.0"
|
||||
image: "kindest/node:v${{ matrix.k8s }}"
|
||||
- name: Fetch built CLI
|
||||
id: cli-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./_output/bin/linux/amd64/velero
|
||||
key: velero-cli-${{ github.event.pull_request.number }}-${{ github.sha }}
|
||||
- name: Fetch built Image
|
||||
id: image-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ./velero.tar
|
||||
key: velero-image-${{ github.event.pull_request.number }}-${{ github.sha }}
|
||||
@@ -110,7 +100,7 @@ jobs:
|
||||
kind load image-archive velero.tar
|
||||
# always try to fetch the cached go modules as the e2e test needs it either
|
||||
- name: Fetch cached go modules
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
@@ -133,13 +123,12 @@ jobs:
|
||||
CREDS_FILE=/tmp/credential BSL_BUCKET=bucket \
|
||||
ADDITIONAL_OBJECT_STORE_PROVIDER=aws ADDITIONAL_BSL_CONFIG=region=minio,s3ForcePathStyle="true",s3Url=http://$(hostname -i):9000 \
|
||||
ADDITIONAL_CREDS_FILE=/tmp/credential ADDITIONAL_BSL_BUCKET=additional-bucket \
|
||||
GINKGO_FOCUS='${{ matrix.focus }}' VELERO_IMAGE=velero:pr-test \
|
||||
GINKGO_SKIP='SKIP_KIND|pv-backup|Restic|Snapshot|LongTime' \
|
||||
make -C test/ run-e2e
|
||||
GINKGO_FOCUS='Basic\]\[ClusterResource' VELERO_IMAGE=velero:pr-test \
|
||||
make -C test/e2e run
|
||||
timeout-minutes: 30
|
||||
- name: Upload debug bundle
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: DebugBundle
|
||||
path: /home/runner/work/velero/velero/test/e2e/debug-bundle*
|
||||
4
.github/workflows/nightly-trivy-scan.yml
vendored
4
.github/workflows/nightly-trivy-scan.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
@@ -31,6 +31,6 @@ jobs:
|
||||
output: 'trivy-results.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
2
.github/workflows/pr-changelog-check.yml
vendored
2
.github/workflows/pr-changelog-check.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Changelog check
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'kind/changelog-not-required') || contains(github.event.pull_request.labels.*.name, 'Design') || contains(github.event.pull_request.labels.*.name, 'Website') || contains(github.event.pull_request.labels.*.name, 'Documentation'))}}
|
||||
|
||||
10
.github/workflows/pr-ci-check.yml
vendored
10
.github/workflows/pr-ci-check.yml
vendored
@@ -8,14 +8,14 @@ jobs:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22.2'
|
||||
go-version: '1.21.6'
|
||||
id: go
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
- name: Fetch cached go modules
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Make ci
|
||||
run: make ci
|
||||
- name: Upload test coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: coverage.out
|
||||
|
||||
4
.github/workflows/pr-codespell.yml
vendored
4
.github/workflows/pr-codespell.yml
vendored
@@ -8,14 +8,14 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@master
|
||||
with:
|
||||
# ignore the config/.../crd.go file as it's generated binary data that is edited elswhere.
|
||||
skip: .git,*.png,*.jpg,*.woff,*.ttf,*.gif,*.ico,./config/crd/v1beta1/crds/crds.go,./config/crd/v1/crds/crds.go,./config/crd/v2alpha1/crds/crds.go,./go.sum,./LICENSE
|
||||
ignore_words_list: iam,aks,ist,bridget,ue,shouldnot,atleast,notin,sme
|
||||
ignore_words_list: iam,aks,ist,bridget,ue,shouldnot,atleast
|
||||
check_filenames: true
|
||||
check_hidden: true
|
||||
|
||||
|
||||
6
.github/workflows/pr-containers.yml
vendored
6
.github/workflows/pr-containers.yml
vendored
@@ -13,18 +13,18 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
name: Checkout
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
|
||||
2
.github/workflows/pr-goreleaser.yml
vendored
2
.github/workflows/pr-goreleaser.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
name: Checkout
|
||||
|
||||
- name: Verify .goreleaser.yml and try a dryrun release.
|
||||
|
||||
18
.github/workflows/pr-linter-check.yml
vendored
18
.github/workflows/pr-linter-check.yml
vendored
@@ -6,15 +6,9 @@ jobs:
|
||||
name: Run Linter Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
id: go
|
||||
- name: Linter check
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.57.2
|
||||
args: --verbose
|
||||
|
||||
- name: Check out the code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Linter check
|
||||
run: make lint
|
||||
|
||||
19
.github/workflows/prow-action.yml
vendored
19
.github/workflows/prow-action.yml
vendored
@@ -9,21 +9,12 @@ jobs:
|
||||
execute:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jpmcb/prow-github-actions@v1.1.3
|
||||
- uses: jpmcb/prow-github-actions@v1.1.2
|
||||
with:
|
||||
# Only support /kind command for now.
|
||||
# TODO: before allowing the /lgtm command, see if we can block merging if changelog labels are missing.
|
||||
prow-commands: |
|
||||
/approve
|
||||
/area
|
||||
/assign
|
||||
/cc
|
||||
/close
|
||||
/hold
|
||||
prow-commands: "/area
|
||||
/kind
|
||||
/milestone
|
||||
/retitle
|
||||
/remove
|
||||
/reopen
|
||||
/uncc
|
||||
/unassign
|
||||
/cc
|
||||
/uncc"
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
2
.github/workflows/push-builder.yml
vendored
2
.github/workflows/push-builder.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
# The default value is "1" which fetches only a single commit. If we merge PR without squash or rebase,
|
||||
# there are at least two commits: the first one is the merge commit and the second one is the real commit
|
||||
|
||||
29
.github/workflows/push.yml
vendored
29
.github/workflows/push.yml
vendored
@@ -16,39 +16,34 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22.2'
|
||||
go-version: '1.21.6'
|
||||
id: go
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Fix issue of setup-gcloud
|
||||
- run: |
|
||||
sudo apt-get install python2.7
|
||||
export CLOUDSDK_PYTHON="/usr/bin/python2"
|
||||
|
||||
- id: 'auth'
|
||||
uses: google-github-actions/auth@v2
|
||||
- uses: google-github-actions/setup-gcloud@v0
|
||||
with:
|
||||
credentials_json: '${{ secrets.GCS_SA_KEY }}'
|
||||
|
||||
- name: 'set up GCloud SDK'
|
||||
uses: google-github-actions/setup-gcloud@v2
|
||||
|
||||
- name: 'use gcloud CLI'
|
||||
run: |
|
||||
gcloud info
|
||||
version: '285.0.0'
|
||||
service_account_key: ${{ secrets.GCS_SA_KEY }}
|
||||
export_default_credentials: true
|
||||
- run: gcloud info
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
@@ -62,14 +57,14 @@ jobs:
|
||||
run: make test
|
||||
|
||||
- name: Upload test coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: coverage.out
|
||||
verbose: true
|
||||
|
||||
# Use the JSON key in secret to login gcr.io
|
||||
- uses: 'docker/login-action@v3'
|
||||
- uses: 'docker/login-action@v2'
|
||||
with:
|
||||
registry: 'gcr.io' # or REGION.docker.pkg.dev
|
||||
username: '_json_key'
|
||||
|
||||
4
.github/workflows/rebase.yml
vendored
4
.github/workflows/rebase.yml
vendored
@@ -9,10 +9,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.8
|
||||
uses: cirrus-actions/rebase@1.3.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/stale-issues.yml
vendored
2
.github/workflows/stale-issues.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9.0.0
|
||||
- uses: actions/stale@v6.0.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days. If a Velero team member has requested log or more information, please provide the output of the shared commands."
|
||||
|
||||
@@ -63,7 +63,7 @@ Okteto integrates Velero in [Okteto Cloud][94] and [Okteto Enterprise][95] to pe
|
||||
Replicated uses the Velero open source project to enable snapshots in [KOTS][101] to backup Kubernetes manifests & persistent volumes. In addition to the default functionality that Velero provides, [KOTS][101] provides a detailed interface in the [Admin Console][102] that can be used to manage the storage destination and schedule, and to perform and monitor the backup and restore process.<br>
|
||||
|
||||
**[CloudCasa][103]**<br>
|
||||
[Catalogic Software][104] integrates Velero with [CloudCasa][103] - A Smart Home in the Cloud for Backups. CloudCasa is a full-featured, scalable, cloud-native solution providing Kubernetes data protection, disaster recovery, and migration as a service. An option to manage existing Velero instances and an enterprise self-hosted option are also available.<br>
|
||||
[Catalogic Software][104] integrates Velero with [CloudCasa][103] - A Smart Home in the Cloud for Backups. CloudCasa is a simple, scalable, cloud-native solution providing data protection and disaster recovery as a service. This solution is built using Kubernetes for protecting Kubernetes clusters.<br>
|
||||
|
||||
**[Microsoft Azure][105]**<br>
|
||||
[Azure Backup for AKS][106] is an Azure native, Kubernetes aware, Enterprise ready backup for containerized applications deployed on Azure Kubernetes Service (AKS). AKS Backup utilizes Velero to perform backup and restore operations to protect stateful applications in AKS clusters.<br>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
## Current release:
|
||||
* [CHANGELOG-1.14.md][24]
|
||||
* [CHANGELOG-1.13.md][23]
|
||||
|
||||
## Older releases:
|
||||
* [CHANGELOG-1.13.md][23]
|
||||
* [CHANGELOG-1.12.md][22]
|
||||
* [CHANGELOG-1.11.md][21]
|
||||
* [CHANGELOG-1.10.md][20]
|
||||
@@ -27,7 +26,6 @@
|
||||
* [CHANGELOG-0.3.md][1]
|
||||
|
||||
|
||||
[24]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.14.md
|
||||
[23]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.13.md
|
||||
[22]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.12.md
|
||||
[21]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.11.md
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
We as members, contributors, and leaders pledge to make participation in the Velero project and our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socioeconomic status,
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
# Velero binary build section
|
||||
FROM --platform=$BUILDPLATFORM golang:1.22.2-bookworm as velero-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21.6-bookworm as velero-builder
|
||||
|
||||
ARG GOPROXY
|
||||
ARG BIN
|
||||
@@ -47,7 +47,7 @@ RUN mkdir -p /output/usr/bin && \
|
||||
go clean -modcache -cache
|
||||
|
||||
# Restic binary build section
|
||||
FROM --platform=$BUILDPLATFORM golang:1.22.2-bookworm as restic-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21.6-bookworm as restic-builder
|
||||
|
||||
ARG BIN
|
||||
ARG TARGETOS
|
||||
@@ -70,7 +70,7 @@ RUN mkdir -p /output/usr/bin && \
|
||||
go clean -modcache -cache
|
||||
|
||||
# Velero image packing section
|
||||
FROM paketobuildpacks/run-jammy-tiny:0.2.38
|
||||
FROM paketobuildpacks/run-jammy-tiny:0.2.19
|
||||
|
||||
LABEL maintainer="Xun Jiang <jxun@vmware.com>"
|
||||
|
||||
|
||||
17
Makefile
17
Makefile
@@ -74,18 +74,8 @@ else
|
||||
GCR_IMAGE_TAGS ?= $(GCR_IMAGE):$(VERSION)
|
||||
endif
|
||||
|
||||
# check buildx is enabled
|
||||
# macOS/Windows docker cli without Docker Desktop license: https://github.com/abiosoft/colima
|
||||
# To add buildx to docker cli: https://github.com/abiosoft/colima/discussions/273#discussioncomment-2684502
|
||||
ifeq ($(shell docker buildx inspect 2>/dev/null | awk '/Status/ { print $$2 }'), running)
|
||||
BUILDX_ENABLED ?= true
|
||||
# if emulated docker cli from podman, assume enabled
|
||||
# emulated docker cli from podman: https://podman-desktop.io/docs/migrating-from-docker/emulating-docker-cli-with-podman
|
||||
# podman known issues:
|
||||
# - on remote podman, such as on macOS,
|
||||
# --output issue: https://github.com/containers/podman/issues/15922
|
||||
else ifeq ($(shell cat $(shell which docker) | grep -c "exec podman"), 1)
|
||||
BUILDX_ENABLED ?= true
|
||||
else
|
||||
BUILDX_ENABLED ?= false
|
||||
endif
|
||||
@@ -118,7 +108,6 @@ platform_temp = $(subst -, ,$(ARCH))
|
||||
GOOS = $(word 1, $(platform_temp))
|
||||
GOARCH = $(word 2, $(platform_temp))
|
||||
GOPROXY ?= https://proxy.golang.org
|
||||
GOBIN=$$(pwd)/.go/bin
|
||||
|
||||
# If you want to build all binaries, see the 'all-build' rule.
|
||||
# If you want to build all containers, see the 'all-containers' rule.
|
||||
@@ -140,7 +129,6 @@ local: build-dirs
|
||||
# Add DEBUG=1 to enable debug locally
|
||||
GOOS=$(GOOS) \
|
||||
GOARCH=$(GOARCH) \
|
||||
GOBIN=$(GOBIN) \
|
||||
VERSION=$(VERSION) \
|
||||
REGISTRY=$(REGISTRY) \
|
||||
PKG=$(PKG) \
|
||||
@@ -157,7 +145,6 @@ _output/bin/$(GOOS)/$(GOARCH)/$(BIN): build-dirs
|
||||
$(MAKE) shell CMD="-c '\
|
||||
GOOS=$(GOOS) \
|
||||
GOARCH=$(GOARCH) \
|
||||
GOBIN=$(GOBIN) \
|
||||
VERSION=$(VERSION) \
|
||||
REGISTRY=$(REGISTRY) \
|
||||
PKG=$(PKG) \
|
||||
@@ -370,11 +357,11 @@ gen-docs:
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: local
|
||||
$(MAKE) -e VERSION=$(VERSION) -C test/ run-e2e
|
||||
$(MAKE) -e VERSION=$(VERSION) -C test/e2e run
|
||||
|
||||
.PHONY: test-perf
|
||||
test-perf: local
|
||||
$(MAKE) -e VERSION=$(VERSION) -C test/ run-perf
|
||||
$(MAKE) -e VERSION=$(VERSION) -C test/perf run
|
||||
|
||||
go-generate:
|
||||
go generate ./pkg/...
|
||||
24
OWNERS
24
OWNERS
@@ -1,24 +0,0 @@
|
||||
# This file is used by the [PROW action](https://github.com/jpmcb/prow-github-actions) to approve and merge PRs.
|
||||
# The file's format follows the [OWNERS SPEC](https://www.kubernetes.dev/docs/guide/owners/#owners-spec).
|
||||
|
||||
# List of usernames who may use /lgtm
|
||||
reviewers:
|
||||
- @Lyndon-Li
|
||||
- @anshulahuja98
|
||||
- @blackpiglet
|
||||
- @qiuming-best
|
||||
- @reasonerjt
|
||||
- @shubham-pampattiwar
|
||||
- @sseago
|
||||
- @ywk253100
|
||||
|
||||
# List of usernames who may use /approve
|
||||
approvers:
|
||||
- @Lyndon-Li
|
||||
- @anshulahuja98
|
||||
- @blackpiglet
|
||||
- @qiuming-best
|
||||
- @reasonerjt
|
||||
- @shubham-pampattiwar
|
||||
- @sseago
|
||||
- @ywk253100
|
||||
17
README.md
17
README.md
@@ -40,17 +40,18 @@ See [the list of releases][6] to find out about feature changes.
|
||||
|
||||
The following is a list of the supported Kubernetes versions for each Velero version.
|
||||
|
||||
| Velero version | Expected Kubernetes version compatibility | Tested on Kubernetes version |
|
||||
|----------------|-------------------------------------------|-------------------------------------|
|
||||
| 1.14 | 1.18-latest | 1.27.9, 1.28.9, and 1.29.4 |
|
||||
| 1.13 | 1.18-latest | 1.26.5, 1.27.3, 1.27.8, and 1.28.3 |
|
||||
| 1.12 | 1.18-latest | 1.25.7, 1.26.5, 1.26.7, and 1.27.3 |
|
||||
| 1.11 | 1.18-latest | 1.23.10, 1.24.9, 1.25.5, and 1.26.1 |
|
||||
| 1.10 | 1.18-latest | 1.22.5, 1.23.8, 1.24.6 and 1.25.1 |
|
||||
| Velero version | Expected Kubernetes version compatibility | Tested on Kubernetes version |
|
||||
|----------------|-------------------------------------------|----------------------------------------|
|
||||
| 1.13 | 1.18-latest | 1.26.5, 1.27.3, 1.27.8, and 1.28.3 |
|
||||
| 1.12 | 1.18-latest | 1.25.7, 1.26.5, 1.26.7, and 1.27.3 |
|
||||
| 1.11 | 1.18-latest | 1.23.10, 1.24.9, 1.25.5, and 1.26.1 |
|
||||
| 1.10 | 1.18-latest | 1.22.5, 1.23.8, 1.24.6 and 1.25.1 |
|
||||
| 1.9 | 1.18-latest | 1.20.5, 1.21.2, 1.22.5, 1.23, and 1.24 |
|
||||
| 1.8 | 1.18-latest | |
|
||||
|
||||
Velero supports IPv4, IPv6, and dual stack environments. Support for this was tested against Velero v1.8.
|
||||
|
||||
The Velero maintainers are continuously working to expand testing coverage, but are not able to test every combination of Velero and supported Kubernetes versions for each Velero release. The table above is meant to track the current testing coverage and the expected supported Kubernetes versions for each Velero version.
|
||||
The Velero maintainers are continuously working to expand testing coverage, but are not able to test every combination of Velero and supported Kubernetes versions for each Velero release. The table above is meant to track the current testing coverage and the expected supported Kubernetes versions for each Velero version. If you have a question about test coverage before v1.9, please reach out in the [#velero-users](https://kubernetes.slack.com/archives/C6VCGP4MT) Slack channel.
|
||||
|
||||
If you are interested in using a different version of Kubernetes with a given Velero version, we'd recommend that you perform testing before installing or upgrading your environment. For full information around capabilities within a release, also see the Velero [release notes](https://github.com/vmware-tanzu/velero/releases) or Kubernetes [release notes](https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG). See the Velero [support page](https://velero.io/docs/latest/support-process/) for information about supported versions of Velero.
|
||||
|
||||
|
||||
2
Tiltfile
2
Tiltfile
@@ -52,7 +52,7 @@ git_sha = str(local("git rev-parse HEAD", quiet = True, echo_off = True)).strip(
|
||||
|
||||
tilt_helper_dockerfile_header = """
|
||||
# Tilt image
|
||||
FROM golang:1.22.2 as tilt-helper
|
||||
FROM golang:1.21.6 as tilt-helper
|
||||
|
||||
# Support live reloading with Tilt
|
||||
RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh && \
|
||||
|
||||
@@ -72,6 +72,7 @@ To fix CVEs and keep pace with Golang, Velero made changes as follows:
|
||||
* After the backup VolumeInfo metadata file is added to the backup, Velero decides how to restore the PV resource according to the VolumeInfo content. To support the backup generated by the older version of Velero, the old logic is also kept. The support for the backup without the VolumeInfo metadata file will be kept for two releases. The support logic will be deleted in the v1.15 release.
|
||||
|
||||
### All Changes
|
||||
* Check resource Group Version and Kind is available in cluster before attempting restore to prevent being stuck (#7336, @kaovilai)
|
||||
* Make "disable-informer-cache" option false(enabled) by default to keep it consistent with the help message (#7294, @ywk253100)
|
||||
* Fix issue #6928, remove snapshot deletion timeout for PVB (#7282, @Lyndon-Li)
|
||||
* Do not set "targetNamespace" to namespace items (#7274, @reasonerjt)
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
## v1.14
|
||||
|
||||
### Download
|
||||
https://github.com/vmware-tanzu/velero/releases/tag/v1.14.0
|
||||
|
||||
### Container Image
|
||||
`velero/velero:v1.14.0`
|
||||
|
||||
### Documentation
|
||||
https://velero.io/docs/v1.14/
|
||||
|
||||
### Upgrading
|
||||
https://velero.io/docs/v1.14/upgrade-to-1.14/
|
||||
|
||||
### Highlights
|
||||
|
||||
#### The maintenance work for kopia/restic backup repositories is run in jobs
|
||||
Since velero started using kopia as the approach for filesystem-level backup/restore, we've noticed an issue when velero connects to the kopia backup repositories and performs maintenance, it sometimes consumes excessive memory that can cause the velero pod to get OOM Killed. To mitigate this issue, the maintenance work will be moved out of velero pod to a separate kubernetes job, and the user will be able to specify the resource request in "velero install".
|
||||
#### Volume Policies are extended to support more actions to handle volumes
|
||||
In an earlier release, a flexible volume policy was introduced to skip certain volumes from a backup. In v1.14 we've made enhancement to this policy to allow the user to set how the volumes should be backed up. The user will be able to set "fs-backup" or "snapshot" as value of “action" in the policy and velero will backup the volumes accordingly. This enhancement allows the user to achieve a fine-grained control like "opt-in/out" without having to update the target workload. For more details please refer to https://velero.io/docs/v1.14/resource-filtering/#supported-volumepolicy-actions
|
||||
#### Node Selection for Data Movement Backup
|
||||
In velero the data movement flow relies on datamover pods, and these pods may take substantial resources and keep running for a long time. In v1.14, the user will be able to create a configmap to define the eligible nodes on which the datamover pods are launched. For more details refer to https://velero.io/docs/v1.14/data-movement-backup-node-selection/
|
||||
#### VolumeInfo metadata for restored volumes
|
||||
In v1.13, we introduced volumeinfo metadata for backup to help velero CLI and downstream adopter understand how velero handles each volume during backup. In v1.14, similar metadata will be persisted for each restore. velero CLI is also updated to bring more info in the output of "velero restore describe".
|
||||
#### "Finalizing" phase is introduced to restores
|
||||
The "Finalizing" phase is added to the state transition flow to restore, which helps us fix several issues: The labels added to PVs will be restored after the data in the PV is restored via volumesnapshotter. The post restore hook will be executed after datamovement is finished.
|
||||
#### Certificate-based authentication support for Azure
|
||||
Besides the service principal with secret(password)-based authentication, Velero introduces the new support for service principal with certificate-based authentication in v1.14.0. This approach enables you to adopt a phishing resistant authentication by using conditional access policies, which better protects Azure resources and is the recommended way by Azure.
|
||||
|
||||
### Runtime and dependencies
|
||||
* Golang runtime: v1.22.2
|
||||
* kopia: v0.17.0
|
||||
|
||||
### Limitations/Known issues
|
||||
* For the external BackupItemAction plugins that take snapshots for PVs, such as vsphere plugin. If the plugin checks the value of the field "snapshotVolumes" in the backup spec as a criteria for snapshot, the settings in the volume policy will not take effect. For example, if the "snapshotVolumes" is set to False in the backup spec, but a volume meets the condition in the volume policy for "snapshot" action, because the plugin will not check the settings in the volume policy, the plugin will not take snapshot for the volume. For more details please refer to #7818
|
||||
|
||||
### Breaking changes
|
||||
* CSI plugin has been merged into velero repo in v1.14 release. It will be installed by default as an internal plugin, and should not be installed via "–plugins " parameter in "velero install" command.
|
||||
* The default resource requests and limitations for node agent are removed in v1.14, to make the node agent pods have the QoS class of "BestEffort", more details please refer to #7391
|
||||
* There's a change in namespace filtering behavior during backup: In v1.14, when the includedNamespaces/excludedNamespaces fields are not set and the labelSelector/OrLabelSelectors are set in the backup spec, the backup will only include the namespaces which contain the resources that match the label selectors, while in previous releases all namespaces will be included in the backup with such settings. More details refer to #7105
|
||||
* Patching the PV in the "Finalizing" state may cause the restore to be in "PartiallyFailed" state when the PV is blocked in "Pending" state, while in the previous release the restore may end up being in "Complete" state. For more details refer to #7866
|
||||
|
||||
### All Changes
|
||||
* Fix backup log to show error string, not index (#7805, @piny940)
|
||||
* Modify the volume helper logic. (#7794, @blackpiglet)
|
||||
* Add documentation for extension of volume policy feature (#7779, @shubham-pampattiwar)
|
||||
* Surface errors when waiting for backupRepository and timeout occurs (#7762, @kaovilai)
|
||||
* Add existingResourcePolicy restore CR validation to controller (#7757, @kaovilai)
|
||||
* Fix condition matching in resource modifier when there are multiple rules (#7715, @27149chen)
|
||||
* Bump up the version of KinD and k8s in github actions (#7702, @reasonerjt)
|
||||
* Implementation for Extending VolumePolicies to support more actions (#7664, @shubham-pampattiwar)
|
||||
* Migrate from `github.com/Azure/azure-storage-blob-go` to `github.com/Azure/azure-sdk-for-go/sdk/storage/azblob` (#7598, @mmorel-35)
|
||||
* When Included/ExcludedNamespaces are omitted, and LabelSelector or OrLabelSelector is used, namespaces without selected items are excluded from backup. (#7697, @blackpiglet)
|
||||
* Display CSI snapshot restores in restore describe (#7687, @reasonerjt)
|
||||
* Use specific credential rather than the credential chain for Azure (#7680, @ywk253100)
|
||||
* Modify hook docs for clarity on displaying hook execution results (#7679, @allenxu404)
|
||||
* Wait for results of restore exec hook executions in Finalizing phase instead of InProgress phase (#7619, @allenxu404)
|
||||
* migrating to `sdk/resourcemanager/**/arm**` from `services/**/mgmt/**` (#7596, @mmorel-35)
|
||||
* Bump up to go1.22 (#7666, @reasonerjt)
|
||||
* Fix issue #7648. Adjust the exposing logic to avoid exposing failure and snapshot leak when expose fails (#7662, @Lyndon-Li)
|
||||
* Track and persist restore volume info (#7630, @reasonerjt)
|
||||
* Check the existence of the namespaces provided in the "--include-namespaces" option (#7569, @ywk253100)
|
||||
* Add the finalization phase to the restore workflow (#7377, @allenxu404)
|
||||
* Upgrade the version of go plugin related libs/tools (#7373, @ywk253100)
|
||||
* Check resource Group Version and Kind is available in cluster before attempting restore to prevent being stuck. (#7322, @kaovilai)
|
||||
* Merge CSI plugin code into Velero. (#7609, @blackpiglet)
|
||||
* Fix issue #7391, remove the default constraint for node-agent pods (#7488, @Lyndon-Li)
|
||||
* Fix DataDownload fails during restore for empty PVC workload (#7521, @qiuming-best)
|
||||
* Add repository maintenance job (#7451, @qiuming-best)
|
||||
* Check whether the VolumeSnapshot's source PVC is nil before using it.
|
||||
Skip populate VolumeInfo for data-moved PV when CSI is not enabled. (#7515, @blackpiglet)
|
||||
* Fix issue #7308, change the data path requeue time to 5 second for data mover backup/restore, PVB and PVR. (#7458, @Lyndon-Li)
|
||||
* Patch newly dynamically provisioned PV with volume info to restore custom setting of PV (#7504, @allenxu404)
|
||||
* Adjust the logic for the backup_last_status metrics to stop incorrectly incrementing over time (#7445, @allenxu404)
|
||||
* dependabot: support github-actions updates (#7594, @mmorel-35)
|
||||
* Include the design for adding the finalization phase to the restore workflow (#7317, @allenxu404)
|
||||
* Fix issue #7211. Enable advanced feature capability and add support to concatenate objects for unified repo. (#7452, @Lyndon-Li)
|
||||
* Add design to introduce restore volume info (#7610, @reasonerjt)
|
||||
* Increase the k8s client QPS/burst to avoid throttling request errors (#7311, @ywk253100)
|
||||
* Support update the backup VolumeInfos by the Async ops result. (#7554, @blackpiglet)
|
||||
* FS backup create PodVolumeBackup when the backup excluded PVC,
|
||||
so I added logic to skip PVC volume type when PVC is not included in the backup resources to be backed up. (#7472, @sbahar619)
|
||||
* Respect and use `credentialsFile` specified in BSL.spec.config when IRSA is configured over Velero Pod Environment credentials (#7374, @reasonerjt)
|
||||
* Move the native snapshot definition code into internal directory (#7544, @blackpiglet)
|
||||
* Fix issue #7036. Add the implementation of node selection for data mover backups (#7437, @Lyndon-Li)
|
||||
* Fix issue #7535, add the MustHave resource check during item collection and item filter for restore (#7585, @Lyndon-Li)
|
||||
* build(deps): bump json-patch to v5.8.0 (#7584, @mmorel-35)
|
||||
* Add confirm flag to velero plugin add (#7566, @kaovilai)
|
||||
* do not skip unknown gvr at the beginning and get new gr when kind is changed (#7523, @27149chen)
|
||||
* Fix snapshot leak for backup (#7558, @qiuming-best)
|
||||
* For issue #7036, add the document for data mover node selection (#7640, @Lyndon-Li)
|
||||
* Add design for Extending VolumePolicies to support more actions (#6956, @shubham-pampattiwar)
|
||||
* BackupRepositories associated with a BSL are invalidated when BSL is (re-)created. (#7380, @kaovilai)
|
||||
* Improve the concurrency for PVBs in different pods (#7571, @ywk253100)
|
||||
* Bump up Kopia to v0.16.0 and open kopia repo with no index change (#7559, @Lyndon-Li)
|
||||
* Bump up the versions of several Kubernetes-related libs (#7489, @ywk253100)
|
||||
* Make parallel restore configurable (#7512, @qiuming-best)
|
||||
* Support certificate-based authentication for Azure (#7549, @ywk253100)
|
||||
* Fix issue #7281, batch delete snapshots in the same repo (#7438, @Lyndon-Li)
|
||||
* Add CRD name to error message when it is not ready to use (#7295, @josemarevalo)
|
||||
* Add the design for node selection for data mover backup (#7383, @Lyndon-Li)
|
||||
* Bump up aws-sdk to latest version to leverage Pod Identity credentials. (#7307, @guikcd)
|
||||
* Fix issue #7246. Document the behavior for repo snapshot deletion (#7622, @Lyndon-Li)
|
||||
* Fix issue #7583, set backupName optional for Restore CRD (#7617, @Lyndon-Li)
|
||||
|
||||
@@ -66,14 +66,14 @@ func done() bool {
|
||||
doneFile := filepath.Join("/restores", child.Name(), ".velero", os.Args[1])
|
||||
|
||||
if _, err := os.Stat(doneFile); os.IsNotExist(err) {
|
||||
fmt.Printf("The filesystem restore done file %s is not found yet. Retry later.\n", doneFile)
|
||||
fmt.Printf("Not found: %s\n", doneFile)
|
||||
return false
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR looking filesystem restore done file %s: %s\n", doneFile, err)
|
||||
fmt.Fprintf(os.Stderr, "ERROR looking for %s: %s\n", doneFile, err)
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Printf("Found the done file %s\n", doneFile)
|
||||
fmt.Printf("Found %s", doneFile)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: backuprepositories.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -26,19 +26,14 @@ spec:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -46,8 +41,7 @@ spec:
|
||||
description: BackupRepositorySpec is the specification for a BackupRepository.
|
||||
properties:
|
||||
backupStorageLocation:
|
||||
description: |-
|
||||
BackupStorageLocation is the name of the BackupStorageLocation
|
||||
description: BackupStorageLocation is the name of the BackupStorageLocation
|
||||
that should contain this repository.
|
||||
type: string
|
||||
maintenanceFrequency:
|
||||
@@ -62,14 +56,12 @@ spec:
|
||||
- ""
|
||||
type: string
|
||||
resticIdentifier:
|
||||
description: |-
|
||||
ResticIdentifier is the full restic-compatible string for identifying
|
||||
this repository.
|
||||
description: ResticIdentifier is the full restic-compatible string
|
||||
for identifying this repository.
|
||||
type: string
|
||||
volumeNamespace:
|
||||
description: |-
|
||||
VolumeNamespace is the namespace this backup repository contains
|
||||
pod volume backups for.
|
||||
description: VolumeNamespace is the namespace this backup repository
|
||||
contains pod volume backups for.
|
||||
type: string
|
||||
required:
|
||||
- backupStorageLocation
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: backups.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -17,24 +17,18 @@ spec:
|
||||
- name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
Backup is a Velero resource that represents the capture of Kubernetes
|
||||
description: Backup is a Velero resource that represents the capture of Kubernetes
|
||||
cluster state at a point in time (API objects and associated volume state).
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -42,63 +36,55 @@ spec:
|
||||
description: BackupSpec defines the specification for a Velero backup.
|
||||
properties:
|
||||
csiSnapshotTimeout:
|
||||
description: |-
|
||||
CSISnapshotTimeout specifies the time used to wait for CSI VolumeSnapshot status turns to
|
||||
ReadyToUse during creation, before returning error as timeout.
|
||||
The default value is 10 minute.
|
||||
description: CSISnapshotTimeout specifies the time used to wait for
|
||||
CSI VolumeSnapshot status turns to ReadyToUse during creation, before
|
||||
returning error as timeout. The default value is 10 minute.
|
||||
type: string
|
||||
datamover:
|
||||
description: |-
|
||||
DataMover specifies the data mover to be used by the backup.
|
||||
If DataMover is "" or "velero", the built-in data mover will be used.
|
||||
description: DataMover specifies the data mover to be used by the
|
||||
backup. If DataMover is "" or "velero", the built-in data mover
|
||||
will be used.
|
||||
type: string
|
||||
defaultVolumesToFsBackup:
|
||||
description: |-
|
||||
DefaultVolumesToFsBackup specifies whether pod volume file system backup should be used
|
||||
for all volumes by default.
|
||||
description: DefaultVolumesToFsBackup specifies whether pod volume
|
||||
file system backup should be used for all volumes by default.
|
||||
nullable: true
|
||||
type: boolean
|
||||
defaultVolumesToRestic:
|
||||
description: |-
|
||||
DefaultVolumesToRestic specifies whether restic should be used to take a
|
||||
backup of all pod volumes by default.
|
||||
|
||||
|
||||
Deprecated: this field is no longer used and will be removed entirely in future. Use DefaultVolumesToFsBackup instead.
|
||||
description: "DefaultVolumesToRestic specifies whether restic should
|
||||
be used to take a backup of all pod volumes by default. \n Deprecated:
|
||||
this field is no longer used and will be removed entirely in future.
|
||||
Use DefaultVolumesToFsBackup instead."
|
||||
nullable: true
|
||||
type: boolean
|
||||
excludedClusterScopedResources:
|
||||
description: |-
|
||||
ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to exclude from the backup.
|
||||
If set to "*", all cluster-scoped resource types are excluded.
|
||||
The default value is empty.
|
||||
description: ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to exclude from the backup. If set to "*", all
|
||||
cluster-scoped resource types are excluded. The default value is
|
||||
empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaceScopedResources:
|
||||
description: |-
|
||||
ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to exclude from the backup.
|
||||
If set to "*", all namespace-scoped resource types are excluded.
|
||||
The default value is empty.
|
||||
description: ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to exclude from the backup. If set to "*", all
|
||||
namespace-scoped resource types are excluded. The default value
|
||||
is empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaces:
|
||||
description: |-
|
||||
ExcludedNamespaces contains a list of namespaces that are not
|
||||
included in the backup.
|
||||
description: ExcludedNamespaces contains a list of namespaces that
|
||||
are not included in the backup.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedResources:
|
||||
description: |-
|
||||
ExcludedResources is a slice of resource names that are not
|
||||
included in the backup.
|
||||
description: ExcludedResources is a slice of resource names that are
|
||||
not included in the backup.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -111,9 +97,9 @@ spec:
|
||||
description: Resources are hooks that should be executed when
|
||||
backing up individual instances of a resource.
|
||||
items:
|
||||
description: |-
|
||||
BackupResourceHookSpec defines one or more BackupResourceHooks that should be executed based on
|
||||
the rules defined for namespaces, resources, and label selector.
|
||||
description: BackupResourceHookSpec defines one or more BackupResourceHooks
|
||||
that should be executed based on the rules defined for namespaces,
|
||||
resources, and label selector.
|
||||
properties:
|
||||
excludedNamespaces:
|
||||
description: ExcludedNamespaces specifies the namespaces
|
||||
@@ -130,17 +116,17 @@ spec:
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaces:
|
||||
description: |-
|
||||
IncludedNamespaces specifies the namespaces to which this hook spec applies. If empty, it applies
|
||||
description: IncludedNamespaces specifies the namespaces
|
||||
to which this hook spec applies. If empty, it applies
|
||||
to all namespaces.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: |-
|
||||
IncludedResources specifies the resources to which this hook spec applies. If empty, it applies
|
||||
to all resources.
|
||||
description: IncludedResources specifies the resources to
|
||||
which this hook spec applies. If empty, it applies to
|
||||
all resources.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -154,8 +140,8 @@ spec:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
description: A label selector requirement is a selector
|
||||
that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
@@ -163,16 +149,17 @@ spec:
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In,
|
||||
NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
description: values is an array of string values.
|
||||
If the operator is In or NotIn, the values array
|
||||
must be non-empty. If the operator is Exists
|
||||
or DoesNotExist, the values array must be empty.
|
||||
This array is replaced during a strategic merge
|
||||
patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -184,10 +171,11 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs.
|
||||
A single {key,value} in the matchLabels map is equivalent
|
||||
to an element of matchExpressions, whose key field
|
||||
is "key", the operator is "In", and the values array
|
||||
contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -195,9 +183,10 @@ spec:
|
||||
description: Name is the name of this hook.
|
||||
type: string
|
||||
post:
|
||||
description: |-
|
||||
PostHooks is a list of BackupResourceHooks to execute after storing the item in the backup.
|
||||
These are executed after all "additional items" from item actions are processed.
|
||||
description: PostHooks is a list of BackupResourceHooks
|
||||
to execute after storing the item in the backup. These
|
||||
are executed after all "additional items" from item actions
|
||||
are processed.
|
||||
items:
|
||||
description: BackupResourceHook defines a hook for a resource.
|
||||
properties:
|
||||
@@ -212,9 +201,10 @@ spec:
|
||||
minItems: 1
|
||||
type: array
|
||||
container:
|
||||
description: |-
|
||||
Container is the container in the pod where the command should be executed. If not specified,
|
||||
the pod's first container is used.
|
||||
description: Container is the container in the
|
||||
pod where the command should be executed. If
|
||||
not specified, the pod's first container is
|
||||
used.
|
||||
type: string
|
||||
onError:
|
||||
description: OnError specifies how Velero should
|
||||
@@ -225,9 +215,9 @@ spec:
|
||||
- Fail
|
||||
type: string
|
||||
timeout:
|
||||
description: |-
|
||||
Timeout defines the maximum amount of time Velero should wait for the hook to complete before
|
||||
considering the execution a failure.
|
||||
description: Timeout defines the maximum amount
|
||||
of time Velero should wait for the hook to complete
|
||||
before considering the execution a failure.
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
@@ -237,9 +227,10 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
pre:
|
||||
description: |-
|
||||
PreHooks is a list of BackupResourceHooks to execute prior to storing the item in the backup.
|
||||
These are executed before any "additional items" from item actions are processed.
|
||||
description: PreHooks is a list of BackupResourceHooks to
|
||||
execute prior to storing the item in the backup. These
|
||||
are executed before any "additional items" from item actions
|
||||
are processed.
|
||||
items:
|
||||
description: BackupResourceHook defines a hook for a resource.
|
||||
properties:
|
||||
@@ -254,9 +245,10 @@ spec:
|
||||
minItems: 1
|
||||
type: array
|
||||
container:
|
||||
description: |-
|
||||
Container is the container in the pod where the command should be executed. If not specified,
|
||||
the pod's first container is used.
|
||||
description: Container is the container in the
|
||||
pod where the command should be executed. If
|
||||
not specified, the pod's first container is
|
||||
used.
|
||||
type: string
|
||||
onError:
|
||||
description: OnError specifies how Velero should
|
||||
@@ -267,9 +259,9 @@ spec:
|
||||
- Fail
|
||||
type: string
|
||||
timeout:
|
||||
description: |-
|
||||
Timeout defines the maximum amount of time Velero should wait for the hook to complete before
|
||||
considering the execution a failure.
|
||||
description: Timeout defines the maximum amount
|
||||
of time Velero should wait for the hook to complete
|
||||
before considering the execution a failure.
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
@@ -285,81 +277,74 @@ spec:
|
||||
type: array
|
||||
type: object
|
||||
includeClusterResources:
|
||||
description: |-
|
||||
IncludeClusterResources specifies whether cluster-scoped resources
|
||||
should be included for consideration in the backup.
|
||||
description: IncludeClusterResources specifies whether cluster-scoped
|
||||
resources should be included for consideration in the backup.
|
||||
nullable: true
|
||||
type: boolean
|
||||
includedClusterScopedResources:
|
||||
description: |-
|
||||
IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to include in the backup.
|
||||
If set to "*", all cluster-scoped resource types are included.
|
||||
The default value is empty, which means only related
|
||||
cluster-scoped resources are included.
|
||||
description: IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to include in the backup. If set to "*", all
|
||||
cluster-scoped resource types are included. The default value is
|
||||
empty, which means only related cluster-scoped resources are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaceScopedResources:
|
||||
description: |-
|
||||
IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to include in the backup.
|
||||
The default value is "*".
|
||||
description: IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to include in the backup. The default value
|
||||
is "*".
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaces:
|
||||
description: |-
|
||||
IncludedNamespaces is a slice of namespace names to include objects
|
||||
from. If empty, all namespaces are included.
|
||||
description: IncludedNamespaces is a slice of namespace names to include
|
||||
objects from. If empty, all namespaces are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: |-
|
||||
IncludedResources is a slice of resource names to include
|
||||
description: IncludedResources is a slice of resource names to include
|
||||
in the backup. If empty, all resources are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
itemOperationTimeout:
|
||||
description: |-
|
||||
ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations
|
||||
The default value is 4 hour.
|
||||
description: ItemOperationTimeout specifies the time used to wait
|
||||
for asynchronous BackupItemAction operations The default value is
|
||||
1 hour.
|
||||
type: string
|
||||
labelSelector:
|
||||
description: |-
|
||||
LabelSelector is a metav1.LabelSelector to filter with
|
||||
when adding individual objects to the backup. If empty
|
||||
or nil, all objects are included. Optional.
|
||||
description: LabelSelector is a metav1.LabelSelector to filter with
|
||||
when adding individual objects to the backup. If empty or nil, all
|
||||
objects are included. Optional.
|
||||
nullable: true
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
description: A label selector requirement is a selector that
|
||||
contains values, a key, and an operator that relates the key
|
||||
and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship to
|
||||
a set of values. Valid operators are In, NotIn, Exists
|
||||
and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
description: values is an array of string values. If the
|
||||
operator is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
@@ -372,10 +357,11 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -387,41 +373,40 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
orLabelSelectors:
|
||||
description: |-
|
||||
OrLabelSelectors is list of metav1.LabelSelector to filter with
|
||||
when adding individual objects to the backup. If multiple provided
|
||||
description: OrLabelSelectors is list of metav1.LabelSelector to filter
|
||||
with when adding individual objects to the backup. If multiple provided
|
||||
they will be joined by the OR operator. LabelSelector as well as
|
||||
OrLabelSelectors cannot co-exist in backup request, only one of them
|
||||
can be used.
|
||||
OrLabelSelectors cannot co-exist in backup request, only one of
|
||||
them can be used.
|
||||
items:
|
||||
description: |-
|
||||
A label selector is a label query over a set of resources. The result of matchLabels and
|
||||
matchExpressions are ANDed. An empty label selector matches all objects. A null
|
||||
label selector matches no objects.
|
||||
description: A label selector is a label query over a set of resources.
|
||||
The result of matchLabels and matchExpressions are ANDed. An empty
|
||||
label selector matches all objects. A null label selector matches
|
||||
no objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
description: A label selector requirement is a selector that
|
||||
contains values, a key, and an operator that relates the
|
||||
key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn, Exists
|
||||
and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
description: values is an array of string values. If the
|
||||
operator is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during a
|
||||
strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -433,10 +418,11 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -445,10 +431,11 @@ spec:
|
||||
orderedResources:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
OrderedResources specifies the backup order of resources of specific Kind.
|
||||
The map key is the resource name and value is a list of object names separated by commas.
|
||||
Each resource name has format "namespace/objectname". For cluster resources, simply use "objectname".
|
||||
description: OrderedResources specifies the backup order of resources
|
||||
of specific Kind. The map key is the resource name and value is
|
||||
a list of object names separated by commas. Each resource name has
|
||||
format "namespace/objectname". For cluster resources, simply use
|
||||
"objectname".
|
||||
nullable: true
|
||||
type: object
|
||||
resourcePolicy:
|
||||
@@ -456,10 +443,10 @@ spec:
|
||||
that backup should follow
|
||||
properties:
|
||||
apiGroup:
|
||||
description: |-
|
||||
APIGroup is the group for the resource being referenced.
|
||||
If APIGroup is not specified, the specified Kind must be in the core API group.
|
||||
For any other third-party types, APIGroup is required.
|
||||
description: APIGroup is the group for the resource being referenced.
|
||||
If APIGroup is not specified, the specified Kind must be in
|
||||
the core API group. For any other third-party types, APIGroup
|
||||
is required.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind is the type of resource being referenced
|
||||
@@ -478,10 +465,8 @@ spec:
|
||||
nullable: true
|
||||
type: boolean
|
||||
snapshotVolumes:
|
||||
description: |-
|
||||
SnapshotVolumes specifies whether to take snapshots
|
||||
of any PV's referenced in the set of objects included
|
||||
in the Backup.
|
||||
description: SnapshotVolumes specifies whether to take snapshots of
|
||||
any PV's referenced in the set of objects included in the Backup.
|
||||
nullable: true
|
||||
type: boolean
|
||||
storageLocation:
|
||||
@@ -489,9 +474,8 @@ spec:
|
||||
BackupStorageLocation where the backup should be stored.
|
||||
type: string
|
||||
ttl:
|
||||
description: |-
|
||||
TTL is a time.Duration-parseable string describing how long
|
||||
the Backup should be retained for.
|
||||
description: TTL is a time.Duration-parseable string describing how
|
||||
long the Backup should be retained for.
|
||||
type: string
|
||||
uploaderConfig:
|
||||
description: UploaderConfig specifies the configuration for the uploader.
|
||||
@@ -513,44 +497,39 @@ spec:
|
||||
description: BackupStatus captures the current status of a Velero backup.
|
||||
properties:
|
||||
backupItemOperationsAttempted:
|
||||
description: |-
|
||||
BackupItemOperationsAttempted is the total number of attempted
|
||||
async BackupItemAction operations for this backup.
|
||||
description: BackupItemOperationsAttempted is the total number of
|
||||
attempted async BackupItemAction operations for this backup.
|
||||
type: integer
|
||||
backupItemOperationsCompleted:
|
||||
description: |-
|
||||
BackupItemOperationsCompleted is the total number of successfully completed
|
||||
async BackupItemAction operations for this backup.
|
||||
description: BackupItemOperationsCompleted is the total number of
|
||||
successfully completed async BackupItemAction operations for this
|
||||
backup.
|
||||
type: integer
|
||||
backupItemOperationsFailed:
|
||||
description: |-
|
||||
BackupItemOperationsFailed is the total number of async
|
||||
BackupItemAction operations for this backup which ended with an error.
|
||||
description: BackupItemOperationsFailed is the total number of async
|
||||
BackupItemAction operations for this backup which ended with an
|
||||
error.
|
||||
type: integer
|
||||
completionTimestamp:
|
||||
description: |-
|
||||
CompletionTimestamp records the time a backup was completed.
|
||||
Completion time is recorded even on failed backups.
|
||||
Completion time is recorded before uploading the backup object.
|
||||
The server's time is used for CompletionTimestamps
|
||||
description: CompletionTimestamp records the time a backup was completed.
|
||||
Completion time is recorded even on failed backups. Completion time
|
||||
is recorded before uploading the backup object. The server's time
|
||||
is used for CompletionTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
csiVolumeSnapshotsAttempted:
|
||||
description: |-
|
||||
CSIVolumeSnapshotsAttempted is the total number of attempted
|
||||
description: CSIVolumeSnapshotsAttempted is the total number of attempted
|
||||
CSI VolumeSnapshots for this backup.
|
||||
type: integer
|
||||
csiVolumeSnapshotsCompleted:
|
||||
description: |-
|
||||
CSIVolumeSnapshotsCompleted is the total number of successfully
|
||||
description: CSIVolumeSnapshotsCompleted is the total number of successfully
|
||||
completed CSI VolumeSnapshots for this backup.
|
||||
type: integer
|
||||
errors:
|
||||
description: |-
|
||||
Errors is a count of all error messages that were generated during
|
||||
execution of the backup. The actual errors are in the backup's log
|
||||
file in object storage.
|
||||
description: Errors is a count of all error messages that were generated
|
||||
during execution of the backup. The actual errors are in the backup's
|
||||
log file in object storage.
|
||||
type: integer
|
||||
expiration:
|
||||
description: Expiration is when this Backup is eligible for garbage-collection.
|
||||
@@ -571,10 +550,10 @@ spec:
|
||||
nullable: true
|
||||
properties:
|
||||
hooksAttempted:
|
||||
description: |-
|
||||
HooksAttempted is the total number of attempted hooks
|
||||
Specifically, HooksAttempted represents the number of hooks that failed to execute
|
||||
and the number of hooks that executed successfully.
|
||||
description: HooksAttempted is the total number of attempted hooks
|
||||
Specifically, HooksAttempted represents the number of hooks
|
||||
that failed to execute and the number of hooks that executed
|
||||
successfully.
|
||||
type: integer
|
||||
hooksFailed:
|
||||
description: HooksFailed is the total number of hooks which ended
|
||||
@@ -597,62 +576,53 @@ spec:
|
||||
- Deleting
|
||||
type: string
|
||||
progress:
|
||||
description: |-
|
||||
Progress contains information about the backup's execution progress. Note
|
||||
that this information is best-effort only -- if Velero fails to update it
|
||||
during a backup for any reason, it may be inaccurate/stale.
|
||||
description: Progress contains information about the backup's execution
|
||||
progress. Note that this information is best-effort only -- if Velero
|
||||
fails to update it during a backup for any reason, it may be inaccurate/stale.
|
||||
nullable: true
|
||||
properties:
|
||||
itemsBackedUp:
|
||||
description: |-
|
||||
ItemsBackedUp is the number of items that have actually been written to the
|
||||
backup tarball so far.
|
||||
description: ItemsBackedUp is the number of items that have actually
|
||||
been written to the backup tarball so far.
|
||||
type: integer
|
||||
totalItems:
|
||||
description: |-
|
||||
TotalItems is the total number of items to be backed up. This number may change
|
||||
throughout the execution of the backup due to plugins that return additional related
|
||||
items to back up, the velero.io/exclude-from-backup label, and various other
|
||||
description: TotalItems is the total number of items to be backed
|
||||
up. This number may change throughout the execution of the backup
|
||||
due to plugins that return additional related items to back
|
||||
up, the velero.io/exclude-from-backup label, and various other
|
||||
filters that happen as items are processed.
|
||||
type: integer
|
||||
type: object
|
||||
startTimestamp:
|
||||
description: |-
|
||||
StartTimestamp records the time a backup was started.
|
||||
Separate from CreationTimestamp, since that value changes
|
||||
on restores.
|
||||
description: StartTimestamp records the time a backup was started.
|
||||
Separate from CreationTimestamp, since that value changes on restores.
|
||||
The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
validationErrors:
|
||||
description: |-
|
||||
ValidationErrors is a slice of all validation errors (if
|
||||
applicable).
|
||||
description: ValidationErrors is a slice of all validation errors
|
||||
(if applicable).
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
version:
|
||||
description: |-
|
||||
Version is the backup format major version.
|
||||
Deprecated: Please see FormatVersion
|
||||
description: 'Version is the backup format major version. Deprecated:
|
||||
Please see FormatVersion'
|
||||
type: integer
|
||||
volumeSnapshotsAttempted:
|
||||
description: |-
|
||||
VolumeSnapshotsAttempted is the total number of attempted
|
||||
description: VolumeSnapshotsAttempted is the total number of attempted
|
||||
volume snapshots for this backup.
|
||||
type: integer
|
||||
volumeSnapshotsCompleted:
|
||||
description: |-
|
||||
VolumeSnapshotsCompleted is the total number of successfully
|
||||
description: VolumeSnapshotsCompleted is the total number of successfully
|
||||
completed volume snapshots for this backup.
|
||||
type: integer
|
||||
warnings:
|
||||
description: |-
|
||||
Warnings is a count of all warning messages that were generated during
|
||||
execution of the backup. The actual warnings are in the backup's log
|
||||
file in object storage.
|
||||
description: Warnings is a count of all warning messages that were
|
||||
generated during execution of the backup. The actual warnings are
|
||||
in the backup's log file in object storage.
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: backupstoragelocations.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -40,19 +40,14 @@ spec:
|
||||
objects
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -86,10 +81,8 @@ spec:
|
||||
valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
@@ -138,36 +131,29 @@ spec:
|
||||
BackupStorageLocation
|
||||
properties:
|
||||
accessMode:
|
||||
description: |-
|
||||
AccessMode is an unused field.
|
||||
|
||||
|
||||
Deprecated: there is now an AccessMode field on the Spec and this field
|
||||
will be removed entirely as of v2.0.
|
||||
description: "AccessMode is an unused field. \n Deprecated: there
|
||||
is now an AccessMode field on the Spec and this field will be removed
|
||||
entirely as of v2.0."
|
||||
enum:
|
||||
- ReadOnly
|
||||
- ReadWrite
|
||||
type: string
|
||||
lastSyncedRevision:
|
||||
description: |-
|
||||
LastSyncedRevision is the value of the `metadata/revision` file in the backup
|
||||
storage location the last time the BSL's contents were synced into the cluster.
|
||||
|
||||
|
||||
Deprecated: this field is no longer updated or used for detecting changes to
|
||||
the location's contents and will be removed entirely in v2.0.
|
||||
description: "LastSyncedRevision is the value of the `metadata/revision`
|
||||
file in the backup storage location the last time the BSL's contents
|
||||
were synced into the cluster. \n Deprecated: this field is no longer
|
||||
updated or used for detecting changes to the location's contents
|
||||
and will be removed entirely in v2.0."
|
||||
type: string
|
||||
lastSyncedTime:
|
||||
description: |-
|
||||
LastSyncedTime is the last time the contents of the location were synced into
|
||||
the cluster.
|
||||
description: LastSyncedTime is the last time the contents of the location
|
||||
were synced into the cluster.
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
lastValidationTime:
|
||||
description: |-
|
||||
LastValidationTime is the last time the backup store location was validated
|
||||
the cluster.
|
||||
description: LastValidationTime is the last time the backup store
|
||||
location was validated the cluster.
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: deletebackuprequests.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -29,19 +29,14 @@ spec:
|
||||
description: DeleteBackupRequest is a request to delete one or more backups.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: downloadrequests.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -17,24 +17,18 @@ spec:
|
||||
- name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
DownloadRequest is a request to download an artifact from backup object storage, such as a backup
|
||||
log file.
|
||||
description: DownloadRequest is a request to download an artifact from backup
|
||||
object storage, such as a backup log file.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -60,7 +54,6 @@ spec:
|
||||
- CSIBackupVolumeSnapshots
|
||||
- CSIBackupVolumeSnapshotContents
|
||||
- BackupVolumeInfos
|
||||
- RestoreVolumeInfo
|
||||
type: string
|
||||
name:
|
||||
description: Name is the name of the Kubernetes resource with
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: podvolumebackups.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -52,19 +52,14 @@ spec:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -72,9 +67,8 @@ spec:
|
||||
description: PodVolumeBackupSpec is the specification for a PodVolumeBackup.
|
||||
properties:
|
||||
backupStorageLocation:
|
||||
description: |-
|
||||
BackupStorageLocation is the name of the backup storage location
|
||||
where the backup repository is stored.
|
||||
description: BackupStorageLocation is the name of the backup storage
|
||||
location where the backup repository is stored.
|
||||
type: string
|
||||
node:
|
||||
description: Node is the name of the node that the Pod is running
|
||||
@@ -88,40 +82,33 @@ spec:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: |-
|
||||
If referring to a piece of an object instead of an entire object, this string
|
||||
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within a pod, this would take on a value like:
|
||||
"spec.containers{name}" (where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]" (container with
|
||||
index 2 in this pod). This syntax is chosen only to have some well-defined way of
|
||||
referencing a part of an object.
|
||||
TODO: this design is not final and this field is subject to change in the future.
|
||||
description: 'If referring to a piece of an object instead of
|
||||
an entire object, this string should contain a valid JSON/Go
|
||||
field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within
|
||||
a pod, this would take on a value like: "spec.containers{name}"
|
||||
(where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]"
|
||||
(container with index 2 in this pod). This syntax is chosen
|
||||
only to have some well-defined way of referencing a part of
|
||||
an object. TODO: this design is not final and this field is
|
||||
subject to change in the future.'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind of the referent.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: |-
|
||||
Namespace of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
|
||||
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: |-
|
||||
Specific resourceVersion to which this reference is made, if any.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
|
||||
description: 'Specific resourceVersion to which this reference
|
||||
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
uid:
|
||||
description: |-
|
||||
UID of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
|
||||
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -131,16 +118,14 @@ spec:
|
||||
tags:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
Tags are a map of key-value pairs that should be applied to the
|
||||
volume backup as tags.
|
||||
description: Tags are a map of key-value pairs that should be applied
|
||||
to the volume backup as tags.
|
||||
type: object
|
||||
uploaderSettings:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
UploaderSettings are a map of key-value pairs that should be applied to the
|
||||
uploader configuration.
|
||||
description: UploaderSettings are a map of key-value pairs that should
|
||||
be applied to the uploader configuration.
|
||||
nullable: true
|
||||
type: object
|
||||
uploaderType:
|
||||
@@ -152,9 +137,8 @@ spec:
|
||||
- ""
|
||||
type: string
|
||||
volume:
|
||||
description: |-
|
||||
Volume is the name of the volume within the Pod to be backed
|
||||
up.
|
||||
description: Volume is the name of the volume within the Pod to be
|
||||
backed up.
|
||||
type: string
|
||||
required:
|
||||
- backupStorageLocation
|
||||
@@ -167,11 +151,10 @@ spec:
|
||||
description: PodVolumeBackupStatus is the current status of a PodVolumeBackup.
|
||||
properties:
|
||||
completionTimestamp:
|
||||
description: |-
|
||||
CompletionTimestamp records the time a backup was completed.
|
||||
Completion time is recorded even on failed backups.
|
||||
Completion time is recorded before uploading the backup object.
|
||||
The server's time is used for CompletionTimestamps
|
||||
description: CompletionTimestamp records the time a backup was completed.
|
||||
Completion time is recorded even on failed backups. Completion time
|
||||
is recorded before uploading the backup object. The server's time
|
||||
is used for CompletionTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
@@ -191,10 +174,9 @@ spec:
|
||||
- Failed
|
||||
type: string
|
||||
progress:
|
||||
description: |-
|
||||
Progress holds the total number of bytes of the volume and the current
|
||||
number of backed up bytes. This can be used to display progress information
|
||||
about the backup operation.
|
||||
description: Progress holds the total number of bytes of the volume
|
||||
and the current number of backed up bytes. This can be used to display
|
||||
progress information about the backup operation.
|
||||
properties:
|
||||
bytesDone:
|
||||
format: int64
|
||||
@@ -208,10 +190,8 @@ spec:
|
||||
pod volume.
|
||||
type: string
|
||||
startTimestamp:
|
||||
description: |-
|
||||
StartTimestamp records the time a backup was started.
|
||||
Separate from CreationTimestamp, since that value changes
|
||||
on restores.
|
||||
description: StartTimestamp records the time a backup was started.
|
||||
Separate from CreationTimestamp, since that value changes on restores.
|
||||
The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: podvolumerestores.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -53,19 +53,14 @@ spec:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -73,9 +68,8 @@ spec:
|
||||
description: PodVolumeRestoreSpec is the specification for a PodVolumeRestore.
|
||||
properties:
|
||||
backupStorageLocation:
|
||||
description: |-
|
||||
BackupStorageLocation is the name of the backup storage location
|
||||
where the backup repository is stored.
|
||||
description: BackupStorageLocation is the name of the backup storage
|
||||
location where the backup repository is stored.
|
||||
type: string
|
||||
pod:
|
||||
description: Pod is a reference to the pod containing the volume to
|
||||
@@ -85,40 +79,33 @@ spec:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: |-
|
||||
If referring to a piece of an object instead of an entire object, this string
|
||||
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within a pod, this would take on a value like:
|
||||
"spec.containers{name}" (where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]" (container with
|
||||
index 2 in this pod). This syntax is chosen only to have some well-defined way of
|
||||
referencing a part of an object.
|
||||
TODO: this design is not final and this field is subject to change in the future.
|
||||
description: 'If referring to a piece of an object instead of
|
||||
an entire object, this string should contain a valid JSON/Go
|
||||
field access statement, such as desiredState.manifest.containers[2].
|
||||
For example, if the object reference is to a container within
|
||||
a pod, this would take on a value like: "spec.containers{name}"
|
||||
(where "name" refers to the name of the container that triggered
|
||||
the event) or if no container name is specified "spec.containers[2]"
|
||||
(container with index 2 in this pod). This syntax is chosen
|
||||
only to have some well-defined way of referencing a part of
|
||||
an object. TODO: this design is not final and this field is
|
||||
subject to change in the future.'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind of the referent.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
namespace:
|
||||
description: |-
|
||||
Namespace of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
|
||||
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
|
||||
type: string
|
||||
resourceVersion:
|
||||
description: |-
|
||||
Specific resourceVersion to which this reference is made, if any.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
|
||||
description: 'Specific resourceVersion to which this reference
|
||||
is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
|
||||
type: string
|
||||
uid:
|
||||
description: |-
|
||||
UID of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
|
||||
description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -135,9 +122,8 @@ spec:
|
||||
uploaderSettings:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
UploaderSettings are a map of key-value pairs that should be applied to the
|
||||
uploader configuration.
|
||||
description: UploaderSettings are a map of key-value pairs that should
|
||||
be applied to the uploader configuration.
|
||||
nullable: true
|
||||
type: object
|
||||
uploaderType:
|
||||
@@ -164,10 +150,9 @@ spec:
|
||||
description: PodVolumeRestoreStatus is the current status of a PodVolumeRestore.
|
||||
properties:
|
||||
completionTimestamp:
|
||||
description: |-
|
||||
CompletionTimestamp records the time a restore was completed.
|
||||
Completion time is recorded even on failed restores.
|
||||
The server's time is used for CompletionTimestamps
|
||||
description: CompletionTimestamp records the time a restore was completed.
|
||||
Completion time is recorded even on failed restores. The server's
|
||||
time is used for CompletionTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
@@ -183,10 +168,9 @@ spec:
|
||||
- Failed
|
||||
type: string
|
||||
progress:
|
||||
description: |-
|
||||
Progress holds the total number of bytes of the snapshot and the current
|
||||
number of restored bytes. This can be used to display progress information
|
||||
about the restore operation.
|
||||
description: Progress holds the total number of bytes of the snapshot
|
||||
and the current number of restored bytes. This can be used to display
|
||||
progress information about the restore operation.
|
||||
properties:
|
||||
bytesDone:
|
||||
format: int64
|
||||
@@ -196,8 +180,7 @@ spec:
|
||||
type: integer
|
||||
type: object
|
||||
startTimestamp:
|
||||
description: |-
|
||||
StartTimestamp records the time a restore was started.
|
||||
description: StartTimestamp records the time a restore was started.
|
||||
The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: restores.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -17,24 +17,18 @@ spec:
|
||||
- name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
Restore is a Velero resource that represents the application of
|
||||
resources from a Velero backup to a target Kubernetes cluster.
|
||||
description: Restore is a Velero resource that represents the application
|
||||
of resources from a Velero backup to a target Kubernetes cluster.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -42,22 +36,19 @@ spec:
|
||||
description: RestoreSpec defines the specification for a Velero restore.
|
||||
properties:
|
||||
backupName:
|
||||
description: |-
|
||||
BackupName is the unique name of the Velero backup to restore
|
||||
from.
|
||||
description: BackupName is the unique name of the Velero backup to
|
||||
restore from.
|
||||
type: string
|
||||
excludedNamespaces:
|
||||
description: |-
|
||||
ExcludedNamespaces contains a list of namespaces that are not
|
||||
included in the restore.
|
||||
description: ExcludedNamespaces contains a list of namespaces that
|
||||
are not included in the restore.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedResources:
|
||||
description: |-
|
||||
ExcludedResources is a slice of resource names that are not
|
||||
included in the restore.
|
||||
description: ExcludedResources is a slice of resource names that are
|
||||
not included in the restore.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -73,9 +64,9 @@ spec:
|
||||
properties:
|
||||
resources:
|
||||
items:
|
||||
description: |-
|
||||
RestoreResourceHookSpec defines one or more RestoreResrouceHooks that should be executed based on
|
||||
the rules defined for namespaces, resources, and label selector.
|
||||
description: RestoreResourceHookSpec defines one or more RestoreResrouceHooks
|
||||
that should be executed based on the rules defined for namespaces,
|
||||
resources, and label selector.
|
||||
properties:
|
||||
excludedNamespaces:
|
||||
description: ExcludedNamespaces specifies the namespaces
|
||||
@@ -92,17 +83,17 @@ spec:
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaces:
|
||||
description: |-
|
||||
IncludedNamespaces specifies the namespaces to which this hook spec applies. If empty, it applies
|
||||
description: IncludedNamespaces specifies the namespaces
|
||||
to which this hook spec applies. If empty, it applies
|
||||
to all namespaces.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: |-
|
||||
IncludedResources specifies the resources to which this hook spec applies. If empty, it applies
|
||||
to all resources.
|
||||
description: IncludedResources specifies the resources to
|
||||
which this hook spec applies. If empty, it applies to
|
||||
all resources.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -116,8 +107,8 @@ spec:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
description: A label selector requirement is a selector
|
||||
that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
@@ -125,16 +116,17 @@ spec:
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In,
|
||||
NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
description: values is an array of string values.
|
||||
If the operator is In or NotIn, the values array
|
||||
must be non-empty. If the operator is Exists
|
||||
or DoesNotExist, the values array must be empty.
|
||||
This array is replaced during a strategic merge
|
||||
patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -146,10 +138,11 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs.
|
||||
A single {key,value} in the matchLabels map is equivalent
|
||||
to an element of matchExpressions, whose key field
|
||||
is "key", the operator is "In", and the values array
|
||||
contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -175,14 +168,15 @@ spec:
|
||||
minItems: 1
|
||||
type: array
|
||||
container:
|
||||
description: |-
|
||||
Container is the container in the pod where the command should be executed. If not specified,
|
||||
the pod's first container is used.
|
||||
description: Container is the container in the
|
||||
pod where the command should be executed. If
|
||||
not specified, the pod's first container is
|
||||
used.
|
||||
type: string
|
||||
execTimeout:
|
||||
description: |-
|
||||
ExecTimeout defines the maximum amount of time Velero should wait for the hook to complete before
|
||||
considering the execution a failure.
|
||||
description: ExecTimeout defines the maximum amount
|
||||
of time Velero should wait for the hook to complete
|
||||
before considering the execution a failure.
|
||||
type: string
|
||||
onError:
|
||||
description: OnError specifies how Velero should
|
||||
@@ -199,9 +193,9 @@ spec:
|
||||
nullable: true
|
||||
type: boolean
|
||||
waitTimeout:
|
||||
description: |-
|
||||
WaitTimeout defines the maximum amount of time Velero should wait for the container to be Ready
|
||||
before attempting to run the command.
|
||||
description: WaitTimeout defines the maximum amount
|
||||
of time Velero should wait for the container
|
||||
to be Ready before attempting to run the command.
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
@@ -231,62 +225,57 @@ spec:
|
||||
type: array
|
||||
type: object
|
||||
includeClusterResources:
|
||||
description: |-
|
||||
IncludeClusterResources specifies whether cluster-scoped resources
|
||||
should be included for consideration in the restore. If null, defaults
|
||||
to true.
|
||||
description: IncludeClusterResources specifies whether cluster-scoped
|
||||
resources should be included for consideration in the restore. If
|
||||
null, defaults to true.
|
||||
nullable: true
|
||||
type: boolean
|
||||
includedNamespaces:
|
||||
description: |-
|
||||
IncludedNamespaces is a slice of namespace names to include objects
|
||||
from. If empty, all namespaces are included.
|
||||
description: IncludedNamespaces is a slice of namespace names to include
|
||||
objects from. If empty, all namespaces are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: |-
|
||||
IncludedResources is a slice of resource names to include
|
||||
description: IncludedResources is a slice of resource names to include
|
||||
in the restore. If empty, all resources in the backup are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
itemOperationTimeout:
|
||||
description: |-
|
||||
ItemOperationTimeout specifies the time used to wait for RestoreItemAction operations
|
||||
The default value is 4 hour.
|
||||
description: ItemOperationTimeout specifies the time used to wait
|
||||
for RestoreItemAction operations The default value is 1 hour.
|
||||
type: string
|
||||
labelSelector:
|
||||
description: |-
|
||||
LabelSelector is a metav1.LabelSelector to filter with
|
||||
when restoring individual objects from the backup. If empty
|
||||
or nil, all objects are included. Optional.
|
||||
description: LabelSelector is a metav1.LabelSelector to filter with
|
||||
when restoring individual objects from the backup. If empty or nil,
|
||||
all objects are included. Optional.
|
||||
nullable: true
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
description: A label selector requirement is a selector that
|
||||
contains values, a key, and an operator that relates the key
|
||||
and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship to
|
||||
a set of values. Valid operators are In, NotIn, Exists
|
||||
and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
description: values is an array of string values. If the
|
||||
operator is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
@@ -299,58 +288,57 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
namespaceMapping:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
NamespaceMapping is a map of source namespace names
|
||||
to target namespace names to restore into. Any source
|
||||
namespaces not included in the map will be restored into
|
||||
namespaces of the same name.
|
||||
description: NamespaceMapping is a map of source namespace names to
|
||||
target namespace names to restore into. Any source namespaces not
|
||||
included in the map will be restored into namespaces of the same
|
||||
name.
|
||||
type: object
|
||||
orLabelSelectors:
|
||||
description: |-
|
||||
OrLabelSelectors is list of metav1.LabelSelector to filter with
|
||||
when restoring individual objects from the backup. If multiple provided
|
||||
they will be joined by the OR operator. LabelSelector as well as
|
||||
OrLabelSelectors cannot co-exist in restore request, only one of them
|
||||
can be used
|
||||
description: OrLabelSelectors is list of metav1.LabelSelector to filter
|
||||
with when restoring individual objects from the backup. If multiple
|
||||
provided they will be joined by the OR operator. LabelSelector as
|
||||
well as OrLabelSelectors cannot co-exist in restore request, only
|
||||
one of them can be used
|
||||
items:
|
||||
description: |-
|
||||
A label selector is a label query over a set of resources. The result of matchLabels and
|
||||
matchExpressions are ANDed. An empty label selector matches all objects. A null
|
||||
label selector matches no objects.
|
||||
description: A label selector is a label query over a set of resources.
|
||||
The result of matchLabels and matchExpressions are ANDed. An empty
|
||||
label selector matches all objects. A null label selector matches
|
||||
no objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
description: A label selector requirement is a selector that
|
||||
contains values, a key, and an operator that relates the
|
||||
key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn, Exists
|
||||
and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
description: values is an array of string values. If the
|
||||
operator is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during a
|
||||
strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -362,10 +350,11 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -382,10 +371,10 @@ spec:
|
||||
nullable: true
|
||||
properties:
|
||||
apiGroup:
|
||||
description: |-
|
||||
APIGroup is the group for the resource being referenced.
|
||||
If APIGroup is not specified, the specified Kind must be in the core API group.
|
||||
For any other third-party types, APIGroup is required.
|
||||
description: APIGroup is the group for the resource being referenced.
|
||||
If APIGroup is not specified, the specified Kind must be in
|
||||
the core API group. For any other third-party types, APIGroup
|
||||
is required.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind is the type of resource being referenced
|
||||
@@ -399,15 +388,13 @@ spec:
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
restorePVs:
|
||||
description: |-
|
||||
RestorePVs specifies whether to restore all included
|
||||
description: RestorePVs specifies whether to restore all included
|
||||
PVs from snapshot
|
||||
nullable: true
|
||||
type: boolean
|
||||
restoreStatus:
|
||||
description: |-
|
||||
RestoreStatus specifies which resources we should restore the status
|
||||
field. If nil, no objects are included. Optional.
|
||||
description: RestoreStatus specifies which resources we should restore
|
||||
the status field. If nil, no objects are included. Optional.
|
||||
nullable: true
|
||||
properties:
|
||||
excludedResources:
|
||||
@@ -418,50 +405,46 @@ spec:
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: |-
|
||||
IncludedResources specifies the resources to which will restore the status.
|
||||
If empty, it applies to all resources.
|
||||
description: IncludedResources specifies the resources to which
|
||||
will restore the status. If empty, it applies to all resources.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
scheduleName:
|
||||
description: |-
|
||||
ScheduleName is the unique name of the Velero schedule to restore
|
||||
from. If specified, and BackupName is empty, Velero will restore
|
||||
from the most recent successful backup created from this schedule.
|
||||
description: ScheduleName is the unique name of the Velero schedule
|
||||
to restore from. If specified, and BackupName is empty, Velero will
|
||||
restore from the most recent successful backup created from this
|
||||
schedule.
|
||||
type: string
|
||||
uploaderConfig:
|
||||
description: UploaderConfig specifies the configuration for the restore.
|
||||
nullable: true
|
||||
properties:
|
||||
parallelFilesDownload:
|
||||
description: ParallelFilesDownload is the concurrency number setting
|
||||
for restore.
|
||||
type: integer
|
||||
writeSparseFiles:
|
||||
description: WriteSparseFiles is a flag to indicate whether write
|
||||
files sparsely or not.
|
||||
nullable: true
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- backupName
|
||||
type: object
|
||||
status:
|
||||
description: RestoreStatus captures the current status of a Velero restore
|
||||
properties:
|
||||
completionTimestamp:
|
||||
description: |-
|
||||
CompletionTimestamp records the time the restore operation was completed.
|
||||
Completion time is recorded even on failed restore.
|
||||
description: CompletionTimestamp records the time the restore operation
|
||||
was completed. Completion time is recorded even on failed restore.
|
||||
The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
errors:
|
||||
description: |-
|
||||
Errors is a count of all error messages that were generated during
|
||||
execution of the restore. The actual errors are stored in object storage.
|
||||
description: Errors is a count of all error messages that were generated
|
||||
during execution of the restore. The actual errors are stored in
|
||||
object storage.
|
||||
type: integer
|
||||
failureReason:
|
||||
description: FailureReason is an error that caused the entire restore
|
||||
@@ -473,10 +456,10 @@ spec:
|
||||
nullable: true
|
||||
properties:
|
||||
hooksAttempted:
|
||||
description: |-
|
||||
HooksAttempted is the total number of attempted hooks
|
||||
Specifically, HooksAttempted represents the number of hooks that failed to execute
|
||||
and the number of hooks that executed successfully.
|
||||
description: HooksAttempted is the total number of attempted hooks
|
||||
Specifically, HooksAttempted represents the number of hooks
|
||||
that failed to execute and the number of hooks that executed
|
||||
successfully.
|
||||
type: integer
|
||||
hooksFailed:
|
||||
description: HooksFailed is the total number of hooks which ended
|
||||
@@ -494,14 +477,11 @@ spec:
|
||||
- Completed
|
||||
- PartiallyFailed
|
||||
- Failed
|
||||
- Finalizing
|
||||
- FinalizingPartiallyFailed
|
||||
type: string
|
||||
progress:
|
||||
description: |-
|
||||
Progress contains information about the restore's execution progress. Note
|
||||
that this information is best-effort only -- if Velero fails to update it
|
||||
during a restore for any reason, it may be inaccurate/stale.
|
||||
description: Progress contains information about the restore's execution
|
||||
progress. Note that this information is best-effort only -- if Velero
|
||||
fails to update it during a restore for any reason, it may be inaccurate/stale.
|
||||
nullable: true
|
||||
properties:
|
||||
itemsRestored:
|
||||
@@ -509,46 +489,42 @@ spec:
|
||||
been restored so far
|
||||
type: integer
|
||||
totalItems:
|
||||
description: |-
|
||||
TotalItems is the total number of items to be restored. This number may change
|
||||
throughout the execution of the restore due to plugins that return additional related
|
||||
items to restore
|
||||
description: TotalItems is the total number of items to be restored.
|
||||
This number may change throughout the execution of the restore
|
||||
due to plugins that return additional related items to restore
|
||||
type: integer
|
||||
type: object
|
||||
restoreItemOperationsAttempted:
|
||||
description: |-
|
||||
RestoreItemOperationsAttempted is the total number of attempted
|
||||
async RestoreItemAction operations for this restore.
|
||||
description: RestoreItemOperationsAttempted is the total number of
|
||||
attempted async RestoreItemAction operations for this restore.
|
||||
type: integer
|
||||
restoreItemOperationsCompleted:
|
||||
description: |-
|
||||
RestoreItemOperationsCompleted is the total number of successfully completed
|
||||
async RestoreItemAction operations for this restore.
|
||||
description: RestoreItemOperationsCompleted is the total number of
|
||||
successfully completed async RestoreItemAction operations for this
|
||||
restore.
|
||||
type: integer
|
||||
restoreItemOperationsFailed:
|
||||
description: |-
|
||||
RestoreItemOperationsFailed is the total number of async
|
||||
RestoreItemAction operations for this restore which ended with an error.
|
||||
description: RestoreItemOperationsFailed is the total number of async
|
||||
RestoreItemAction operations for this restore which ended with an
|
||||
error.
|
||||
type: integer
|
||||
startTimestamp:
|
||||
description: |-
|
||||
StartTimestamp records the time the restore operation was started.
|
||||
The server's time is used for StartTimestamps
|
||||
description: StartTimestamp records the time the restore operation
|
||||
was started. The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
validationErrors:
|
||||
description: |-
|
||||
ValidationErrors is a slice of all validation errors (if
|
||||
applicable)
|
||||
description: ValidationErrors is a slice of all validation errors
|
||||
(if applicable)
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
warnings:
|
||||
description: |-
|
||||
Warnings is a count of all warning messages that were generated during
|
||||
execution of the restore. The actual warnings are stored in object storage.
|
||||
description: Warnings is a count of all warning messages that were
|
||||
generated during execution of the restore. The actual warnings are
|
||||
stored in object storage.
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: schedules.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -36,24 +36,18 @@ spec:
|
||||
name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
Schedule is a Velero resource that represents a pre-scheduled or
|
||||
periodic Backup that should be run.
|
||||
description: Schedule is a Velero resource that represents a pre-scheduled
|
||||
or periodic Backup that should be run.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -64,80 +58,73 @@ spec:
|
||||
description: Paused specifies whether the schedule is paused or not
|
||||
type: boolean
|
||||
schedule:
|
||||
description: |-
|
||||
Schedule is a Cron expression defining when to run
|
||||
the Backup.
|
||||
description: Schedule is a Cron expression defining when to run the
|
||||
Backup.
|
||||
type: string
|
||||
skipImmediately:
|
||||
description: |-
|
||||
SkipImmediately specifies whether to skip backup if schedule is due immediately from `schedule.status.lastBackup` timestamp when schedule is unpaused or if schedule is new.
|
||||
If true, backup will be skipped immediately when schedule is unpaused if it is due based on .Status.LastBackupTimestamp or schedule is new, and will run at next schedule time.
|
||||
If false, backup will not be skipped immediately when schedule is unpaused, but will run at next schedule time.
|
||||
If empty, will follow server configuration (default: false).
|
||||
description: 'SkipImmediately specifies whether to skip backup if
|
||||
schedule is due immediately from `schedule.status.lastBackup` timestamp
|
||||
when schedule is unpaused or if schedule is new. If true, backup
|
||||
will be skipped immediately when schedule is unpaused if it is due
|
||||
based on .Status.LastBackupTimestamp or schedule is new, and will
|
||||
run at next schedule time. If false, backup will not be skipped
|
||||
immediately when schedule is unpaused, but will run at next schedule
|
||||
time. If empty, will follow server configuration (default: false).'
|
||||
type: boolean
|
||||
template:
|
||||
description: |-
|
||||
Template is the definition of the Backup to be run
|
||||
on the provided schedule
|
||||
description: Template is the definition of the Backup to be run on
|
||||
the provided schedule
|
||||
properties:
|
||||
csiSnapshotTimeout:
|
||||
description: |-
|
||||
CSISnapshotTimeout specifies the time used to wait for CSI VolumeSnapshot status turns to
|
||||
ReadyToUse during creation, before returning error as timeout.
|
||||
The default value is 10 minute.
|
||||
description: CSISnapshotTimeout specifies the time used to wait
|
||||
for CSI VolumeSnapshot status turns to ReadyToUse during creation,
|
||||
before returning error as timeout. The default value is 10 minute.
|
||||
type: string
|
||||
datamover:
|
||||
description: |-
|
||||
DataMover specifies the data mover to be used by the backup.
|
||||
If DataMover is "" or "velero", the built-in data mover will be used.
|
||||
description: DataMover specifies the data mover to be used by
|
||||
the backup. If DataMover is "" or "velero", the built-in data
|
||||
mover will be used.
|
||||
type: string
|
||||
defaultVolumesToFsBackup:
|
||||
description: |-
|
||||
DefaultVolumesToFsBackup specifies whether pod volume file system backup should be used
|
||||
for all volumes by default.
|
||||
description: DefaultVolumesToFsBackup specifies whether pod volume
|
||||
file system backup should be used for all volumes by default.
|
||||
nullable: true
|
||||
type: boolean
|
||||
defaultVolumesToRestic:
|
||||
description: |-
|
||||
DefaultVolumesToRestic specifies whether restic should be used to take a
|
||||
backup of all pod volumes by default.
|
||||
|
||||
|
||||
Deprecated: this field is no longer used and will be removed entirely in future. Use DefaultVolumesToFsBackup instead.
|
||||
description: "DefaultVolumesToRestic specifies whether restic
|
||||
should be used to take a backup of all pod volumes by default.
|
||||
\n Deprecated: this field is no longer used and will be removed
|
||||
entirely in future. Use DefaultVolumesToFsBackup instead."
|
||||
nullable: true
|
||||
type: boolean
|
||||
excludedClusterScopedResources:
|
||||
description: |-
|
||||
ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to exclude from the backup.
|
||||
If set to "*", all cluster-scoped resource types are excluded.
|
||||
The default value is empty.
|
||||
description: ExcludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to exclude from the backup. If set to "*",
|
||||
all cluster-scoped resource types are excluded. The default
|
||||
value is empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaceScopedResources:
|
||||
description: |-
|
||||
ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to exclude from the backup.
|
||||
If set to "*", all namespace-scoped resource types are excluded.
|
||||
The default value is empty.
|
||||
description: ExcludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to exclude from the backup. If set to "*",
|
||||
all namespace-scoped resource types are excluded. The default
|
||||
value is empty.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedNamespaces:
|
||||
description: |-
|
||||
ExcludedNamespaces contains a list of namespaces that are not
|
||||
included in the backup.
|
||||
description: ExcludedNamespaces contains a list of namespaces
|
||||
that are not included in the backup.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
excludedResources:
|
||||
description: |-
|
||||
ExcludedResources is a slice of resource names that are not
|
||||
included in the backup.
|
||||
description: ExcludedResources is a slice of resource names that
|
||||
are not included in the backup.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -150,9 +137,9 @@ spec:
|
||||
description: Resources are hooks that should be executed when
|
||||
backing up individual instances of a resource.
|
||||
items:
|
||||
description: |-
|
||||
BackupResourceHookSpec defines one or more BackupResourceHooks that should be executed based on
|
||||
the rules defined for namespaces, resources, and label selector.
|
||||
description: BackupResourceHookSpec defines one or more
|
||||
BackupResourceHooks that should be executed based on the
|
||||
rules defined for namespaces, resources, and label selector.
|
||||
properties:
|
||||
excludedNamespaces:
|
||||
description: ExcludedNamespaces specifies the namespaces
|
||||
@@ -169,16 +156,16 @@ spec:
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaces:
|
||||
description: |-
|
||||
IncludedNamespaces specifies the namespaces to which this hook spec applies. If empty, it applies
|
||||
description: IncludedNamespaces specifies the namespaces
|
||||
to which this hook spec applies. If empty, it applies
|
||||
to all namespaces.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: |-
|
||||
IncludedResources specifies the resources to which this hook spec applies. If empty, it applies
|
||||
description: IncludedResources specifies the resources
|
||||
to which this hook spec applies. If empty, it applies
|
||||
to all resources.
|
||||
items:
|
||||
type: string
|
||||
@@ -193,25 +180,26 @@ spec:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
description: A label selector requirement is a
|
||||
selector that contains values, a key, and an
|
||||
operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the
|
||||
selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are
|
||||
In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the
|
||||
operator is Exists or DoesNotExist, the
|
||||
values array must be empty. This array is
|
||||
replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -223,10 +211,12 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is "In",
|
||||
and the values array contains only "value". The
|
||||
requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -234,9 +224,10 @@ spec:
|
||||
description: Name is the name of this hook.
|
||||
type: string
|
||||
post:
|
||||
description: |-
|
||||
PostHooks is a list of BackupResourceHooks to execute after storing the item in the backup.
|
||||
These are executed after all "additional items" from item actions are processed.
|
||||
description: PostHooks is a list of BackupResourceHooks
|
||||
to execute after storing the item in the backup. These
|
||||
are executed after all "additional items" from item
|
||||
actions are processed.
|
||||
items:
|
||||
description: BackupResourceHook defines a hook for
|
||||
a resource.
|
||||
@@ -252,9 +243,10 @@ spec:
|
||||
minItems: 1
|
||||
type: array
|
||||
container:
|
||||
description: |-
|
||||
Container is the container in the pod where the command should be executed. If not specified,
|
||||
the pod's first container is used.
|
||||
description: Container is the container in
|
||||
the pod where the command should be executed.
|
||||
If not specified, the pod's first container
|
||||
is used.
|
||||
type: string
|
||||
onError:
|
||||
description: OnError specifies how Velero
|
||||
@@ -265,9 +257,10 @@ spec:
|
||||
- Fail
|
||||
type: string
|
||||
timeout:
|
||||
description: |-
|
||||
Timeout defines the maximum amount of time Velero should wait for the hook to complete before
|
||||
considering the execution a failure.
|
||||
description: Timeout defines the maximum amount
|
||||
of time Velero should wait for the hook
|
||||
to complete before considering the execution
|
||||
a failure.
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
@@ -277,9 +270,10 @@ spec:
|
||||
type: object
|
||||
type: array
|
||||
pre:
|
||||
description: |-
|
||||
PreHooks is a list of BackupResourceHooks to execute prior to storing the item in the backup.
|
||||
These are executed before any "additional items" from item actions are processed.
|
||||
description: PreHooks is a list of BackupResourceHooks
|
||||
to execute prior to storing the item in the backup.
|
||||
These are executed before any "additional items" from
|
||||
item actions are processed.
|
||||
items:
|
||||
description: BackupResourceHook defines a hook for
|
||||
a resource.
|
||||
@@ -295,9 +289,10 @@ spec:
|
||||
minItems: 1
|
||||
type: array
|
||||
container:
|
||||
description: |-
|
||||
Container is the container in the pod where the command should be executed. If not specified,
|
||||
the pod's first container is used.
|
||||
description: Container is the container in
|
||||
the pod where the command should be executed.
|
||||
If not specified, the pod's first container
|
||||
is used.
|
||||
type: string
|
||||
onError:
|
||||
description: OnError specifies how Velero
|
||||
@@ -308,9 +303,10 @@ spec:
|
||||
- Fail
|
||||
type: string
|
||||
timeout:
|
||||
description: |-
|
||||
Timeout defines the maximum amount of time Velero should wait for the hook to complete before
|
||||
considering the execution a failure.
|
||||
description: Timeout defines the maximum amount
|
||||
of time Velero should wait for the hook
|
||||
to complete before considering the execution
|
||||
a failure.
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
@@ -326,56 +322,50 @@ spec:
|
||||
type: array
|
||||
type: object
|
||||
includeClusterResources:
|
||||
description: |-
|
||||
IncludeClusterResources specifies whether cluster-scoped resources
|
||||
should be included for consideration in the backup.
|
||||
description: IncludeClusterResources specifies whether cluster-scoped
|
||||
resources should be included for consideration in the backup.
|
||||
nullable: true
|
||||
type: boolean
|
||||
includedClusterScopedResources:
|
||||
description: |-
|
||||
IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to include in the backup.
|
||||
If set to "*", all cluster-scoped resource types are included.
|
||||
The default value is empty, which means only related
|
||||
cluster-scoped resources are included.
|
||||
description: IncludedClusterScopedResources is a slice of cluster-scoped
|
||||
resource type names to include in the backup. If set to "*",
|
||||
all cluster-scoped resource types are included. The default
|
||||
value is empty, which means only related cluster-scoped resources
|
||||
are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaceScopedResources:
|
||||
description: |-
|
||||
IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to include in the backup.
|
||||
The default value is "*".
|
||||
description: IncludedNamespaceScopedResources is a slice of namespace-scoped
|
||||
resource type names to include in the backup. The default value
|
||||
is "*".
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedNamespaces:
|
||||
description: |-
|
||||
IncludedNamespaces is a slice of namespace names to include objects
|
||||
from. If empty, all namespaces are included.
|
||||
description: IncludedNamespaces is a slice of namespace names
|
||||
to include objects from. If empty, all namespaces are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
includedResources:
|
||||
description: |-
|
||||
IncludedResources is a slice of resource names to include
|
||||
in the backup. If empty, all resources are included.
|
||||
description: IncludedResources is a slice of resource names to
|
||||
include in the backup. If empty, all resources are included.
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
itemOperationTimeout:
|
||||
description: |-
|
||||
ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations
|
||||
The default value is 4 hour.
|
||||
description: ItemOperationTimeout specifies the time used to wait
|
||||
for asynchronous BackupItemAction operations The default value
|
||||
is 1 hour.
|
||||
type: string
|
||||
labelSelector:
|
||||
description: |-
|
||||
LabelSelector is a metav1.LabelSelector to filter with
|
||||
when adding individual objects to the backup. If empty
|
||||
description: LabelSelector is a metav1.LabelSelector to filter
|
||||
with when adding individual objects to the backup. If empty
|
||||
or nil, all objects are included. Optional.
|
||||
nullable: true
|
||||
properties:
|
||||
@@ -383,25 +373,25 @@ spec:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
description: A label selector requirement is a selector
|
||||
that contains values, a key, and an operator that relates
|
||||
the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn,
|
||||
Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
description: values is an array of string values. If
|
||||
the operator is In or NotIn, the values array must
|
||||
be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced
|
||||
during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -413,10 +403,11 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs. A
|
||||
single {key,value} in the matchLabels map is equivalent
|
||||
to an element of matchExpressions, whose key field is "key",
|
||||
the operator is "In", and the values array contains only
|
||||
"value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -428,41 +419,40 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
orLabelSelectors:
|
||||
description: |-
|
||||
OrLabelSelectors is list of metav1.LabelSelector to filter with
|
||||
when adding individual objects to the backup. If multiple provided
|
||||
they will be joined by the OR operator. LabelSelector as well as
|
||||
OrLabelSelectors cannot co-exist in backup request, only one of them
|
||||
can be used.
|
||||
description: OrLabelSelectors is list of metav1.LabelSelector
|
||||
to filter with when adding individual objects to the backup.
|
||||
If multiple provided they will be joined by the OR operator.
|
||||
LabelSelector as well as OrLabelSelectors cannot co-exist in
|
||||
backup request, only one of them can be used.
|
||||
items:
|
||||
description: |-
|
||||
A label selector is a label query over a set of resources. The result of matchLabels and
|
||||
matchExpressions are ANDed. An empty label selector matches all objects. A null
|
||||
label selector matches no objects.
|
||||
description: A label selector is a label query over a set of
|
||||
resources. The result of matchLabels and matchExpressions
|
||||
are ANDed. An empty label selector matches all objects. A
|
||||
null label selector matches no objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
description: A label selector requirement is a selector
|
||||
that contains values, a key, and an operator that relates
|
||||
the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn,
|
||||
Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
description: values is an array of string values.
|
||||
If the operator is In or NotIn, the values array
|
||||
must be non-empty. If the operator is Exists or
|
||||
DoesNotExist, the values array must be empty. This
|
||||
array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -474,10 +464,11 @@ spec:
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
description: matchLabels is a map of {key,value} pairs.
|
||||
A single {key,value} in the matchLabels map is equivalent
|
||||
to an element of matchExpressions, whose key field is
|
||||
"key", the operator is "In", and the values array contains
|
||||
only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
@@ -486,10 +477,11 @@ spec:
|
||||
orderedResources:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
OrderedResources specifies the backup order of resources of specific Kind.
|
||||
The map key is the resource name and value is a list of object names separated by commas.
|
||||
Each resource name has format "namespace/objectname". For cluster resources, simply use "objectname".
|
||||
description: OrderedResources specifies the backup order of resources
|
||||
of specific Kind. The map key is the resource name and value
|
||||
is a list of object names separated by commas. Each resource
|
||||
name has format "namespace/objectname". For cluster resources,
|
||||
simply use "objectname".
|
||||
nullable: true
|
||||
type: object
|
||||
resourcePolicy:
|
||||
@@ -497,10 +489,10 @@ spec:
|
||||
policies that backup should follow
|
||||
properties:
|
||||
apiGroup:
|
||||
description: |-
|
||||
APIGroup is the group for the resource being referenced.
|
||||
If APIGroup is not specified, the specified Kind must be in the core API group.
|
||||
For any other third-party types, APIGroup is required.
|
||||
description: APIGroup is the group for the resource being
|
||||
referenced. If APIGroup is not specified, the specified
|
||||
Kind must be in the core API group. For any other third-party
|
||||
types, APIGroup is required.
|
||||
type: string
|
||||
kind:
|
||||
description: Kind is the type of resource being referenced
|
||||
@@ -519,10 +511,9 @@ spec:
|
||||
nullable: true
|
||||
type: boolean
|
||||
snapshotVolumes:
|
||||
description: |-
|
||||
SnapshotVolumes specifies whether to take snapshots
|
||||
of any PV's referenced in the set of objects included
|
||||
in the Backup.
|
||||
description: SnapshotVolumes specifies whether to take snapshots
|
||||
of any PV's referenced in the set of objects included in the
|
||||
Backup.
|
||||
nullable: true
|
||||
type: boolean
|
||||
storageLocation:
|
||||
@@ -530,9 +521,8 @@ spec:
|
||||
a BackupStorageLocation where the backup should be stored.
|
||||
type: string
|
||||
ttl:
|
||||
description: |-
|
||||
TTL is a time.Duration-parseable string describing how long
|
||||
the Backup should be retained for.
|
||||
description: TTL is a time.Duration-parseable string describing
|
||||
how long the Backup should be retained for.
|
||||
type: string
|
||||
uploaderConfig:
|
||||
description: UploaderConfig specifies the configuration for the
|
||||
@@ -552,9 +542,8 @@ spec:
|
||||
type: array
|
||||
type: object
|
||||
useOwnerReferencesInBackup:
|
||||
description: |-
|
||||
UseOwnerReferencesBackup specifies whether to use
|
||||
OwnerReferences on backups created by this Schedule.
|
||||
description: UseOwnerReferencesBackup specifies whether to use OwnerReferences
|
||||
on backups created by this Schedule.
|
||||
nullable: true
|
||||
type: boolean
|
||||
required:
|
||||
@@ -565,8 +554,7 @@ spec:
|
||||
description: ScheduleStatus captures the current state of a Velero schedule
|
||||
properties:
|
||||
lastBackup:
|
||||
description: |-
|
||||
LastBackup is the last time a Backup was run for this
|
||||
description: LastBackup is the last time a Backup was run for this
|
||||
Schedule schedule
|
||||
format: date-time
|
||||
nullable: true
|
||||
@@ -584,9 +572,8 @@ spec:
|
||||
- FailedValidation
|
||||
type: string
|
||||
validationErrors:
|
||||
description: |-
|
||||
ValidationErrors is a slice of all validation errors (if
|
||||
applicable)
|
||||
description: ValidationErrors is a slice of all validation errors
|
||||
(if applicable)
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: serverstatusrequests.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -19,24 +19,18 @@ spec:
|
||||
- name: v1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
ServerStatusRequest is a request to access current status information about
|
||||
the Velero server.
|
||||
description: ServerStatusRequest is a request to access current status information
|
||||
about the Velero server.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -69,9 +63,8 @@ spec:
|
||||
nullable: true
|
||||
type: array
|
||||
processedTimestamp:
|
||||
description: |-
|
||||
ProcessedTimestamp is when the ServerStatusRequest was processed
|
||||
by the ServerStatusRequestController.
|
||||
description: ProcessedTimestamp is when the ServerStatusRequest was
|
||||
processed by the ServerStatusRequestController.
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: volumesnapshotlocations.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -23,19 +23,14 @@ spec:
|
||||
snapshots.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -57,10 +52,8 @@ spec:
|
||||
valid secret key.
|
||||
type: string
|
||||
name:
|
||||
description: |-
|
||||
Name of the referent.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must be defined
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: datadownloads.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -52,19 +52,14 @@ spec:
|
||||
and data mover controller for the datamover restore operation
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -72,14 +67,12 @@ spec:
|
||||
description: DataDownloadSpec is the specification for a DataDownload.
|
||||
properties:
|
||||
backupStorageLocation:
|
||||
description: |-
|
||||
BackupStorageLocation is the name of the backup storage location
|
||||
where the backup repository is stored.
|
||||
description: BackupStorageLocation is the name of the backup storage
|
||||
location where the backup repository is stored.
|
||||
type: string
|
||||
cancel:
|
||||
description: |-
|
||||
Cancel indicates request to cancel the ongoing DataDownload. It can be set
|
||||
when the DataDownload is in InProgress phase
|
||||
description: Cancel indicates request to cancel the ongoing DataDownload.
|
||||
It can be set when the DataDownload is in InProgress phase
|
||||
type: boolean
|
||||
dataMoverConfig:
|
||||
additionalProperties:
|
||||
@@ -88,23 +81,22 @@ spec:
|
||||
fields.
|
||||
type: object
|
||||
datamover:
|
||||
description: |-
|
||||
DataMover specifies the data mover to be used by the backup.
|
||||
If DataMover is "" or "velero", the built-in data mover will be used.
|
||||
description: DataMover specifies the data mover to be used by the
|
||||
backup. If DataMover is "" or "velero", the built-in data mover
|
||||
will be used.
|
||||
type: string
|
||||
operationTimeout:
|
||||
description: |-
|
||||
OperationTimeout specifies the time used to wait internal operations,
|
||||
before returning error as timeout.
|
||||
description: OperationTimeout specifies the time used to wait internal
|
||||
operations, before returning error as timeout.
|
||||
type: string
|
||||
snapshotID:
|
||||
description: SnapshotID is the ID of the Velero backup snapshot to
|
||||
be restored from.
|
||||
type: string
|
||||
sourceNamespace:
|
||||
description: |-
|
||||
SourceNamespace is the original namespace where the volume is backed up from.
|
||||
It may be different from SourcePVC's namespace if namespace is remapped during restore.
|
||||
description: SourceNamespace is the original namespace where the volume
|
||||
is backed up from. It may be different from SourcePVC's namespace
|
||||
if namespace is remapped during restore.
|
||||
type: string
|
||||
targetVolume:
|
||||
description: TargetVolume is the information of the target PVC and
|
||||
@@ -137,10 +129,9 @@ spec:
|
||||
description: DataDownloadStatus is the current status of a DataDownload.
|
||||
properties:
|
||||
completionTimestamp:
|
||||
description: |-
|
||||
CompletionTimestamp records the time a restore was completed.
|
||||
Completion time is recorded even on failed restores.
|
||||
The server's time is used for CompletionTimestamps
|
||||
description: CompletionTimestamp records the time a restore was completed.
|
||||
Completion time is recorded even on failed restores. The server's
|
||||
time is used for CompletionTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
@@ -163,10 +154,9 @@ spec:
|
||||
- Failed
|
||||
type: string
|
||||
progress:
|
||||
description: |-
|
||||
Progress holds the total number of bytes of the snapshot and the current
|
||||
number of restored bytes. This can be used to display progress information
|
||||
about the restore operation.
|
||||
description: Progress holds the total number of bytes of the snapshot
|
||||
and the current number of restored bytes. This can be used to display
|
||||
progress information about the restore operation.
|
||||
properties:
|
||||
bytesDone:
|
||||
format: int64
|
||||
@@ -176,8 +166,7 @@ spec:
|
||||
type: integer
|
||||
type: object
|
||||
startTimestamp:
|
||||
description: |-
|
||||
StartTimestamp records the time a restore was started.
|
||||
description: StartTimestamp records the time a restore was started.
|
||||
The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
|
||||
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
controller-gen.kubebuilder.io/version: v0.12.0
|
||||
name: datauploads.velero.io
|
||||
spec:
|
||||
group: velero.io
|
||||
@@ -53,19 +53,14 @@ spec:
|
||||
data mover controller for the datamover backup operation
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
@@ -73,14 +68,12 @@ spec:
|
||||
description: DataUploadSpec is the specification for a DataUpload.
|
||||
properties:
|
||||
backupStorageLocation:
|
||||
description: |-
|
||||
BackupStorageLocation is the name of the backup storage location
|
||||
where the backup repository is stored.
|
||||
description: BackupStorageLocation is the name of the backup storage
|
||||
location where the backup repository is stored.
|
||||
type: string
|
||||
cancel:
|
||||
description: |-
|
||||
Cancel indicates request to cancel the ongoing DataUpload. It can be set
|
||||
when the DataUpload is in InProgress phase
|
||||
description: Cancel indicates request to cancel the ongoing DataUpload.
|
||||
It can be set when the DataUpload is in InProgress phase
|
||||
type: boolean
|
||||
csiSnapshot:
|
||||
description: If SnapshotType is CSI, CSISnapshot provides the information
|
||||
@@ -111,23 +104,22 @@ spec:
|
||||
nullable: true
|
||||
type: object
|
||||
datamover:
|
||||
description: |-
|
||||
DataMover specifies the data mover to be used by the backup.
|
||||
If DataMover is "" or "velero", the built-in data mover will be used.
|
||||
description: DataMover specifies the data mover to be used by the
|
||||
backup. If DataMover is "" or "velero", the built-in data mover
|
||||
will be used.
|
||||
type: string
|
||||
operationTimeout:
|
||||
description: |-
|
||||
OperationTimeout specifies the time used to wait internal operations,
|
||||
before returning error as timeout.
|
||||
description: OperationTimeout specifies the time used to wait internal
|
||||
operations, before returning error as timeout.
|
||||
type: string
|
||||
snapshotType:
|
||||
description: SnapshotType is the type of the snapshot to be backed
|
||||
up.
|
||||
type: string
|
||||
sourceNamespace:
|
||||
description: |-
|
||||
SourceNamespace is the original namespace where the volume is backed up from.
|
||||
It is the same namespace for SourcePVC and CSI namespaced objects.
|
||||
description: SourceNamespace is the original namespace where the volume
|
||||
is backed up from. It is the same namespace for SourcePVC and CSI
|
||||
namespaced objects.
|
||||
type: string
|
||||
sourcePVC:
|
||||
description: SourcePVC is the name of the PVC which the snapshot is
|
||||
@@ -144,11 +136,10 @@ spec:
|
||||
description: DataUploadStatus is the current status of a DataUpload.
|
||||
properties:
|
||||
completionTimestamp:
|
||||
description: |-
|
||||
CompletionTimestamp records the time a backup was completed.
|
||||
Completion time is recorded even on failed backups.
|
||||
Completion time is recorded before uploading the backup object.
|
||||
The server's time is used for CompletionTimestamps
|
||||
description: CompletionTimestamp records the time a backup was completed.
|
||||
Completion time is recorded even on failed backups. Completion time
|
||||
is recorded before uploading the backup object. The server's time
|
||||
is used for CompletionTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
@@ -182,10 +173,9 @@ spec:
|
||||
- Failed
|
||||
type: string
|
||||
progress:
|
||||
description: |-
|
||||
Progress holds the total number of bytes of the volume and the current
|
||||
number of backed up bytes. This can be used to display progress information
|
||||
about the backup operation.
|
||||
description: Progress holds the total number of bytes of the volume
|
||||
and the current number of backed up bytes. This can be used to display
|
||||
progress information about the backup operation.
|
||||
properties:
|
||||
bytesDone:
|
||||
format: int64
|
||||
@@ -199,10 +189,8 @@ spec:
|
||||
backup repository.
|
||||
type: string
|
||||
startTimestamp:
|
||||
description: |-
|
||||
StartTimestamp records the time a backup was started.
|
||||
Separate from CreationTimestamp, since that value changes
|
||||
on restores.
|
||||
description: StartTimestamp records the time a backup was started.
|
||||
Separate from CreationTimestamp, since that value changes on restores.
|
||||
The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,344 +0,0 @@
|
||||
# Extend VolumePolicies to support more actions
|
||||
|
||||
## Abstract
|
||||
|
||||
Currently, the [VolumePolicies feature](https://github.com/vmware-tanzu/velero/blob/main/design/Implemented/handle-backup-of-volumes-by-resources-filters.md) which can be used to filter/handle volumes during backup only supports the skip action on matching conditions. Users need more actions to be supported.
|
||||
|
||||
## Background
|
||||
|
||||
The `VolumePolicies` feature was introduced in Velero 1.11 as a flexible way to handle volumes. The main agenda of
|
||||
introducing the VolumePolicies feature was to improve the overall user experience when performing backup operations
|
||||
for volume resources, the feature enables users to group volumes according the `conditions` (criteria) specified and
|
||||
also lets you specify the `action` that velero needs to take for these grouped volumes during the backup operation.
|
||||
The limitation being that currently `VolumePolicies` only supports `skip` as an action, We want to extend the `action`
|
||||
functionality to support more usable options like `fs-backup` (File system backup) and `snapshot` (VolumeSnapshots).
|
||||
|
||||
## Goals
|
||||
- Extending the VolumePolicies to support more actions like `fs-backup` (File system backup) and `snapshot` (VolumeSnapshots).
|
||||
- Improve user experience when backing up Volumes via Velero
|
||||
|
||||
## Non-Goals
|
||||
- No changes to existing approaches to opt-in/opt-out annotations for volumes
|
||||
- No changes to existing `VolumePolicies` functionalities
|
||||
- No additions or implementations to support more granular actions like `snapshot-csi` and `snapshot-datamover`. These actions can be implemented as a future enhancement
|
||||
|
||||
|
||||
## Use-cases/Scenarios
|
||||
|
||||
**Use-case 1:**
|
||||
- A user wants to use `snapshot` (volumesnapshots) backup option for all the csi supported volumes and `fs-backup` for the rest of the volumes.
|
||||
- Currently, velero supports this use-case but the user experience is not that great.
|
||||
- The user will have to individually annotate the volume mounting pod with the annotation "backup.velero.io/backup-volumes" for `fs-backup`
|
||||
- This becomes cumbersome at scale.
|
||||
- Using `VolumePolicies`, the user can just specify 2 simple `VolumePolicies` like for csi supported volumes as `snapshot` action and rest can be backed up`fs-backup` action:
|
||||
```yaml
|
||||
version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
storageClass:
|
||||
- gp2
|
||||
action:
|
||||
type: snapshot
|
||||
- conditions: {}
|
||||
action:
|
||||
type: fs-backup
|
||||
```
|
||||
|
||||
**Use-case 2:**
|
||||
- A user wants to use `fs-backup` for nfs volumes pertaining to a particular server
|
||||
- In such a scenario the user can just specify a `VolumePolicy` like:
|
||||
```yaml
|
||||
version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
nfs:
|
||||
server: 192.168.200.90
|
||||
action:
|
||||
type: fs-backup
|
||||
```
|
||||
## High-Level Design
|
||||
- When the VolumePolicy action is set as `fs-backup` the backup workflow modifications would be:
|
||||
- We call [backupItem() -> backupItemInternal()](https://github.com/vmware-tanzu/velero/blob/main/pkg/backup/item_backupper.go#L95) on all the items that are to be backed up
|
||||
- Here when we encounter [Pod as an item ](https://github.com/vmware-tanzu/velero/blob/main/pkg/backup/item_backupper.go#L195)
|
||||
- We will have to modify the backup workflow to account for the `fs-backup` VolumePolicy action
|
||||
|
||||
|
||||
- When the VolumePolicy action is set as `snapshot` the backup workflow modifications would be:
|
||||
- Once again, We call [backupItem() -> backupItemInternal()](https://github.com/vmware-tanzu/velero/blob/main/pkg/backup/item_backupper.go#L95) on all the items that are to be backed up
|
||||
- Here when we encounter [Persistent Volume as an item](https://github.com/vmware-tanzu/velero/blob/d4128542590470b204a642ee43311921c11db880/pkg/backup/item_backupper.go#L253)
|
||||
- And we call the [takePVSnapshot func](https://github.com/vmware-tanzu/velero/blob/d4128542590470b204a642ee43311921c11db880/pkg/backup/item_backupper.go#L508)
|
||||
- We need to modify the takePVSnapshot function to account for the `snapshot` VolumePolicy action.
|
||||
- In case of csi snapshots for PVC objects, these snapshot actions are taken by the velero-plugin-for-csi, we need to modify the [executeActions()](https://github.com/vmware-tanzu/velero/blob/512fe0dabdcb3bbf1ca68a9089056ae549663bcf/pkg/backup/item_backupper.go#L232) function to account for the `snapshot` VolumePolicy action.
|
||||
|
||||
**Note:** `Snapshot` action can either be a native snapshot or a csi snapshot, as is the case with the current flow where velero itself makes the decision based on the backup CR.
|
||||
|
||||
## Detailed Design
|
||||
- Update VolumePolicy action type validation to account for `fs-backup` and `snapshot` as valid VolumePolicy actions.
|
||||
- Modifications needed for `fs-backup` action:
|
||||
- Now based on the specification of volume policy on backup request we will decide whether to go via legacy pod annotations approach or the newer volume policy based fs-backup action approach.
|
||||
- If there is a presence of volume policy(fs-backup/snapshot) on the backup request that matches as an action for a volume we use the newer volume policy approach to get the list of the volumes for `fs-backup` action
|
||||
- Else continue with the annotation based legacy approach workflow.
|
||||
|
||||
- Modifications needed for `snapshot` action:
|
||||
- In the [takePVSnapshot function](https://github.com/vmware-tanzu/velero/blob/d4128542590470b204a642ee43311921c11db880/pkg/backup/item_backupper.go#L508) we will check the PV fits the volume policy criteria and see if the associated action is `snapshot`
|
||||
- If it is not snapshot then we skip the further workflow and avoid taking the snapshot of the PV
|
||||
- Similarly, For csi snapshot of PVC object, we need to do similar changes in [executeAction() function](https://github.com/vmware-tanzu/velero/blob/512fe0dabdcb3bbf1ca68a9089056ae549663bcf/pkg/backup/item_backupper.go#L348). we will check the PVC fits the volume policy criteria and see if the associated action is `snapshot` via csi plugin
|
||||
- If it is not snapshot then we skip the csi BIA execute action and avoid taking the snapshot of the PVC by not invoking the csi plugin action for the PVC
|
||||
|
||||
**Note:**
|
||||
- When we are using the `VolumePolicy` approach for backing up the volumes then the volume policy criteria and action need to be specific and explicit, there is no default behavior, if a volume matches `fs-backup` action then `fs-backup` method will be used for that volume and similarly if the volume matches the criteria for `snapshot` action then the snapshot workflow will be used for the volume backup.
|
||||
- Another thing to note is the workflow proposed in this design uses the legacy `opt-in/opt-out` approach as a fallback option. For instance, the user specifies a VolumePolicy but for a particular volume included in the backup there are no actions(fs-backup/snapshot) matching in the volume policy for that volume, in such a scenario the legacy approach will be used for backing up the particular volume.
|
||||
- The relation between the `VolumePolicy` and the backup's legacy parameter `SnapshotVolumes`:
|
||||
- The `VolumePolicy`'s `snapshot` action matching for volume has higher priority. When there is a `snapshot` action matching for the selected volume, it will be backed by the snapshot way, no matter of the `backup.Spec.SnapshotVolumes` setting.
|
||||
- If there is no `snapshot` action matching the selected volume in the `VolumePolicy`, then the volume will be backed up by `snapshot` way, if the `backup.Spec.SnapshotVolumes` is not set to false.
|
||||
- The relation between the `VolumePolicy` and the backup's legacy filesystem `opt-in/opt-out` approach:
|
||||
- The `VolumePolicy`'s `fs-backup` action matching for volume has higher priority. When there is a `fs-backup` action matching for the selected volume, it will be backed by the fs-backup way, no matter of the `backup.Spec.DefaultVolumesToFsBackup` setting and the pod's `opt-in/opt-out` annotation setting.
|
||||
- If there is no `fs-backup` action matching the selected volume in the `VolumePolicy`, then the volume will be backed up by the legacy `opt-in/opt-out` way.
|
||||
|
||||
## Implementation
|
||||
|
||||
- The implementation should be included in velero 1.14
|
||||
|
||||
- We will introduce a `VolumeHelper` interface. It will consist of two methods:
|
||||
```go
|
||||
type VolumeHelper interface {
|
||||
ShouldPerformSnapshot(obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error)
|
||||
ShouldPerformFSBackup(volume corev1api.Volume, pod corev1api.Pod) (bool, error)
|
||||
}
|
||||
```
|
||||
- The `VolumeHelperImpl` struct will implement the `VolumeHelper` interface and will consist of the functions that we will use through the backup workflow to accommodate volume policies for PVs and PVCs.
|
||||
```go
|
||||
type volumeHelperImpl struct {
|
||||
volumePolicy *resourcepolicies.Policies
|
||||
snapshotVolumes *bool
|
||||
logger logrus.FieldLogger
|
||||
client crclient.Client
|
||||
defaultVolumesToFSBackup bool
|
||||
backupExcludePVC bool
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- We will create an instance of the structure `volumeHelperImpl` in `item_backupper.go`
|
||||
```go
|
||||
itemBackupper := &itemBackupper{
|
||||
...
|
||||
volumeHelperImpl: volumehelper.NewVolumeHelperImpl(
|
||||
resourcePolicy,
|
||||
backupRequest.Spec.SnapshotVolumes,
|
||||
log,
|
||||
kb.kbClient,
|
||||
boolptr.IsSetToTrue(backupRequest.Spec.DefaultVolumesToFsBackup),
|
||||
!backupRequest.ResourceIncludesExcludes.ShouldInclude(kuberesource.PersistentVolumeClaims.String()),
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### FS-Backup
|
||||
- Regarding `fs-backup` action to decide whether to use legacy annotation based approach or volume policy based approach:
|
||||
- We will use the `vh.ShouldPerformFSBackup()` function from the `volumehelper` package
|
||||
- Functions involved in processing `fs-backup` volume policy action will somewhat look like:
|
||||
|
||||
```go
|
||||
func (v volumeHelperImpl) ShouldPerformFSBackup(volume corev1api.Volume, pod corev1api.Pod) (bool, error) {
|
||||
if !v.shouldIncludeVolumeInBackup(volume) {
|
||||
v.logger.Debugf("skip fs-backup action for pod %s's volume %s, due to not pass volume check.", pod.Namespace+"/"+pod.Name, volume.Name)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if v.volumePolicy != nil {
|
||||
pvc, err := kubeutil.GetPVCForPodVolume(&volume, &pod, v.client)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get PVC for pod %s", pod.Namespace+"/"+pod.Name)
|
||||
return false, err
|
||||
}
|
||||
pv, err := kubeutil.GetPVForPVC(pvc, v.client)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get PV for PVC %s", pvc.Namespace+"/"+pvc.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
action, err := v.volumePolicy.GetMatchAction(pv)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get VolumePolicy match action for PV %s", pv.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if action != nil {
|
||||
if action.Type == resourcepolicies.FSBackup {
|
||||
v.logger.Infof("Perform fs-backup action for volume %s of pod %s due to volume policy match",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name)
|
||||
return true, nil
|
||||
} else {
|
||||
v.logger.Infof("Skip fs-backup action for volume %s for pod %s because the action type is %s",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name, action.Type)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.shouldPerformFSBackupLegacy(volume, pod) {
|
||||
v.logger.Infof("Perform fs-backup action for volume %s of pod %s due to opt-in/out way",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name)
|
||||
return true, nil
|
||||
} else {
|
||||
v.logger.Infof("Skip fs-backup action for volume %s of pod %s due to opt-in/out way",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The main function from the above will be called when we encounter Pods during the backup workflow:
|
||||
```go
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
shouldDoFSBackup, err := ib.volumeHelperImpl.ShouldPerformFSBackup(volume, *pod)
|
||||
if err != nil {
|
||||
backupErrs = append(backupErrs, errors.WithStack(err))
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Snapshot (PV)
|
||||
|
||||
- Making sure that `snapshot` action is skipped for PVs that do not fit the volume policy criteria, for this we will use the `vh.ShouldPerformSnapshot` from the `VolumeHelperImpl(vh)` receiver.
|
||||
```go
|
||||
func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error) {
|
||||
// check if volume policy exists and also check if the object(pv/pvc) fits a volume policy criteria and see if the associated action is snapshot
|
||||
// if it is not snapshot then skip the code path for snapshotting the PV/PVC
|
||||
pvc := new(corev1api.PersistentVolumeClaim)
|
||||
pv := new(corev1api.PersistentVolume)
|
||||
var err error
|
||||
|
||||
if groupResource == kuberesource.PersistentVolumeClaims {
|
||||
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &pvc); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
pv, err = kubeutil.GetPVForPVC(pvc, v.client)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.PersistentVolumes {
|
||||
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &pv); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if v.volumePolicy != nil {
|
||||
action, err := v.volumePolicy.GetMatchAction(pv)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If there is a match action, and the action type is snapshot, return true,
|
||||
// or the action type is not snapshot, then return false.
|
||||
// If there is no match action, go on to the next check.
|
||||
if action != nil {
|
||||
if action.Type == resourcepolicies.Snapshot {
|
||||
v.logger.Infof(fmt.Sprintf("performing snapshot action for pv %s", pv.Name))
|
||||
return true, nil
|
||||
} else {
|
||||
v.logger.Infof("Skip snapshot action for pv %s as the action type is %s", pv.Name, action.Type)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this PV is claimed, see if we've already taken a (pod volume backup)
|
||||
// snapshot of the contents of this PV. If so, don't take a snapshot.
|
||||
if pv.Spec.ClaimRef != nil {
|
||||
pods, err := podvolumeutil.GetPodsUsingPVC(
|
||||
pv.Spec.ClaimRef.Namespace,
|
||||
pv.Spec.ClaimRef.Name,
|
||||
v.client,
|
||||
)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get pod for PV %s", pv.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, pod := range pods {
|
||||
for _, vol := range pod.Spec.Volumes {
|
||||
if vol.PersistentVolumeClaim != nil &&
|
||||
vol.PersistentVolumeClaim.ClaimName == pv.Spec.ClaimRef.Name &&
|
||||
v.shouldPerformFSBackupLegacy(vol, pod) {
|
||||
v.logger.Infof("Skipping snapshot of pv %s because it is backed up with PodVolumeBackup.", pv.Name)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !boolptr.IsSetToFalse(v.snapshotVolumes) {
|
||||
// If the backup.Spec.SnapshotVolumes is not set, or set to true, then should take the snapshot.
|
||||
v.logger.Infof("performing snapshot action for pv %s as the snapshotVolumes is not set to false")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
v.logger.Infof(fmt.Sprintf("skipping snapshot action for pv %s possibly due to no volume policy setting or snapshotVolumes is false", pv.Name))
|
||||
return false, nil
|
||||
}
|
||||
```
|
||||
|
||||
- The function `ShouldPerformSnapshot` will be used as follows in `takePVSnapshot` function of the backup workflow:
|
||||
```go
|
||||
snapshotVolume, err := ib.volumeHelperImpl.ShouldPerformSnapshot(obj, kuberesource.PersistentVolumes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !snapshotVolume {
|
||||
log.Info(fmt.Sprintf("skipping volume snapshot for PV %s as it does not fit the volume policy criteria specified by the user for snapshot action", pv.Name))
|
||||
ib.trackSkippedPV(obj, kuberesource.PersistentVolumes, volumeSnapshotApproach, "does not satisfy the criteria for volume policy based snapshot action", log)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Snapshot (PVC)
|
||||
|
||||
- Making sure that `snapshot` action is skipped for PVCs that do not fit the volume policy criteria, for this we will again use the `vh.ShouldPerformSnapshot` from the `VolumeHelperImpl(vh)` receiver.
|
||||
- We will pass the `VolumeHelperImpl(vh)` instance in `executeActions` method so that it is available to use.
|
||||
```go
|
||||
|
||||
```
|
||||
- The above function will be used as follows in the `executeActions` function of backup workflow.
|
||||
- Considering the vSphere plugin doesn't support the VolumePolicy yet, don't use the VolumePolicy for vSphere plugin by now.
|
||||
```go
|
||||
if groupResource == kuberesource.PersistentVolumeClaims {
|
||||
if actionName == csiBIAPluginName {
|
||||
snapshotVolume, err := ib.volumeHelperImpl.ShouldPerformSnapshot(obj, kuberesource.PersistentVolumeClaims)
|
||||
if err != nil {
|
||||
return nil, itemFiles, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if !snapshotVolume {
|
||||
log.Info(fmt.Sprintf("skipping csi volume snapshot for PVC %s as it does not fit the volume policy criteria specified by the user for snapshot action", namespace+"/"+name))
|
||||
ib.trackSkippedPV(obj, kuberesource.PersistentVolumeClaims, volumeSnapshotApproach, "does not satisfy the criteria for volume policy based snapshot action", log)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Future Implementation
|
||||
It makes sense to add more specific actions in the future, once we deprecate the legacy opt-in/opt-out approach to keep things simple. Another point of note is, csi related action can be
|
||||
easier to implement once we decide to merge the csi plugin into main velero code flow.
|
||||
In the future, we envision the following actions that can be implemented:
|
||||
- `snapshot-native`: only use volume snapshotter (native cloud provider snapshots), do nothing if not present/not compatible
|
||||
- `snapshot-csi`: only use csi-plugin, don't use volume snapshotter(native cloud provider snapshots), don't use datamover even if snapshotMoveData is true
|
||||
- `snapshot-datamover`: only use csi with datamover, don't use volume snapshotter (native cloud provider snapshots), use datamover even if snapshotMoveData is false
|
||||
|
||||
**Note:** The above actions are just suggestions for future scope, we may not use/implement them as is. We could definitely merge these suggested actions as `Snapshot` actions and use volume policy parameters and criteria to segregate them instead of making the user explicitly supply the action names to such granular levels.
|
||||
|
||||
## Related to Design
|
||||
|
||||
[Handle backup of volumes by resources filters](https://github.com/vmware-tanzu/velero/blob/main/design/Implemented/handle-backup-of-volumes-by-resources-filters.md)
|
||||
|
||||
## Alternatives Considered
|
||||
Same as the earlier design as this is an extension of the original VolumePolicies design
|
||||
@@ -65,7 +65,7 @@ This page contains a pre-migration checklist for ensuring a repo migration goes
|
||||
|
||||
#### Updating Netlify
|
||||
|
||||
The settings for Netlify should remain the same, except that it now needs to be installed in the new repo. The instructions on how to install Netlify on the new repo are here: https://www.netlify.com/docs/github-permissions/.
|
||||
The settings for Netflify should remain the same, except that it now needs to be installed in the new repo. The instructions on how to install Netlify on the new repo are here: https://www.netlify.com/docs/github-permissions/.
|
||||
|
||||
#### Communication strategy
|
||||
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
# Node-agent Load Affinity Design
|
||||
|
||||
## Glossary & Abbreviation
|
||||
|
||||
**Velero Generic Data Path (VGDP)**: VGDP is the collective modules that is introduced in [Unified Repository design][1]. Velero uses these modules to finish data transfer for various purposes (i.e., PodVolume backup/restore, Volume Snapshot Data Movement). VGDP modules include uploaders and the backup repository.
|
||||
|
||||
**Exposer**: Exposer is a module that is introduced in [Volume Snapshot Data Movement Design][2]. Velero uses this module to expose the volume snapshots to Velero node-agent pods or node-agent associated pods so as to complete the data movement from the snapshots.
|
||||
|
||||
## Background
|
||||
|
||||
Velero node-agent is a daemonset hosting controllers and VGDP modules to complete the concrete work of backups/restores, i.e., PodVolume backup/restore, Volume Snapshot Data Movement backup/restore.
|
||||
Specifically, node-agent runs DataUpload controllers to watch DataUpload CRs for Volume Snapshot Data Movement backups, so there is one controller instance in each node. One controller instance takes a DataUpload CR and then launches a VGDP instance, which initializes a uploader instance and the backup repository connection, to finish the data transfer. The VGDP instance runs inside a node-agent pod or in a pod associated to the node-agent pod in the same node.
|
||||
|
||||
Varying from the data size, data complexity, resource availability, VGDP may take a long time and remarkable resources (CPU, memory, network bandwidth, etc.).
|
||||
Technically, VGDP instances are able to run in any node that allows pod schedule. On the other hand, users may want to constrain the nodes where VGDP instances run for various reasons, below are some examples:
|
||||
- Prevent VGDP instances from running in specific nodes because users have more critical workloads in the nodes
|
||||
- Constrain VGDP instances to run in specific nodes because these nodes have more resources than others
|
||||
- Constrain VGDP instances to run in specific nodes because the storage allows volume/snapshot provisions in these nodes only
|
||||
|
||||
Therefore, in order to improve the compatibility, it is worthy to configure the affinity of VGDP to nodes, especially for backups for which VGDP instances run frequently and centrally.
|
||||
|
||||
## Goals
|
||||
|
||||
- Define the behaviors of node affinity of VGDP instances in node-agent for volume snapshot data movement backups
|
||||
- Create a mechanism for users to specify the node affinity of VGDP instances for volume snapshot data movement backups
|
||||
|
||||
## Non-Goals
|
||||
- It is also beneficial to support VGDP instances affinity for PodVolume backup/restore, however, it is not possible since VGDP instances for PodVolume backup/restore should always run in the node where the source/target pods are created.
|
||||
- It is also beneficial to support VGDP instances affinity for data movement restores, however, it is not possible in some cases. For example, when the `volumeBindingMode` in the storageclass is `WaitForFirstConsumer`, the restore volume must be mounted in the node where the target pod is scheduled, so the VGDP instance must run in the same node. On the other hand, considering the fact that restores may not frequently and centrally run, we will not support data movement restores.
|
||||
- As elaberated in the [Volume Snapshot Data Movement Design][2], the Exposer may take different ways to expose snapshots, i.e., through backup pods (this is the only way supported at present). The implementation section below only considers this approach currently, if a new expose method is introduced in future, the definition of the affinity configurations and behaviors should still work, but we may need a new implementation.
|
||||
|
||||
## Solution
|
||||
|
||||
We will use the ```node-agent-config``` configMap to host the node affinity configurations.
|
||||
This configMap is not created by Velero, users should create it manually on demand. The configMap should be in the same namespace where Velero is installed. If multiple Velero instances are installed in different namespaces, there should be one configMap in each namespace which applies to node-agent in that namespace only.
|
||||
Node-agent server checks these configurations at startup time and use it to initiate the related VGDP modules. Therefore, users could edit this configMap any time, but in order to make the changes effective, node-agent server needs to be restarted.
|
||||
Inside ```node-agent-config``` configMap we will add one new kind of configuration as the data in the configMap, the name is ```loadAffinity```.
|
||||
Users may want to set different LoadAffinity configurations according to different conditions (i.e., for different storages represented by StorageClass, CSI driver, etc.), so we define ```loadAffinity``` as an array. This is for extensibility consideration, at present, we don't implement multiple configurations support, so if there are multiple configurations, we always take the first one in the array.
|
||||
|
||||
The data structure for ```node-agent-config``` is as below:
|
||||
```go
|
||||
type Configs struct {
|
||||
// LoadConcurrency is the config for load concurrency per node.
|
||||
LoadConcurrency *LoadConcurrency `json:"loadConcurrency,omitempty"`
|
||||
|
||||
// LoadAffinity is the config for data path load affinity.
|
||||
LoadAffinity []*LoadAffinity `json:"loadAffinity,omitempty"`
|
||||
}
|
||||
|
||||
type LoadAffinity struct {
|
||||
// NodeSelector specifies the label selector to match nodes
|
||||
NodeSelector metav1.LabelSelector `json:"nodeSelector"`
|
||||
}
|
||||
```
|
||||
|
||||
### Affinity
|
||||
Affinity configuration means allowing VGDP instances running in the nodes specified. There are two ways to define it:
|
||||
- It could be defined by `MatchLabels` of `metav1.LabelSelector`. The labels defined in `MatchLabels` means a `LabelSelectorOpIn` operation by default, so in the current context, they will be treated as affinity rules.
|
||||
- It could be defined by `MatchExpressions` of `metav1.LabelSelector`. The labels are defined in `Key` and `Values` of `MatchExpressions` and the `Operator` should be defined as `LabelSelectorOpIn` or `LabelSelectorOpExists`.
|
||||
|
||||
### Anti-affinity
|
||||
Anti-affinity configuration means preventing VGDP instances running in the nodes specified. Below is the way to define it:
|
||||
- It could be defined by `MatchExpressions` of `metav1.LabelSelector`. The labels are defined in `Key` and `Values` of `MatchExpressions` and the `Operator` should be defined as `LabelSelectorOpNotIn` or `LabelSelectorOpDoesNotExist`.
|
||||
|
||||
### Sample
|
||||
A sample of the ```node-agent-config``` configMap is as below:
|
||||
```json
|
||||
{
|
||||
"loadAffinity": [
|
||||
{
|
||||
"nodeSelector": {
|
||||
"matchLabels": {
|
||||
"beta.kubernetes.io/instance-type": "Standard_B4ms"
|
||||
},
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "kubernetes.io/hostname",
|
||||
"values": [
|
||||
"node-1",
|
||||
"node-2",
|
||||
"node-3"
|
||||
],
|
||||
"operator": "In"
|
||||
},
|
||||
{
|
||||
"key": "xxx/critial-workload",
|
||||
"operator": "DoesNotExist"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
This sample showcases two affinity configurations:
|
||||
- matchLabels: VGDP instances will run only in nodes with label key `beta.kubernetes.io/instance-type` and value `Standard_B4ms`
|
||||
- matchExpressions: VGDP instances will run in node `node1`, `node2` and `node3` (selected by `kubernetes.io/hostname` label)
|
||||
|
||||
This sample showcases one anti-affinity configuration:
|
||||
- matchExpressions: VGDP instances will not run in nodes with label key `xxx/critial-workload`
|
||||
|
||||
To create the configMap, users need to save something like the above sample to a json file and then run below command:
|
||||
```
|
||||
kubectl create cm node-agent-config -n velero --from-file=<json file name>
|
||||
```
|
||||
|
||||
### Implementation
|
||||
As mentioned in the [Volume Snapshot Data Movement Design][2], the exposer decides where to launch the VGDP instances. At present, for volume snapshot data movement backups, the exposer creates backupPods and the VGDP instances will be initiated in the nodes where backupPods are scheduled. So the loadAffinity will be translated (from `metav1.LabelSelector` to `corev1.Affinity`) and set to the backupPods.
|
||||
|
||||
It is possible that node-agent pods, as a daemonset, don't run in every worker nodes, users could fulfil this by specify `nodeSelector` or `nodeAffinity` to the node-agent daemonset spec. On the other hand, at present, VGDP instances must be assigned to nodes where node-agent pods are running. Therefore, if there is any node selection for node-agent pods, users must consider this into this load affinity configuration, so as to guarantee that VGDP instances are always assigned to nodes where node-agent pods are available. This is done by users, we don't inherit any node selection configuration from node-agent daemonset as we think daemonset scheduler works differently from plain pod scheduler, simply inheriting all the configurations may cause unexpected result of backupPod schedule.
|
||||
Otherwise, if a backupPod are scheduled to a node where node-agent pod is absent, the corresponding DataUpload CR will stay in `Accepted` phase until the prepare timeout (by default 30min).
|
||||
|
||||
At present, as part of the expose operations, the exposer creates a volume, represented by backupPVC, from the snapshot. The backupPVC uses the same storageClass with the source volume. If the `volumeBindingMode` in the storageClass is `Immediate`, the volume is immediately allocated from the underlying storage without waiting for the backupPod. On the other hand, the loadAffinity is set to the backupPod's affinity. If the backupPod is scheduled to a node where the snapshot volume is not accessible, e.g., because of storage topologies, the backupPod won't get into Running state, concequently, the data movement won't complete.
|
||||
Once this problem happens, the backupPod stays in `Pending` phase, and the corresponding DataUpload CR stays in `Accepted` phase until the prepare timeout (by default 30min).
|
||||
|
||||
There is a common solution for the both problems:
|
||||
- We have an existing logic to periodically enqueue the dataupload CRs which are in the `Accepted` phase for timeout and cancel checks
|
||||
- We add a new logic to this existing logic to check if the corresponding backupPods are in unrecoverable status
|
||||
- The above problems could be covered by this check, because in both cases the backupPods are in abnormal and unrecoverable status
|
||||
- If a backupPod is unrecoverable, the dataupload controller cancels the dataupload and deletes the backupPod
|
||||
|
||||
Specifically, when the above problems happen, the status of a backupPod is like below:
|
||||
```
|
||||
status:
|
||||
conditions:
|
||||
- lastProbeTime: null
|
||||
message: '0/2 nodes are available: 1 node(s) didn''t match Pod''s node affinity/selector,
|
||||
1 node(s) had volume node affinity conflict. preemption: 0/2 nodes are available:
|
||||
2 Preemption is not helpful for scheduling..'
|
||||
reason: Unschedulable
|
||||
status: "False"
|
||||
type: PodScheduled
|
||||
phase: Pending
|
||||
```
|
||||
|
||||
[1]: Implemented/unified-repo-and-kopia-integration/unified-repo-and-kopia-integration.md
|
||||
[2]: volume-snapshot-data-movement/volume-snapshot-data-movement.md
|
||||
@@ -1,143 +0,0 @@
|
||||
# Volume information for restore design
|
||||
|
||||
## Background
|
||||
Velero has different ways to handle data in the volumes during restore. The users want to have more clarity in terms of how
|
||||
the volumes are handled in restore process via either Velero CLI or other downstream product which consumes Velero.
|
||||
|
||||
## Goals
|
||||
- Create new metadata to store the information of the restored volume, which will have the same life-cycle as the restore CR.
|
||||
- Consume the metadata in velero CLI to enable it display more details for volumes in the output of `velero restore describe --details`
|
||||
|
||||
## Non Goals
|
||||
- Provide finer grained control of the volume restore process. The focus of the design is to enable displaying more details.
|
||||
- Persist additional metadata like podvolume, datadownloads etc to the restore folder in backup-location.
|
||||
|
||||
## Design
|
||||
|
||||
### Structure of the restore volume info
|
||||
The restore volume info will be stored in a file named like `${restore_name}-vol-info.json`. The content of the file will
|
||||
be a list of volume info objects, each of which will map to a volume that is restored, and will contain the information
|
||||
like name of the restored PV/PVC, restore method and related objects to provide details depending on the way it's restored,
|
||||
it will look like this:
|
||||
```
|
||||
[
|
||||
{
|
||||
"pvcName": "nginx-logs-2",
|
||||
"pvcNamespace": "nginx-app-restore",
|
||||
"pvName": "pvc-e320d75b-a788-41a3-b6ba-267a553efa5e",
|
||||
"restoreMethod": "PodVolumeRestore",
|
||||
"snapshotDataMoved": false,
|
||||
"pvrInfo": {
|
||||
"snapshotHandle": "81973157c3a945a5229285c931b02c68",
|
||||
"uploaderType": "kopia",
|
||||
"volumeName": "nginx-logs",
|
||||
"podName": "nginx-deployment-79b56c644b-mjdhp",
|
||||
"podNamespace": "nginx-app-restore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pvcName": "nginx-logs-1",
|
||||
"pvcNamespace": "nginx-app-restore",
|
||||
"pvName": "pvc-98c151f4-df47-4980-ba6d-470842f652cc",
|
||||
"restoreMethod": "CSISnapshot",
|
||||
"snapshotDataMoved": false,
|
||||
"csiSnapshotInfo": {
|
||||
"snapshotHandle": "snap-01a3b21a5e9f85528",
|
||||
"size": 2147483648,
|
||||
"driver": "ebs.csi.aws.com",
|
||||
"vscName": "velero-velero-nginx-logs-1-jxmbg-hx9x5"
|
||||
}
|
||||
}
|
||||
......
|
||||
]
|
||||
```
|
||||
Each field will have the same meaning as the corresponding field in the backup volume info. It will not have the fields
|
||||
that were introduced to help with the backup process, like `pvInfo`, `dataupload` etc.
|
||||
|
||||
### How the restore volume info is generated
|
||||
Two steps are involved in generating the restore volume info, the first is "collection", which is to gather the information
|
||||
for restoration of the volumes, the second is "generation", which is to iterate through the data collected in the first step
|
||||
and generate the volume info list as is described above.
|
||||
|
||||
Unlike backup, the CR objects created during the restore process will not be persisted to the backup storage location.
|
||||
Therefore, to gather the information needed to generate volume information, we either need to collect the CRs in the middle
|
||||
of the restore process, or retrieve the objects based on the `resouce-list.json` of the restore via API server.
|
||||
The information to be collected are:
|
||||
- **PV/PVC mapping relationship:** It will be collected via the `restore-resource-list.json`, b/c at the time the json is ready, all
|
||||
PVCs and PVs are already created.
|
||||
- **Native snapshot information:** It will be collected in the restore workflow when each snapshot is restored.
|
||||
- **podvolumerestore CRs:** It will be collected in the restore workflow after each pvr is created.
|
||||
- **volumesnapshot CRs for CSI snapshot:** It will be collected in the step of collecting PVC info, by reading the `dataSource`
|
||||
field in the spec of the PVC.
|
||||
- **datadownload CRs** It will be collected in the phase of collecting PVC info, by querying the API-server to list the datadownload
|
||||
CRs labeled with the restore name.
|
||||
|
||||
After the collection step, the generation step is relatively straight-forward, as we have all the information needed in
|
||||
the data structures.
|
||||
|
||||
The whole collection and generation steps will be done with the "best-effort" manner, i.e. if there are any failures we
|
||||
will only log the error in restore log, rather than failing the whole restore process, we will not put these errors or warnings
|
||||
into the `result.json`, b/c it won't impact the restored resources.
|
||||
|
||||
Depending on the number of the restored PVCs the "collection" step may involve many API calls, but it's considered acceptable
|
||||
b/c at that time the resources are already created, so the actual RTO is not impacted. By using the client of controller runtime
|
||||
we can make the collection step more efficient by using the cache of the API server. We may consider to make improvements if
|
||||
we observe performance issues, like using multiple go-routines in the collection.
|
||||
|
||||
### Implementation
|
||||
Because the restore volume info shares the same data structures with the backup volume info, we will refactor the code in
|
||||
package `internal/volume` to make the sub-components in backup volume info shared by both backup and restore volume info.
|
||||
|
||||
We'll introduce a struct called `RestoreVolumeInfoTracker` which encapsulates the logic of collecting and generating the restore volume info:
|
||||
```
|
||||
// RestoreVolumeInfoTracker is used to track the volume information during restore.
|
||||
// It is used to generate the RestoreVolumeInfo array.
|
||||
type RestoreVolumeInfoTracker struct {
|
||||
*sync.Mutex
|
||||
restore *velerov1api.Restore
|
||||
log logrus.FieldLogger
|
||||
client kbclient.Client
|
||||
pvPvc *pvcPvMap
|
||||
|
||||
// map of PV name to the NativeSnapshotInfo from which the PV is restored
|
||||
pvNativeSnapshotMap map[string]NativeSnapshotInfo
|
||||
// map of PV name to the CSISnapshot object from which the PV is restored
|
||||
pvCSISnapshotMap map[string]snapshotv1api.VolumeSnapshot
|
||||
datadownloadList *velerov2alpha1.DataDownloadList
|
||||
pvrs []*velerov1api.PodVolumeRestore
|
||||
}
|
||||
```
|
||||
The `RestoreVolumeInfoTracker` will be created when the restore request is initialized, and it will be passed to the `restoreContext`
|
||||
and carried over the whole restore process.
|
||||
|
||||
The `client` in this struct is to be used to query the resources in the restored namespace, and the current client in restore
|
||||
reconciler only watches the resources in the namespace where velero is installed. Therefore, we need to introduce the
|
||||
`CrClient` which has the same life-cycle of velero server to the restore reconciler, because this is the client that watches all the
|
||||
resources on the cluster.
|
||||
|
||||
In addition to that, we will make small changes in the restore workflow to collect the information needed. We'll make the
|
||||
changes un-intrusive and make sure not to change the logic of the restore to avoid break change or regression.
|
||||
We'll also introduce routine changes in the package `pkg/persistence` to persist the restore volume info to the backup storage location.
|
||||
|
||||
Last but not least, the `velero restore describe --details` will be updated to display the volume info in the output.
|
||||
|
||||
## Alternatives Considered
|
||||
There used to be suggestion that to provide more details about volume, we can query the `backup-vol-info.json` with the resource
|
||||
identifier in `restore-resource-list.json`. This will not work when there're resource modifiers involved in the restore process,
|
||||
which may change the metadata of PVC/PV. In addition, we may add more detailed restore-specific information about the volumes that is not available
|
||||
in the `backup-vol-info.json`. Therefore, the `restore-vol-info.json` is a better approach.
|
||||
|
||||
## Security Considerations
|
||||
There should be no security impact introduced by this design.
|
||||
|
||||
## Compatibility
|
||||
The restore volume info will be consumed by Velero CLI and downstream products for displaying details. So the functionality
|
||||
of backup and restore will not be impacted for restores created by older versions of Velero which do not have the restore volume info
|
||||
metadata. The client should properly handle the case when the restore volume info does not exist.
|
||||
|
||||
The data structures referenced by volume info is shared between both restore and backup and it's not versioned, so in the future
|
||||
we must make sure there will only be incremental changes to the metadata, such that no break change will be introduced to the client.
|
||||
|
||||
## Open Issues
|
||||
https://github.com/vmware-tanzu/velero/issues/7546
|
||||
https://github.com/vmware-tanzu/velero/issues/6478
|
||||
@@ -1,318 +0,0 @@
|
||||
# Design for repository maintenance job
|
||||
|
||||
## Abstract
|
||||
This design proposal aims to decouple repository maintenance from the Velero server by launching a maintenance job when needed, to mitigate the impact on the Velero server during backups.
|
||||
|
||||
## Background
|
||||
During backups, Velero performs periodic maintenance on the repository. This operation may consume significant CPU and memory resources in some cases, leading to potential issues such as the Velero server being killed by OOM. This proposal addresses these challenges by separating repository maintenance from the Velero server.
|
||||
|
||||
## Goals
|
||||
1. **Independent Repository Maintenance**: Decouple maintenance from Velero's main logic to reduce the impact on the Velero server pod.
|
||||
|
||||
2. **Configurable Resources Usage**: Make the resources used by the maintenance job configurable.
|
||||
|
||||
3. **No API Changes**: Retain existing APIs and workflow in the backup repository controller.
|
||||
|
||||
## Non Goals
|
||||
We have lots of concerns over parallel maintenance, which will increase the complexity of our design currently.
|
||||
|
||||
- Non-blocking maintenance job: it may conflict with updating the same `backuprepositories` CR when parallel maintenance.
|
||||
|
||||
- Maintenance job concurrency control: there is no one suitable mechanism in Kubernetes to control the concurrency of different jobs.
|
||||
|
||||
- Parallel maintenance: Maintaining the same repo by multiple jobs at the same time would have some compatible cases that some providers may not support.
|
||||
|
||||
Unfortunately, parallel maintenance is currently not a priority because of the concerns above, improving maintenance efficiency is not the primary focus at this stage.
|
||||
|
||||
## High-Level Design
|
||||
1. **Add Maintenance Subcommand**: Introduce a new Velero server subcommand for repository maintenance.
|
||||
|
||||
2. **Create Jobs by Repository Manager**: Modify the backup repository controller to create a maintenance job instead of directly calling the multiple chain calls for Kopia or Restic maintenance.
|
||||
|
||||
3. **Update Maintenance Job Result in BackupRepository CR**: Retrieve the result of the maintenance job and update the status of the `BackupRepository` CR accordingly.
|
||||
|
||||
4. **Add Setting for Maintenance Job**: Introduce a configuration option to set maintenance jobs, including resource limits (CPU and memory), keeping the latest N maintenance jobs for each repository.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### 1. Add Maintenance sub-command
|
||||
|
||||
The CLI command will be added to the Velero CLI, the command is designed for use in a pod of maintenance jobs.
|
||||
|
||||
Our CLI command is designed as follows:
|
||||
```shell
|
||||
$ velero repo-maintenance --repo-name $repo-name --repo-type $repo-type --backup-storage-location $bsl
|
||||
```
|
||||
|
||||
Compared with other CLI commands, the maintenance command is used in a pod of maintenance jobs not for user use, and the job should show the result of maintenance after finish.
|
||||
|
||||
Here we will write the error message into one specific file which could be read by the maintenance job.
|
||||
|
||||
on the whole, we record two kinds of logs:
|
||||
|
||||
- one is the log output of the intermediate maintenance process: this log could be retrieved via the Kubernetes API server, including the error log.
|
||||
|
||||
- one is the result of the command which could indicate whether the execution is an error or not: the result could be redirected to a file that the maintenance job itself could read, and the file only contains the error message.
|
||||
|
||||
we will write the error message into the `/dev/termination-log` file if execution is failed.
|
||||
|
||||
The main maintenance logic would be using the repository provider to do the maintenance.
|
||||
|
||||
```golang
|
||||
func checkError(err error, file *os.File) {
|
||||
if err != nil {
|
||||
if err != context.Canceled {
|
||||
if _, errWrite := file.WriteString(fmt.Sprintf("An error occurred: %v", err)); errWrite != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write error to termination log file: %v\n", errWrite)
|
||||
}
|
||||
file.Close()
|
||||
os.Exit(1) // indicate the command executed failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Options) Run(f veleroCli.Factory) {
|
||||
logger := logging.DefaultLogger(o.LogLevelFlag.Parse(), o.FormatFlag.Parse())
|
||||
logger.SetOutput(os.Stdout)
|
||||
|
||||
errorFile, err := os.Create("/dev/termination-log")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create termination log file: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer errorFile.Close()
|
||||
...
|
||||
|
||||
err = o.runRepoPrune(cli, f.Namespace(), logger)
|
||||
checkError(err, errorFile)
|
||||
...
|
||||
}
|
||||
|
||||
func (o *Options) runRepoPrune(cli client.Client, namespace string, logger logrus.FieldLogger) error {
|
||||
...
|
||||
var repoProvider provider.Provider
|
||||
if o.RepoType == velerov1api.BackupRepositoryTypeRestic {
|
||||
repoProvider = provider.NewResticRepositoryProvider(credentialFileStore, filesystem.NewFileSystem(), logger)
|
||||
} else {
|
||||
repoProvider = provider.NewUnifiedRepoProvider(
|
||||
credentials.CredentialGetter{
|
||||
FromFile: credentialFileStore,
|
||||
FromSecret: credentialSecretStore,
|
||||
}, o.RepoType, cli, logger)
|
||||
}
|
||||
...
|
||||
|
||||
err = repoProvider.BoostRepoConnect(context.Background(), para)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to boost repo connect")
|
||||
}
|
||||
|
||||
err = repoProvider.PruneRepo(context.Background(), para)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to prune repo")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Jobs by Repository Manager
|
||||
Currently, the backup repository controller will call the repository manager to do the `PruneRepo`, and Kopia or Restic maintenance is then finally called through multiple chain calls.
|
||||
|
||||
We will keep using the `PruneRepo` function in the repository manager, but we cut off the multiple chain calls by creating a maintenance job.
|
||||
|
||||
The job definition would be like below:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
items:
|
||||
- apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
# labels or affinity or topology settings would inherit from the velero deployment
|
||||
labels:
|
||||
# label the job name for later list jobs by name
|
||||
job-name: nginx-example-default-kopia-pqz6c
|
||||
name: nginx-example-default-kopia-pqz6c
|
||||
namespace: velero
|
||||
spec:
|
||||
# Not retry it again
|
||||
backoffLimit: 1
|
||||
# Only have one job one time
|
||||
completions: 1
|
||||
# Not parallel running job
|
||||
parallelism: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
job-name: nginx-example-default-kopia-pqz6c
|
||||
name: kopia-maintenance-job
|
||||
spec:
|
||||
containers:
|
||||
# arguments for repo maintenance job
|
||||
- args:
|
||||
- repo-maintenance
|
||||
- --repo-name=nginx-example
|
||||
- --repo-type=kopia
|
||||
- --backup-storage-location=default
|
||||
# inherit from Velero server
|
||||
- --log-level=debug
|
||||
command:
|
||||
- /velero
|
||||
# inherit environment variables from the velero deployment
|
||||
env:
|
||||
- name: AZURE_CREDENTIALS_FILE
|
||||
value: /credentials/cloud
|
||||
# inherit image from the velero deployment
|
||||
image: velero/velero:main
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: kopia-maintenance-container
|
||||
# resource limitation set by Velero server configuration
|
||||
# if not specified, it would apply best effort resources allocation strategy
|
||||
resources: {}
|
||||
# error message would be written to /dev/termination-log
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
# inherit volume mounts from the velero deployment
|
||||
volumeMounts:
|
||||
- mountPath: /credentials
|
||||
name: cloud-credentials
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Never
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
# inherit service account from the velero deployment
|
||||
serviceAccount: velero
|
||||
serviceAccountName: velero
|
||||
volumes:
|
||||
# inherit cloud credentials from the velero deployment
|
||||
- name: cloud-credentials
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: cloud-credentials
|
||||
# ttlSecondsAfterFinished set the job expired seconds
|
||||
ttlSecondsAfterFinished: 86400
|
||||
status:
|
||||
# which contains the result after maintenance
|
||||
message: ""
|
||||
lastMaintenanceTime: ""
|
||||
```
|
||||
|
||||
Now, the backup repository controller will call the repository manager to create one maintenance job and wait for the job to complete. The Kopia or Restic maintenance multiple chains are called by the job.
|
||||
|
||||
### 3. Update the Result of the Maintenance Job into BackupRepository CR
|
||||
|
||||
The backup repository controller will update the result of the maintenance job into the backup repository CR.
|
||||
|
||||
For how to get the result of the maintenance job we could refer to [here](https://kubernetes.io/docs/tasks/debug/debug-application/determine-reason-pod-failure/#writing-and-reading-a-termination-message).
|
||||
|
||||
After the maintenance job is finished, we could get the result of maintenance by getting the terminated message from the related pod:
|
||||
|
||||
```golang
|
||||
func GetContainerTerminatedMessage(pod *v1.Pod) string {
|
||||
...
|
||||
for _, containerStatus := range pod.Status.ContainerStatuses {
|
||||
if containerStatus.LastTerminationState.Terminated != nil {
|
||||
return containerStatus.LastTerminationState.Terminated.Message
|
||||
}
|
||||
}
|
||||
...
|
||||
return ""
|
||||
}
|
||||
```
|
||||
Then we could update the status of backupRepository CR with the message.
|
||||
|
||||
### 4. Add Setting for Resource Usage of Maintenance
|
||||
Add one configuration for setting the resource limit of maintenance jobs as below:
|
||||
```shell
|
||||
velero server --maintenance-job-cpu-request $cpu-request --maintenance-job-mem-request $mem-request --maintenance-job-cpu-limit $cpu-limit --maintenance-job-mem-limit $mem-limit
|
||||
```
|
||||
Our default value is 0, which means we don't limit the resources, and the resource allocation strategy would be [best effort](https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#besteffort).
|
||||
|
||||
### 5. Automatic Cleanup for Finished Maintenance Jobs
|
||||
Add configuration for clean up maintenance jobs:
|
||||
|
||||
- keep-latest-maintenance-jobs: the number of keeping latest maintenance jobs for each repository.
|
||||
|
||||
```shell
|
||||
velero server --keep-latest-maintenance-jobs $num
|
||||
```
|
||||
|
||||
We would check and keep the latest N jobs after a new job is finished.
|
||||
```golang
|
||||
func deleteOldMaintenanceJobs(cli client.Client, repo string, keep int) error {
|
||||
// Get the maintenance job list by label
|
||||
jobList := &batchv1.JobList{}
|
||||
err := cli.List(context.TODO(), jobList, client.MatchingLabels(map[string]string{RepositoryNameLabel: repo}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete old maintenance jobs
|
||||
if len(jobList.Items) > keep {
|
||||
sort.Slice(jobList.Items, func(i, j int) bool {
|
||||
return jobList.Items[i].CreationTimestamp.Before(&jobList.Items[j].CreationTimestamp)
|
||||
})
|
||||
for i := 0; i < len(jobList.Items)-keep; i++ {
|
||||
err = cli.Delete(context.TODO(), &jobList.Items[i], client.PropagationPolicy(metav1.DeletePropagationBackground))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 6 Velero Install with Maintenance Options
|
||||
All the above maintenance options should be supported by Velero install command.
|
||||
|
||||
### 7. Observability and Debuggability
|
||||
Some monitoring metrics are added for backup repository maintenance:
|
||||
- repo_maintenance_total
|
||||
- repo_maintenance_success_total
|
||||
- repo_maintenance_failed_total
|
||||
- repo_maintenance_duration_seconds
|
||||
|
||||
We will keep the latest N maintenance jobs for each repo, and users can get the log from the job. the job log level inherent from the Velero server setting.
|
||||
|
||||
Also, we would integrate maintenance job logs and `backuprepositories` CRs into `velero debug`.
|
||||
|
||||
Roughly, the process is as follows:
|
||||
1. The backup repository controller will check the BackupRepository request in the queue periodically.
|
||||
|
||||
2. If the maintenance period of the repository checked by `runMaintenanceIfDue` in `Reconcile` is due, then the backup repository controller will call the Repository manager to execute `PruneRepo`
|
||||
|
||||
3. The `PruneRepo` of the Repository manager will create one maintenance job, the resource limitation, environment variables, service account, images, etc. would inherit from the Velero server pod. Also, one clean up TTL would be set to maintenance job.
|
||||
|
||||
4. The maintenance job will execute the Velero maintenance command, wait for maintaining to finish and write the maintenance result into the terminationMessagePath file of the related pod.
|
||||
|
||||
5. Kubernetes could show the result in the status of the pod by reading the termination message in the pod.
|
||||
|
||||
6. The backup repository controller will wait for the maintenance job to finish and read the status of the maintenance job, then update the message field and phase in the status of `backuprepositories` CR accordingly.
|
||||
|
||||
6. Clean up old maintenance jobs and keep only N latest for each repository.
|
||||
|
||||
### 8. Codes Refinement
|
||||
Once `backuprepositories` CR status is modified, the CR would re-queue to be reconciled, and re-execute logics in reconcile shortly not respecting the re-queue frequency configured by `repoSyncPeriod`.
|
||||
For one abnormal scenario if the maintenance job fails, the status of `backuprepositories` CR would be updated and the CR will re-queue immediately, if the new maintenance job still fails, then it will re-queue again, making the logic of `backuprepositories` CR re-queue like a dead loop.
|
||||
|
||||
So we change the Predicates logic in Controller manager making it only re-queue if the Spec of `backuprepositories` CR is changed.
|
||||
|
||||
```golang
|
||||
ctrl.NewControllerManagedBy(mgr).For(&velerov1api.BackupRepository{}, builder.WithPredicates(kube.SpecChangePredicate{}))
|
||||
```
|
||||
|
||||
This change would bring the behavior different from the previous, errors that occurred in the maintenance job would retry in the next reconciliation period instead of retrying immediately.
|
||||
|
||||
## Prospects for Future Work
|
||||
Future work may focus on improving the efficiency of Velero maintenance through non-blocking parallel modes. Potential areas for enhancement include:
|
||||
|
||||
**Non-blocking Mode**: Explore the implementation of a non-blocking mode for parallel maintenance to enhance overall efficiency.
|
||||
|
||||
**Concurrency Control**: Investigate mechanisms for better concurrency control of different maintenance jobs.
|
||||
|
||||
**Provider Support for Parallel Maintenance**: Evaluate the feasibility of parallel maintenance for different providers and address any compatibility issues.
|
||||
|
||||
**Efficiency Improvements**: Investigate strategies to optimize maintenance efficiency without compromising reliability.
|
||||
|
||||
By considering these areas, future iterations of Velero may benefit from enhanced parallelization and improved resource utilization during repository maintenance.
|
||||
@@ -1,120 +0,0 @@
|
||||
# Design for Adding Finalization Phase in Restore Workflow
|
||||
|
||||
## Abstract
|
||||
This design proposes adding the finalization phase to the restore workflow. The finalization phase would be entered after all item restoration and plugin operations have been completed, similar to the way the backup process proceeds. Its purpose is to perform any wrap-up work necessary before transitioning the restore process to a terminal phase.
|
||||
|
||||
## Background
|
||||
Currently, the restore process enters a terminal phase once all item restoration and plugin operations have been completed. However, there are some wrap-up works that need to be performed after item restoration and plugin operations have been fully executed. There is no suitable opportunity to perform them at present.
|
||||
|
||||
To address this, a new finalization phase should be added to the existing restore workflow. in this phase, all plugin operations and item restoration has been fully completed, which provides a clean opportunity to perform any wrap-up work before termination, improving the overall restore process.
|
||||
|
||||
Wrap-up tasks in Velero can serve several purposes:
|
||||
- Post-restore modification - Velero can modify the restored data that was temporarily changed for some purpose but required to be changed back finally or data that was newly created but missing some information. For example, [issue6435](https://github.com/vmware-tanzu/velero/issues/6435) indicates that some custom settings(like labels, reclaim policy) on restored PVs was lost because those restored PVs was newly dynamically provisioned. Velero can address it by patching the PVs' custom settings back in the finalization phase.
|
||||
- Clean up unused data - Velero can identify and delete any data that are no longer needed after a successful restore in the finalization phase.
|
||||
- Post-restore validation - Velero can validate the state of restored data and report any errors to help users locate the issue in the finalization phase.
|
||||
|
||||
The uses of wrap-up tasks are not limited to these examples. Additional needs may be addressed as they develop over time.
|
||||
|
||||
## Goals
|
||||
- Add the finalization phase and the corresponding controller to restore workflow.
|
||||
|
||||
## Non Goals
|
||||
- Implement the specific wrap-up work.
|
||||
|
||||
|
||||
## High-Level Design
|
||||
- The finalization phase will be added to current restore workflow.
|
||||
- The logic for handling current phase transition in restore and restore operations controller will be modified with the introduction of the finalization phase.
|
||||
- A new restore finalizer controller will be implemented to handle the finalization phase.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### phase transition
|
||||
Two new phases related to finalization will be added to restore workflow, which are `FinalizingPartiallyFailed` and `Finalizing`. The new phase transition will be similar to backup workflow, proceeding as follow:
|
||||
|
||||

|
||||
|
||||
### restore finalizer controller
|
||||
The new restore finalizer controller will be implemented to watch for restores in `FinalizingPartiallyFailed` and `Finalizing` phases. Any wrap-up work that needs to wait for the completion of item restoration and plugin operations will be executed by this controller, and the phase will be set to either `Completed` or `PartiallyFailed` based on the results of these works.
|
||||
|
||||
Points worth noting about the new restore finalizer controller:
|
||||
|
||||
A new structure `finalizerContext` will be created to facilitate the implementation of any wrap-up tasks. It includes all the dependencies the tasks require as well as a function `execute()` to orderly implement task logic.
|
||||
```
|
||||
// finalizerContext includes all the dependencies required by wrap-up tasks
|
||||
type finalizerContext struct {
|
||||
.......
|
||||
restore *velerov1api.Restore
|
||||
log logrus.FieldLogger
|
||||
.......
|
||||
}
|
||||
|
||||
// execute executes all the wrap-up tasks and return the result
|
||||
func (ctx *finalizerContext) execute() (results.Result, results.Result) {
|
||||
// execute task1
|
||||
.......
|
||||
|
||||
// execute task2
|
||||
.......
|
||||
|
||||
// the task execution logic will be expanded as new tasks are included
|
||||
.......
|
||||
}
|
||||
|
||||
// newFinalizerContext returns a finalizerContext object, the parameters will be added as new tasks are included.
|
||||
func newFinalizerContext(restore *velerov1api.Restore, log logrus.FieldLogger, ...) *finalizerContext{
|
||||
return &finalizerContext{
|
||||
.......
|
||||
restore: restore,
|
||||
log: log,
|
||||
.......
|
||||
}
|
||||
}
|
||||
```
|
||||
The finalizer controller is responsible for collecting all dependencies and creating a `finalizerContext` object using those dependencies. It then invokes the `execute` function.
|
||||
```
|
||||
func (r *restoreFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
.......
|
||||
|
||||
// collect all dependencies required by wrap-up tasks
|
||||
.......
|
||||
|
||||
// create a finalizerContext object and invoke execute()
|
||||
finalizerCtx := newFinalizerContext(restore, log, ...)
|
||||
warnings, errs := finalizerCtx.execute()
|
||||
|
||||
.......
|
||||
}
|
||||
|
||||
```
|
||||
After completing all necessary tasks, the result metadata in object storage will be updated if any errors or warnings occur during the execution. This behavior breaks the feature of keeping metadata files in object storage immutable, However, we believe the tradeoff is justified because it provides users with the access to examine the error/warning details when the wrap-up tasks go wrong.
|
||||
|
||||
```
|
||||
// UpdateResults updates the result metadata in object storage if necessary
|
||||
func (r *restoreFinalizerReconciler) UpdateResults(restore *api.Restore, newWarnings *results.Result, newErrs *results.Result, backupStore persistence.BackupStore) error {
|
||||
originResults, err := backupStore.GetRestoreResults(restore.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting restore results")
|
||||
}
|
||||
warnings := originResults["warnings"]
|
||||
errs := originResults["errors"]
|
||||
warnings.Merge(newWarnings)
|
||||
errs.Merge(newErrs)
|
||||
|
||||
m := map[string]results.Result{
|
||||
"warnings": warnings,
|
||||
"errors": errs,
|
||||
}
|
||||
if err := putResults(restore, m, backupStore); err != nil {
|
||||
return errors.Wrap(err, "error putting restore results")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Compatibility
|
||||
The new finalization phases are added without modifying the existing phases in the restore workflow. Both new and ongoing restore processes will continue to eventually transition to a terminal phase from any prior phase, ensuring backward compatibility.
|
||||
|
||||
## Implementation
|
||||
This will be implemented during the Velero 1.14 development cycle.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB |
@@ -177,54 +177,5 @@ Roughly, the process is as follows:
|
||||
4. Each respective controller within the CRs calls the uploader, and the WriteSparseFiles from map in CRs is passed to the uploader.
|
||||
5. When the uploader subsequently calls the Kopia API, it can use the WriteSparseFiles to set the WriteSparseFiles parameter, and if the uploader calls the Restic command it would append `--sparse` flag within the restore command.
|
||||
|
||||
### Parallel Restore
|
||||
Setting the parallelism of restore operations can improve the efficiency and speed of the restore process, especially when dealing with large amounts of data.
|
||||
|
||||
### Velero CLI
|
||||
The Velero CLI will support a --parallel-files-download flag, allowing users to set the parallelism value when creating restores. when no value specified, the value of it would be the number of CPUs for the node that the node agent pod is running.
|
||||
```bash
|
||||
velero restore create --parallel-files-download $num
|
||||
```
|
||||
|
||||
### UploaderConfig
|
||||
below the sub-option parallel is added into UploaderConfig:
|
||||
|
||||
```go
|
||||
type UploaderConfigForRestore struct {
|
||||
// ParallelFilesDownload is the number of parallel for restore.
|
||||
// +optional
|
||||
ParallelFilesDownload int `json:"parallelFilesDownload,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Kopia Parallel Restore Policy
|
||||
|
||||
Velero Uploader can set restore policies when calling Kopia APIs. In the Kopia codebase, the structure for restore policies is defined as follows:
|
||||
|
||||
```go
|
||||
// first get concurrrency from uploader config
|
||||
restoreConcurrency, _ := uploaderutil.GetRestoreConcurrency(uploaderCfg)
|
||||
// set restore concurrency into restore options
|
||||
restoreOpt := restore.Options{
|
||||
Parallel: restoreConcurrency,
|
||||
}
|
||||
// do restore with restore option
|
||||
restore.Entry(..., restoreOpt)
|
||||
```
|
||||
|
||||
#### Restic Parallel Restore Policy
|
||||
|
||||
Configurable parallel restore is not supported by restic, so we would return one error if the option is configured.
|
||||
```go
|
||||
restoreConcurrency, err := uploaderutil.GetRestoreConcurrency(uploaderCfg)
|
||||
if err != nil {
|
||||
return extraFlags, errors.Wrap(err, "failed to get uploader config")
|
||||
}
|
||||
|
||||
if restoreConcurrency > 0 {
|
||||
return extraFlags, errors.New("restic does not support parallel restore")
|
||||
}
|
||||
```
|
||||
|
||||
## Alternatives Considered
|
||||
To enhance extensibility further, the option of storing `UploaderConfig` in a Kubernetes ConfigMap can be explored, this approach would allow the addition and modification of configuration options without the need to modify the CRD.
|
||||
|
||||
@@ -27,7 +27,7 @@ Moreover, we would like to create a general workflow to variations during the da
|
||||
- Support different data accesses, i.e., file system level and block level
|
||||
- Support different snapshot types, i.e., CSI snapshot, volume snapshot API from storage vendors
|
||||
- Support different snapshot accesses, i.e., through PV generated from snapshots, and through direct access API from storage vendors
|
||||
- Reuse the existing Velero generic data path as created in [Unified Repository design][1]
|
||||
- Reuse the existing Velero generic data path as creatd in [Unified Repository design][1]
|
||||
|
||||
## Non-Goals
|
||||
|
||||
@@ -84,14 +84,14 @@ Below are actions from Velero and DMP:
|
||||
**BIA Execute**
|
||||
This is the existing logic in Velero. For a source PVC/PV, Velero delivers it to the corresponding BackupItemAction plugin, the plugin then takes the related actions to back it up.
|
||||
For example, the existing CSI plugin takes a CSI snapshot to the volume represented by the PVC and then returns additional items (i.e., VolumeSnapshot, VolumeSnapshotContent and VolumeSnapshotClass) for Velero to further backup.
|
||||
To support data movement, we will use BIA V2 which supports asynchronous operation management. Here is the Execute method from BIA V2:
|
||||
To support data movement, we will use BIA V2 which supports asynchronized operation management. Here is the Execute method from BIA V2:
|
||||
```
|
||||
Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error)
|
||||
```
|
||||
Besides ```additionalItem``` (as the 2nd return value), Execute method will return one more resource list called ```itemToUpdate```, which means the items to be updated and persisted when the async operation completes. For details, visit [general progress monitoring design][2].
|
||||
Specifically, this mechanism will be used to persist DUCR into the persisted backup data, in another words, DUCR will be returned as ```itemToUpdate``` from Execute method. DUCR contains all the information the restore requires, so during restore, DUCR will be extracted from the backup data.
|
||||
Additionally, in the same way, a DMP could add any other items into the persisted backup data.
|
||||
Execute method also returns the ```operationID``` which uniquely identifies the asynchronous operation. This ```operationID``` is generated by plugins. The [general progress monitoring design][2] doesn't restrict the format of the ```operationID```, for Velero CSI plugin, the ```operationID``` is a combination of the backup CR UID and the source PVC (represented by the ```item``` parameter) UID.
|
||||
Execute method also returns the ```operationID``` which uniquely identifies the asynchronized operation. This ```operationID``` is generated by plugins. The [general progress monitoring design][2] doesn't restrict the format of the ```operationID```, for Velero CSI plugin, the ```operationID``` is a combination of the backup CR UID and the source PVC (represented by the ```item``` parameter) UID.
|
||||
|
||||
**Create Snapshot**
|
||||
The DMP creates a snapshot of the requested volume and deliver it to DM through DUCR. After that, the DMP leaves the snapshot and its related objects (e.g., VolumeSnapshot and VolumeSnapshotContent for CSI snapshot) to the DM, DM then has full control of the snapshot and its related objects, i.e., deciding whether to delete the snapshot or its related objects and when to do it.
|
||||
@@ -930,20 +930,12 @@ For 3, Velero leverage on DMs to decide how to save the log, but they will not g
|
||||
## Installation
|
||||
DMs need to be configured during installation so that they can be installed. Plugin DMs may have their own configuration, for VGDM, the only requirement is to install Velero node-agent.
|
||||
Moreover, the DMP is also required during the installation.
|
||||
|
||||
From release-1.14, the `github.com/vmware-tanzu/velero-plugin-for-csi` repository, which is the Velero CSI plugin, is merged into the `github.com/vmware-tanzu/velero` repository.
|
||||
The reason to merge the CSI plugin is:
|
||||
* The VolumeSnapshot data mover depends on the CSI plugin, it's reasonabe to integrate them.
|
||||
* This change reduces the Velero deploying complexity.
|
||||
* This makes performance tuning easier in the future.
|
||||
|
||||
As a result, no need to install Velero CSI plugin anymore.
|
||||
|
||||
For example, to move CSI snapshot through VBDM, below is the installation script:
|
||||
```
|
||||
velero install \
|
||||
--provider \
|
||||
--image \
|
||||
--plugins velero/velero-plugin-for-csi:xxx \
|
||||
--features=EnableCSI \
|
||||
--use-node-agent \
|
||||
```
|
||||
|
||||
225
go.mod
225
go.mod
@@ -1,178 +1,191 @@
|
||||
module github.com/vmware-tanzu/velero
|
||||
|
||||
go 1.22.2
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.6
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.40.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.6.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.3
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.14
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.143.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7
|
||||
cloud.google.com/go/storage v1.33.0
|
||||
github.com/Azure/azure-pipeline-go v0.2.3
|
||||
github.com/Azure/azure-sdk-for-go v67.2.0+incompatible
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.3.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0
|
||||
github.com/Azure/go-autorest/autorest v0.11.27
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.42
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.87
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.123.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0
|
||||
github.com/bombsimon/logrusr/v3 v3.0.0
|
||||
github.com/evanphx/json-patch/v5 v5.8.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/hashicorp/go-hclog v0.14.1
|
||||
github.com/hashicorp/go-plugin v1.6.0
|
||||
github.com/hashicorp/go-plugin v1.4.3
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/kopia/kopia v0.16.0
|
||||
github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0
|
||||
github.com/kopia/kopia v0.14.1
|
||||
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/onsi/gomega v1.20.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/robfig/cron v1.1.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/afero v1.6.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/vmware-tanzu/crash-diagnostics v0.3.7
|
||||
go.uber.org/zap v1.27.0
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/mod v0.17.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/mod v0.13.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/api v0.172.0
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/api v0.146.0
|
||||
google.golang.org/grpc v1.58.3
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.29.0
|
||||
k8s.io/apiextensions-apiserver v0.29.0
|
||||
k8s.io/apimachinery v0.29.0
|
||||
k8s.io/api v0.25.6
|
||||
k8s.io/apiextensions-apiserver v0.24.2
|
||||
k8s.io/apimachinery v0.25.6
|
||||
k8s.io/cli-runtime v0.24.0
|
||||
k8s.io/client-go v0.29.0
|
||||
k8s.io/klog/v2 v2.110.1
|
||||
k8s.io/client-go v0.25.6
|
||||
k8s.io/klog/v2 v2.70.1
|
||||
k8s.io/kube-aggregator v0.19.12
|
||||
k8s.io/metrics v0.25.6
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
||||
sigs.k8s.io/controller-runtime v0.17.2
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
|
||||
sigs.k8s.io/controller-runtime v0.12.2
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.112.1 // indirect
|
||||
cloud.google.com/go/compute v1.24.0 // indirect
|
||||
cloud.google.com/go v0.110.7 // indirect
|
||||
cloud.google.com/go/compute v1.23.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.7 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect
|
||||
github.com/aws/smithy-go v1.14.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chmduquesne/rollinghash v4.0.0+incompatible // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-logr/zapr v1.2.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.21.1 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/hashicorp/cronexpr v1.1.2 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/klauspost/reedsolomon v1.12.1 // indirect
|
||||
github.com/klauspost/reedsolomon v1.11.8 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.69 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.63 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/mxk/go-vss v1.2.0 // indirect
|
||||
github.com/natefinch/atomic v1.0.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.52.3 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/vladimirvivien/gexe v0.1.1 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
go.starlark.net v0.0.0-20201006213952-227f4aabceb5 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/component-base v0.29.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
k8s.io/component-base v0.24.2 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
replace github.com/kopia/kopia => github.com/project-velero/kopia v0.0.0-20240417031915-e07d5b7de567
|
||||
replace github.com/kopia/kopia => github.com/project-velero/kopia v0.0.0-20231023031817-cf7bbc7f8519
|
||||
|
||||
@@ -12,11 +12,31 @@ run:
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
||||
# which dirs to skip: issues from them won't be reported;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but default dirs are skipped independently
|
||||
# from this option's value (see skip-dirs-use-default).
|
||||
# "/" will be replaced by current OS file path separator to properly work
|
||||
# on Windows.
|
||||
skip-dirs:
|
||||
- test/*
|
||||
- pkg/plugin/generated/*
|
||||
# - autogenerated_by_my_lib
|
||||
|
||||
# default is true. Enables skipping of directories:
|
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs-use-default: true
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
# "/" will be replaced by current OS file path separator to properly work
|
||||
# on Windows.
|
||||
skip-files:
|
||||
- ".*_test.go$"
|
||||
# - lib/bad.go
|
||||
|
||||
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
|
||||
# If invoked with -mod=readonly, the go command is disallowed from the implicit
|
||||
# automatic updating of go.mod described above. Instead, it fails when any changes
|
||||
@@ -36,9 +56,7 @@ run:
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||
formats:
|
||||
- format: colored-line-number
|
||||
path: stdout
|
||||
format: colored-line-number
|
||||
|
||||
# print lines of code with issue, default is true
|
||||
print-issued-lines: true
|
||||
@@ -142,8 +160,10 @@ linters-settings:
|
||||
# minimal confidence for issues, default is 0.8
|
||||
min-confidence: 0.8
|
||||
gomnd:
|
||||
# the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description.
|
||||
checks: argument,case,condition,operation,return,assign
|
||||
settings:
|
||||
mnd:
|
||||
# the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description.
|
||||
checks: argument,case,condition,operation,return,assign
|
||||
gomodguard:
|
||||
allowed:
|
||||
modules: # List of allowed modules
|
||||
@@ -235,24 +255,6 @@ linters-settings:
|
||||
rowserrcheck:
|
||||
packages:
|
||||
- github.com/jmoiron/sqlx
|
||||
testifylint:
|
||||
# TODO: enable them all
|
||||
disable:
|
||||
- bool-compare
|
||||
- compares
|
||||
- error-is-as
|
||||
- error-nil
|
||||
- expected-actual
|
||||
- go-require
|
||||
- float-compare
|
||||
- require-error
|
||||
- suite-dont-use-pkg
|
||||
- suite-extra-assert-call
|
||||
- suite-thelper
|
||||
enable:
|
||||
- empty
|
||||
- len
|
||||
- nil-compare
|
||||
testpackage:
|
||||
# regexp pattern to skip files
|
||||
skip-regexp: (export|internal)_test\.go
|
||||
@@ -300,10 +302,8 @@ linters:
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- durationcheck
|
||||
- dupword
|
||||
- errcheck
|
||||
- exportloopref
|
||||
- errchkjson
|
||||
- goconst
|
||||
- gofmt
|
||||
- goheader
|
||||
@@ -312,25 +312,26 @@ linters:
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ginkgolinter
|
||||
- importas
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- nosprintfhostport
|
||||
- nilerr
|
||||
- noctx
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- testifylint
|
||||
- revive
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- whitespace
|
||||
- dupword
|
||||
- errchkjson
|
||||
- ginkgolinter
|
||||
- nilerr
|
||||
- noctx
|
||||
- nolintlint
|
||||
fast: false
|
||||
|
||||
|
||||
@@ -339,34 +340,12 @@ issues:
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "github.com/golang/protobuf/proto" # grpc-go still uses github.com/golang/protobuf/proto.
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" # Kopia still depends on this.
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "DefaultVolumesToRestic" # No need to report deprecate for DefaultVolumesToRestic.
|
||||
- path: ".*_test.go$"
|
||||
linters:
|
||||
- dupword
|
||||
- errcheck
|
||||
- goconst
|
||||
- gosec
|
||||
- govet
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- path: test/
|
||||
linters:
|
||||
- dupword
|
||||
- errcheck
|
||||
- goconst
|
||||
- gosec
|
||||
- gosimple
|
||||
- nilerr
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
|
||||
# The list of ids of default excludes to include or disable. By default it's empty.
|
||||
include:
|
||||
@@ -381,15 +360,6 @@ issues:
|
||||
# Show only new issues created after git revision `REV`
|
||||
# new-from-rev: origin/main
|
||||
|
||||
# which dirs to skip: issues from them won't be reported;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but default dirs are skipped independently
|
||||
# from this option's value (see skip-dirs-use-default).
|
||||
# "/" will be replaced by current OS file path separator to properly work
|
||||
# on Windows.
|
||||
exclude-dirs:
|
||||
- pkg/plugin/generated/*
|
||||
|
||||
severity:
|
||||
# Default value is empty string.
|
||||
# Set the default severity for issues. If severity rules are defined and the issues
|
||||
@@ -413,4 +383,4 @@ severity:
|
||||
rules:
|
||||
- linters:
|
||||
- dupl
|
||||
severity: info
|
||||
severity: info
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM --platform=$TARGETPLATFORM golang:1.22.2-bookworm
|
||||
FROM --platform=linux/amd64 golang:1.21.6-bookworm
|
||||
|
||||
ARG GOPROXY
|
||||
|
||||
@@ -21,16 +21,16 @@ ENV GO111MODULE=on
|
||||
ENV GOPROXY=${GOPROXY}
|
||||
|
||||
# kubebuilder test bundle is separated from kubebuilder. Need to setup it for CI test.
|
||||
RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/$(go env GOARCH) && \
|
||||
RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/amd64 && \
|
||||
mkdir /usr/local/kubebuilder && \
|
||||
tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz
|
||||
|
||||
RUN wget --quiet https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.2.0/kubebuilder_linux_$(go env GOARCH) && \
|
||||
mv kubebuilder_linux_$(go env GOARCH) /usr/local/kubebuilder/bin/kubebuilder && \
|
||||
RUN wget --quiet https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.2.0/kubebuilder_linux_amd64 && \
|
||||
mv kubebuilder_linux_amd64 /usr/local/kubebuilder/bin/kubebuilder && \
|
||||
chmod +x /usr/local/kubebuilder/bin/kubebuilder
|
||||
|
||||
# get controller-tools
|
||||
RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0
|
||||
RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.0
|
||||
|
||||
# get goimports (the revision is pinned so we don't indiscriminately update, but the particular commit
|
||||
# is not important)
|
||||
@@ -39,66 +39,26 @@ RUN go install golang.org/x/tools/cmd/goimports@11e9d9cc0042e6bd10337d4d2c3e5d92
|
||||
# get protoc compiler and golang plugin
|
||||
WORKDIR /root
|
||||
RUN apt-get update && apt-get install -y unzip
|
||||
# protobuf uses bazel cpunames except following
|
||||
# if cpu == "systemz":
|
||||
# cpu = "s390_64"
|
||||
# elif cpu == "aarch64":
|
||||
# cpu = "aarch_64"
|
||||
# elif cpu == "ppc64":
|
||||
# cpu = "ppcle_64"
|
||||
# snippet from: https://github.com/protocolbuffers/protobuf/blob/d445953603e66eb8992a39b4e10fcafec8501f24/protobuf_release.bzl#L18-L24
|
||||
# cpu names: https://github.com/bazelbuild/platforms/blob/main/cpu/BUILD
|
||||
RUN ARCH=$(go env GOARCH) && \
|
||||
if [ "$ARCH" = "s390x" ] ; then \
|
||||
ARCH="s390_64"; \
|
||||
elif [ "$ARCH" = "arm64" ] ; then \
|
||||
ARCH="aarch_64"; \
|
||||
elif [ "$ARCH" = "ppc64le" ] ; then \
|
||||
ARCH="ppcle_64"; \
|
||||
elif [ "$ARCH" = "ppc64" ] ; then \
|
||||
ARCH="ppcle_64"; \
|
||||
else \
|
||||
ARCH=$(uname -m); \
|
||||
fi && echo "ARCH=$ARCH" && \
|
||||
wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-$ARCH.zip && \
|
||||
unzip protoc-25.2-linux-$ARCH.zip; \
|
||||
rm *.zip && \
|
||||
RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip && \
|
||||
unzip protoc-3.14.0-linux-x86_64.zip && \
|
||||
mv bin/protoc /usr/bin/protoc && \
|
||||
mv include/google /usr/include && \
|
||||
chmod a+x /usr/include/google && \
|
||||
chmod a+x /usr/include/google/protobuf && \
|
||||
chmod a+x /usr/include/google && \
|
||||
chmod a+x /usr/include/google/protobuf && \
|
||||
chmod a+r -R /usr/include/google && \
|
||||
chmod +x /usr/bin/protoc
|
||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.32.0
|
||||
RUN go install github.com/golang/protobuf/protoc-gen-go@v1.4.3
|
||||
|
||||
# get goreleaser
|
||||
# goreleaser name template per arch is basically goarch except for amd64 and 386 https://github.com/goreleaser/goreleaser/blob/ec8819a95c5527fae65e5cb41673f5bbc3245fda/.goreleaser.yaml#L167C1-L173C42
|
||||
# {{- .ProjectName }}_
|
||||
# {{- title .Os }}_
|
||||
# {{- if eq .Arch "amd64" }}x86_64
|
||||
# {{- else if eq .Arch "386" }}i386
|
||||
# {{- else }}{{ .Arch }}{{ end }}
|
||||
# {{- if .Arm }}v{{ .Arm }}{{ end -}}
|
||||
RUN ARCH=$(go env GOARCH) && \
|
||||
if [ "$ARCH" = "amd64" ] ; then \
|
||||
ARCH="x86_64"; \
|
||||
elif [ "$ARCH" = "386" ] ; then \
|
||||
ARCH="i386"; \
|
||||
elif [ "$ARCH" = "ppc64le" ] ; then \
|
||||
ARCH="ppc64"; \
|
||||
fi && \
|
||||
wget --quiet "https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_$ARCH.tar.gz" && \
|
||||
tar xvf goreleaser_Linux_$ARCH.tar.gz; \
|
||||
RUN wget --quiet https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_x86_64.tar.gz && \
|
||||
tar xvf goreleaser_Linux_x86_64.tar.gz && \
|
||||
mv goreleaser /usr/bin/goreleaser && \
|
||||
chmod +x /usr/bin/goreleaser
|
||||
|
||||
# get golangci-lint
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.57.2
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2
|
||||
|
||||
# install kubectl
|
||||
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/$(go env GOARCH)/kubectl
|
||||
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
|
||||
RUN chmod +x ./kubectl
|
||||
RUN mv ./kubectl /usr/local/bin
|
||||
|
||||
# Fix the "dubious ownership" issue from git when running goreleaser.sh
|
||||
RUN echo "[safe] \n\t directory = *" > /.gitconfig
|
||||
@@ -32,10 +32,6 @@ if [[ -z "${GOOS}" ]]; then
|
||||
echo "GOOS must be set"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${GOBIN}" ]]; then
|
||||
echo "GOBIN must be set"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${GOARCH}" ]]; then
|
||||
echo "GOARCH must be set"
|
||||
exit 1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/go.mod b/go.mod
|
||||
index 5f939c481..1caa51275 100644
|
||||
index 5f939c481..a2c584c4d 100644
|
||||
--- a/go.mod
|
||||
+++ b/go.mod
|
||||
@@ -24,32 +24,32 @@ require (
|
||||
@@ -9,16 +9,16 @@ index 5f939c481..1caa51275 100644
|
||||
- golang.org/x/crypto v0.5.0
|
||||
- golang.org/x/net v0.5.0
|
||||
- golang.org/x/oauth2 v0.4.0
|
||||
+ golang.org/x/crypto v0.21.0
|
||||
+ golang.org/x/net v0.23.0
|
||||
+ golang.org/x/crypto v0.17.0
|
||||
+ golang.org/x/net v0.17.0
|
||||
+ golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/sync v0.1.0
|
||||
- golang.org/x/sys v0.4.0
|
||||
- golang.org/x/term v0.4.0
|
||||
- golang.org/x/text v0.6.0
|
||||
- google.golang.org/api v0.106.0
|
||||
+ golang.org/x/sys v0.18.0
|
||||
+ golang.org/x/term v0.18.0
|
||||
+ golang.org/x/sys v0.15.0
|
||||
+ golang.org/x/term v0.15.0
|
||||
+ golang.org/x/text v0.14.0
|
||||
+ google.golang.org/api v0.114.0
|
||||
)
|
||||
@@ -57,12 +57,12 @@ index 5f939c481..1caa51275 100644
|
||||
- google.golang.org/protobuf v1.28.1 // indirect
|
||||
+ google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
+ google.golang.org/grpc v1.56.3 // indirect
|
||||
+ google.golang.org/protobuf v1.33.0 // indirect
|
||||
+ google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
diff --git a/go.sum b/go.sum
|
||||
index 026e1d2fa..27d4207f4 100644
|
||||
index 026e1d2fa..8387b4e2f 100644
|
||||
--- a/go.sum
|
||||
+++ b/go.sum
|
||||
@@ -1,13 +1,13 @@
|
||||
@@ -126,8 +126,8 @@ index 026e1d2fa..27d4207f4 100644
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
-golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
-golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -137,8 +137,8 @@ index 026e1d2fa..27d4207f4 100644
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
-golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||
-golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
@@ -153,13 +153,13 @@ index 026e1d2fa..27d4207f4 100644
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
-golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
-golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
+golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -208,8 +208,8 @@ index 026e1d2fa..27d4207f4 100644
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
HACK_DIR=$(dirname "${BASH_SOURCE[0]}")
|
||||
|
||||
# Printing out cache status
|
||||
golangci-lint cache status
|
||||
|
||||
# Enable GL_DEBUG line below for debug messages for golangci-lint
|
||||
# export GL_DEBUG=loader,gocritic,env
|
||||
CMD="golangci-lint run"
|
||||
CMD="golangci-lint run -c $HACK_DIR/../golangci.yaml"
|
||||
echo "Running $CMD"
|
||||
|
||||
eval $CMD
|
||||
|
||||
@@ -21,9 +21,10 @@ set -o pipefail
|
||||
|
||||
export CGO_ENABLED=0
|
||||
|
||||
TARGETS=($(go list ./pkg/... ./internal/...| grep -vE "/pkg/builder|pkg/apis|pkg/test|pkg/generated|pkg/plugin/generated|mocks|internal/restartabletest"))
|
||||
TARGETS=($(go list ./pkg/... | grep -v "github.com/vmware-tanzu/velero/pkg/builder"))
|
||||
TARGETS+=(
|
||||
./cmd/...
|
||||
./internal/...
|
||||
)
|
||||
|
||||
if [[ ${#@} -ne 0 ]]; then
|
||||
|
||||
@@ -33,7 +33,7 @@ if ! command -v goimports > /dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
files="$(find . -type f -name '*.go' -not -path './.go/*' -not -path './vendor/*' -not -path './site/*' -not -path './.git/*' -not -path '*/generated/*' -not -name 'zz_generated*' -not -path '*/mocks/*')"
|
||||
files="$(find . -type f -name '*.go' -not -path './.go/*' -not -path './vendor/*' -not -path './site/*' -not -path '*/generated/*' -not -name 'zz_generated*' -not -path '*/mocks/*')"
|
||||
echo "${ACTION} gofmt"
|
||||
output=$(gofmt "${MODE}" -s ${files})
|
||||
if [[ -n "${output}" ]]; then
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
Copyright the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package csi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
crclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
plugincommon "github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/csi"
|
||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
||||
// volumeSnapshotDeleteItemAction is a backup item action plugin for Velero.
|
||||
type volumeSnapshotDeleteItemAction struct {
|
||||
log logrus.FieldLogger
|
||||
crClient crclient.Client
|
||||
}
|
||||
|
||||
// AppliesTo returns information indicating that the
|
||||
// VolumeSnapshotBackupItemAction should be invoked to backup
|
||||
// VolumeSnapshots.
|
||||
func (p *volumeSnapshotDeleteItemAction) AppliesTo() (velero.ResourceSelector, error) {
|
||||
p.log.Debug("VolumeSnapshotBackupItemAction AppliesTo")
|
||||
|
||||
return velero.ResourceSelector{
|
||||
IncludedResources: []string{"volumesnapshots.snapshot.storage.k8s.io"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *volumeSnapshotDeleteItemAction) Execute(
|
||||
input *velero.DeleteItemActionExecuteInput,
|
||||
) error {
|
||||
p.log.Info("Starting VolumeSnapshotDeleteItemAction for volumeSnapshot")
|
||||
|
||||
var vs snapshotv1api.VolumeSnapshot
|
||||
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(
|
||||
input.Item.UnstructuredContent(),
|
||||
&vs,
|
||||
); err != nil {
|
||||
return errors.Wrapf(err, "failed to convert input.Item from unstructured")
|
||||
}
|
||||
|
||||
// We don't want this DeleteItemAction plugin to delete VolumeSnapshot
|
||||
// taken outside of Velero. So skip deleting VolumeSnapshot objects
|
||||
// that were not created in the process of creating the Velero
|
||||
// backup being deleted.
|
||||
if !kubeutil.HasBackupLabel(&vs.ObjectMeta, input.Backup.Name) {
|
||||
p.log.Info(
|
||||
"VolumeSnapshot %s/%s was not taken by backup %s, skipping deletion",
|
||||
vs.Namespace, vs.Name, input.Backup.Name,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
p.log.Infof("Deleting VolumeSnapshot %s/%s", vs.Namespace, vs.Name)
|
||||
if vs.Status != nil && vs.Status.BoundVolumeSnapshotContentName != nil {
|
||||
// we patch the DeletionPolicy of the VolumeSnapshotContent
|
||||
// to set it to Delete. This ensures that the volume snapshot
|
||||
// in the storage provider is also deleted.
|
||||
err := csi.SetVolumeSnapshotContentDeletionPolicy(
|
||||
*vs.Status.BoundVolumeSnapshotContentName,
|
||||
p.crClient,
|
||||
)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return errors.Wrapf(
|
||||
err,
|
||||
fmt.Sprintf("failed to patch DeletionPolicy of volume snapshot %s/%s",
|
||||
vs.Namespace, vs.Name),
|
||||
)
|
||||
}
|
||||
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err := p.crClient.Delete(context.TODO(), &vs)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewVolumeSnapshotDeleteItemAction(f client.Factory) plugincommon.HandlerInitializer {
|
||||
return func(logger logrus.FieldLogger) (interface{}, error) {
|
||||
crClient, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &volumeSnapshotDeleteItemAction{
|
||||
log: logger,
|
||||
crClient: crClient,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
Copyright the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package csi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
func TestVSExecute(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
item runtime.Unstructured
|
||||
vs *snapshotv1api.VolumeSnapshot
|
||||
backup *velerov1api.Backup
|
||||
createVS bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "VolumeSnapshot doesn't have backup label",
|
||||
item: velerotest.UnstructuredOrDie(
|
||||
`
|
||||
{
|
||||
"apiVersion": "snapshot.storage.k8s.io/v1",
|
||||
"kind": "VolumeSnapshot",
|
||||
"metadata": {
|
||||
"namespace": "ns",
|
||||
"name": "foo"
|
||||
}
|
||||
}
|
||||
`,
|
||||
),
|
||||
backup: builder.ForBackup("velero", "backup").Result(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumeSnapshot doesn't exist in the cluster",
|
||||
vs: builder.ForVolumeSnapshot("foo", "bar").
|
||||
ObjectMeta(builder.WithLabelsMap(
|
||||
map[string]string{velerov1api.BackupNameLabel: "backup"},
|
||||
)).Status().
|
||||
BoundVolumeSnapshotContentName("vsc").
|
||||
Result(),
|
||||
backup: builder.ForBackup("velero", "backup").Result(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Normal case, VolumeSnapshot should be deleted",
|
||||
vs: builder.ForVolumeSnapshot("foo", "bar").
|
||||
ObjectMeta(builder.WithLabelsMap(
|
||||
map[string]string{velerov1api.BackupNameLabel: "backup"},
|
||||
)).Status().
|
||||
BoundVolumeSnapshotContentName("vsc").
|
||||
Result(),
|
||||
backup: builder.ForBackup("velero", "backup").Result(),
|
||||
expectErr: false,
|
||||
createVS: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
logger := logrus.StandardLogger()
|
||||
|
||||
p := volumeSnapshotDeleteItemAction{log: logger, crClient: crClient}
|
||||
|
||||
if test.vs != nil {
|
||||
vsMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(test.vs)
|
||||
require.NoError(t, err)
|
||||
test.item = &unstructured.Unstructured{Object: vsMap}
|
||||
}
|
||||
|
||||
if test.createVS {
|
||||
require.NoError(t, crClient.Create(context.TODO(), test.vs))
|
||||
}
|
||||
|
||||
err := p.Execute(
|
||||
&velero.DeleteItemActionExecuteInput{
|
||||
Item: test.item,
|
||||
Backup: test.backup,
|
||||
},
|
||||
)
|
||||
|
||||
if test.expectErr == false {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVSAppliesTo(t *testing.T) {
|
||||
p := volumeSnapshotDeleteItemAction{
|
||||
log: logrus.StandardLogger(),
|
||||
}
|
||||
selector, err := p.AppliesTo()
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
velero.ResourceSelector{
|
||||
IncludedResources: []string{"volumesnapshots.snapshot.storage.k8s.io"},
|
||||
},
|
||||
selector,
|
||||
)
|
||||
}
|
||||
|
||||
func TestNewVolumeSnapshotDeleteItemAction(t *testing.T) {
|
||||
logger := logrus.StandardLogger()
|
||||
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
|
||||
f := &factorymocks.Factory{}
|
||||
f.On("KubebuilderClient").Return(nil, fmt.Errorf(""))
|
||||
plugin := NewVolumeSnapshotDeleteItemAction(f)
|
||||
_, err := plugin(logger)
|
||||
require.Error(t, err)
|
||||
|
||||
f1 := &factorymocks.Factory{}
|
||||
f1.On("KubebuilderClient").Return(crClient, nil)
|
||||
plugin1 := NewVolumeSnapshotDeleteItemAction(f1)
|
||||
_, err1 := plugin1(logger)
|
||||
require.NoError(t, err1)
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
Copyright the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package csi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
crclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
plugincommon "github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/csi"
|
||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
||||
// volumeSnapshotContentDeleteItemAction is a restore item action plugin for Velero
|
||||
type volumeSnapshotContentDeleteItemAction struct {
|
||||
log logrus.FieldLogger
|
||||
crClient crclient.Client
|
||||
}
|
||||
|
||||
// AppliesTo returns information indicating
|
||||
// VolumeSnapshotContentRestoreItemAction action should be invoked
|
||||
// while restoring VolumeSnapshotContent.snapshot.storage.k8s.io resources
|
||||
func (p *volumeSnapshotContentDeleteItemAction) AppliesTo() (velero.ResourceSelector, error) {
|
||||
return velero.ResourceSelector{
|
||||
IncludedResources: []string{"volumesnapshotcontents.snapshot.storage.k8s.io"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *volumeSnapshotContentDeleteItemAction) Execute(
|
||||
input *velero.DeleteItemActionExecuteInput,
|
||||
) error {
|
||||
p.log.Info("Starting VolumeSnapshotContentDeleteItemAction")
|
||||
|
||||
var snapCont snapshotv1api.VolumeSnapshotContent
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(
|
||||
input.Item.UnstructuredContent(),
|
||||
&snapCont,
|
||||
); err != nil {
|
||||
return errors.Wrapf(err, "failed to convert VolumeSnapshotContent from unstructured")
|
||||
}
|
||||
|
||||
// We don't want this DeleteItemAction plugin to delete
|
||||
// VolumeSnapshotContent taken outside of Velero.
|
||||
// So skip deleting VolumeSnapshotContent not have the backup name
|
||||
// in its labels.
|
||||
if !kubeutil.HasBackupLabel(&snapCont.ObjectMeta, input.Backup.Name) {
|
||||
p.log.Info(
|
||||
"VolumeSnapshotContent %s was not taken by backup %s, skipping deletion",
|
||||
snapCont.Name,
|
||||
input.Backup.Name,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
p.log.Infof("Deleting VolumeSnapshotContent %s", snapCont.Name)
|
||||
|
||||
if err := csi.SetVolumeSnapshotContentDeletionPolicy(
|
||||
snapCont.Name,
|
||||
p.crClient,
|
||||
); err != nil {
|
||||
// #4764: Leave a warning when VolumeSnapshotContent cannot be found for deletion.
|
||||
// Manual deleting VolumeSnapshotContent can cause this.
|
||||
// It's tricky for Velero to handle this inconsistency.
|
||||
// Even if Velero restores the VolumeSnapshotContent, CSI snapshot controller
|
||||
// may not delete it correctly due to the snapshot represented by VolumeSnapshotContent
|
||||
// already deleted on cloud provider.
|
||||
if apierrors.IsNotFound(err) {
|
||||
p.log.Warnf(
|
||||
"VolumeSnapshotContent %s of backup %s cannot be found. May leave orphan snapshot %s on cloud provider.",
|
||||
snapCont.Name, input.Backup.Name, *snapCont.Status.SnapshotHandle)
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, fmt.Sprintf(
|
||||
"failed to set DeletionPolicy on volumesnapshotcontent %s. Skipping deletion",
|
||||
snapCont.Name))
|
||||
}
|
||||
|
||||
if err := p.crClient.Delete(
|
||||
context.TODO(),
|
||||
&snapCont,
|
||||
); err != nil && !apierrors.IsNotFound(err) {
|
||||
p.log.Infof("VolumeSnapshotContent %s not found", snapCont.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewVolumeSnapshotContentDeleteItemAction(
|
||||
f client.Factory,
|
||||
) plugincommon.HandlerInitializer {
|
||||
return func(logger logrus.FieldLogger) (interface{}, error) {
|
||||
crClient, err := f.KubebuilderClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &volumeSnapshotContentDeleteItemAction{
|
||||
log: logger,
|
||||
crClient: crClient,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
Copyright the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package csi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
func TestVSCExecute(t *testing.T) {
|
||||
snapshotHandleStr := "test"
|
||||
tests := []struct {
|
||||
name string
|
||||
item runtime.Unstructured
|
||||
vsc *snapshotv1api.VolumeSnapshotContent
|
||||
backup *velerov1api.Backup
|
||||
createVSC bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "VolumeSnapshotContent doesn't have backup label",
|
||||
item: velerotest.UnstructuredOrDie(
|
||||
`
|
||||
{
|
||||
"apiVersion": "snapshot.storage.k8s.io/v1",
|
||||
"kind": "VolumeSnapshotContent",
|
||||
"metadata": {
|
||||
"namespace": "ns",
|
||||
"name": "foo"
|
||||
}
|
||||
}
|
||||
`,
|
||||
),
|
||||
backup: builder.ForBackup("velero", "backup").Result(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumeSnapshotContent doesn't exist in the cluster, no error",
|
||||
vsc: builder.ForVolumeSnapshotContent("bar").ObjectMeta(builder.WithLabelsMap(map[string]string{velerov1api.BackupNameLabel: "backup"})).Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: &snapshotHandleStr}).Result(),
|
||||
backup: builder.ForBackup("velero", "backup").Result(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Normal case, VolumeSnapshot should be deleted",
|
||||
vsc: builder.ForVolumeSnapshotContent("bar").ObjectMeta(builder.WithLabelsMap(map[string]string{velerov1api.BackupNameLabel: "backup"})).Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: &snapshotHandleStr}).Result(),
|
||||
backup: builder.ForBackup("velero", "backup").Result(),
|
||||
expectErr: false,
|
||||
createVSC: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
logger := logrus.StandardLogger()
|
||||
|
||||
p := volumeSnapshotContentDeleteItemAction{log: logger, crClient: crClient}
|
||||
|
||||
if test.vsc != nil {
|
||||
vscMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(test.vsc)
|
||||
require.NoError(t, err)
|
||||
test.item = &unstructured.Unstructured{Object: vscMap}
|
||||
}
|
||||
|
||||
if test.createVSC {
|
||||
require.NoError(t, crClient.Create(context.TODO(), test.vsc))
|
||||
}
|
||||
|
||||
err := p.Execute(
|
||||
&velero.DeleteItemActionExecuteInput{
|
||||
Item: test.item,
|
||||
Backup: test.backup,
|
||||
},
|
||||
)
|
||||
|
||||
if test.expectErr == false {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVSCAppliesTo(t *testing.T) {
|
||||
p := volumeSnapshotContentDeleteItemAction{
|
||||
log: logrus.StandardLogger(),
|
||||
}
|
||||
selector, err := p.AppliesTo()
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(
|
||||
t,
|
||||
velero.ResourceSelector{
|
||||
IncludedResources: []string{"volumesnapshotcontents.snapshot.storage.k8s.io"},
|
||||
},
|
||||
selector,
|
||||
)
|
||||
}
|
||||
|
||||
func TestNewVolumeSnapshotContentDeleteItemAction(t *testing.T) {
|
||||
logger := logrus.StandardLogger()
|
||||
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
|
||||
f := &factorymocks.Factory{}
|
||||
f.On("KubebuilderClient").Return(nil, fmt.Errorf(""))
|
||||
plugin := NewVolumeSnapshotContentDeleteItemAction(f)
|
||||
_, err := plugin(logger)
|
||||
require.Error(t, err)
|
||||
|
||||
f1 := &factorymocks.Factory{}
|
||||
f1.On("KubebuilderClient").Return(crClient, nil)
|
||||
plugin1 := NewVolumeSnapshotContentDeleteItemAction(f1)
|
||||
_, err1 := plugin1(logger)
|
||||
require.NoError(t, err1)
|
||||
}
|
||||
@@ -26,8 +26,8 @@ const (
|
||||
HookSourceSpec = "spec"
|
||||
)
|
||||
|
||||
// hookKey identifies a backup/restore hook
|
||||
type hookKey struct {
|
||||
// hookTrackerKey identifies a backup/restore hook
|
||||
type hookTrackerKey struct {
|
||||
// PodNamespace indicates the namespace of pod where hooks are executed.
|
||||
// For hooks specified in the backup/restore spec, this field is the namespace of an applicable pod.
|
||||
// For hooks specified in pod annotation, this field is the namespace of pod where hooks are annotated.
|
||||
@@ -48,46 +48,37 @@ type hookKey struct {
|
||||
container string
|
||||
}
|
||||
|
||||
// hookStatus records the execution status of a specific hook.
|
||||
// hookStatus is extensible to accommodate additional fields as needs develop.
|
||||
type hookStatus struct {
|
||||
// hookTrackerVal records the execution status of a specific hook.
|
||||
// hookTrackerVal is extensible to accommodate additional fields as needs develop.
|
||||
type hookTrackerVal struct {
|
||||
// HookFailed indicates if hook failed to execute.
|
||||
hookFailed bool
|
||||
// hookExecuted indicates if hook already execute.
|
||||
hookExecuted bool
|
||||
}
|
||||
|
||||
// HookTracker tracks all hooks' execution status in a single backup/restore.
|
||||
// HookTracker tracks all hooks' execution status
|
||||
type HookTracker struct {
|
||||
lock *sync.RWMutex
|
||||
// tracker records all hook info for a single backup/restore.
|
||||
tracker map[hookKey]hookStatus
|
||||
// hookAttemptedCnt indicates the number of attempted hooks.
|
||||
hookAttemptedCnt int
|
||||
// hookFailedCnt indicates the number of failed hooks.
|
||||
hookFailedCnt int
|
||||
// HookExecutedCnt indicates the number of executed hooks.
|
||||
hookExecutedCnt int
|
||||
// hookErrs records hook execution errors if any.
|
||||
hookErrs []HookErrInfo
|
||||
lock *sync.RWMutex
|
||||
tracker map[hookTrackerKey]hookTrackerVal
|
||||
}
|
||||
|
||||
// NewHookTracker creates a hookTracker instance.
|
||||
// NewHookTracker creates a hookTracker.
|
||||
func NewHookTracker() *HookTracker {
|
||||
return &HookTracker{
|
||||
lock: &sync.RWMutex{},
|
||||
tracker: make(map[hookKey]hookStatus),
|
||||
tracker: make(map[hookTrackerKey]hookTrackerVal),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a hook to the hook tracker
|
||||
// Add adds a hook to the tracker
|
||||
// Add must precede the Record for each individual hook.
|
||||
// In other words, a hook must be added to the tracker before its execution result is recorded.
|
||||
func (ht *HookTracker) Add(podNamespace, podName, container, source, hookName string, hookPhase hookPhase) {
|
||||
ht.lock.Lock()
|
||||
defer ht.lock.Unlock()
|
||||
|
||||
key := hookKey{
|
||||
key := hookTrackerKey{
|
||||
podNamespace: podNamespace,
|
||||
podName: podName,
|
||||
hookSource: source,
|
||||
@@ -97,22 +88,21 @@ func (ht *HookTracker) Add(podNamespace, podName, container, source, hookName st
|
||||
}
|
||||
|
||||
if _, ok := ht.tracker[key]; !ok {
|
||||
ht.tracker[key] = hookStatus{
|
||||
ht.tracker[key] = hookTrackerVal{
|
||||
hookFailed: false,
|
||||
hookExecuted: false,
|
||||
}
|
||||
ht.hookAttemptedCnt++
|
||||
}
|
||||
}
|
||||
|
||||
// Record records the hook's execution status
|
||||
// Add must precede the Record for each individual hook.
|
||||
// In other words, a hook must be added to the tracker before its execution result is recorded.
|
||||
func (ht *HookTracker) Record(podNamespace, podName, container, source, hookName string, hookPhase hookPhase, hookFailed bool, hookErr error) error {
|
||||
func (ht *HookTracker) Record(podNamespace, podName, container, source, hookName string, hookPhase hookPhase, hookFailed bool) error {
|
||||
ht.lock.Lock()
|
||||
defer ht.lock.Unlock()
|
||||
|
||||
key := hookKey{
|
||||
key := hookTrackerKey{
|
||||
podNamespace: podNamespace,
|
||||
podName: podName,
|
||||
hookSource: source,
|
||||
@@ -121,125 +111,38 @@ func (ht *HookTracker) Record(podNamespace, podName, container, source, hookName
|
||||
hookName: hookName,
|
||||
}
|
||||
|
||||
if _, ok := ht.tracker[key]; !ok {
|
||||
return fmt.Errorf("hook not exist in hook tracker, hook: %+v", key)
|
||||
}
|
||||
|
||||
if !ht.tracker[key].hookExecuted {
|
||||
ht.tracker[key] = hookStatus{
|
||||
var err error
|
||||
if _, ok := ht.tracker[key]; ok {
|
||||
ht.tracker[key] = hookTrackerVal{
|
||||
hookFailed: hookFailed,
|
||||
hookExecuted: true,
|
||||
}
|
||||
ht.hookExecutedCnt++
|
||||
if hookFailed {
|
||||
ht.hookFailedCnt++
|
||||
ht.hookErrs = append(ht.hookErrs, HookErrInfo{Namespace: key.podNamespace, Err: hookErr})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stat returns the number of attempted hooks and failed hooks
|
||||
func (ht *HookTracker) Stat() (hookAttemptedCnt int, hookFailedCnt int) {
|
||||
ht.lock.RLock()
|
||||
defer ht.lock.RUnlock()
|
||||
|
||||
return ht.hookAttemptedCnt, ht.hookFailedCnt
|
||||
}
|
||||
|
||||
// IsComplete returns whether the execution of all hooks has finished or not
|
||||
func (ht *HookTracker) IsComplete() bool {
|
||||
ht.lock.RLock()
|
||||
defer ht.lock.RUnlock()
|
||||
|
||||
return ht.hookAttemptedCnt == ht.hookExecutedCnt
|
||||
}
|
||||
|
||||
// HooksErr returns hook execution errors
|
||||
func (ht *HookTracker) HookErrs() []HookErrInfo {
|
||||
ht.lock.RLock()
|
||||
defer ht.lock.RUnlock()
|
||||
|
||||
return ht.hookErrs
|
||||
}
|
||||
|
||||
// MultiHookTrackers tracks all hooks' execution status for multiple backups/restores.
|
||||
type MultiHookTracker struct {
|
||||
lock *sync.RWMutex
|
||||
// trackers is a map that uses the backup/restore name as the key and stores a HookTracker as value.
|
||||
trackers map[string]*HookTracker
|
||||
}
|
||||
|
||||
// NewMultiHookTracker creates a multiHookTracker instance.
|
||||
func NewMultiHookTracker() *MultiHookTracker {
|
||||
return &MultiHookTracker{
|
||||
lock: &sync.RWMutex{},
|
||||
trackers: make(map[string]*HookTracker),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a backup/restore hook to the tracker
|
||||
func (mht *MultiHookTracker) Add(name, podNamespace, podName, container, source, hookName string, hookPhase hookPhase) {
|
||||
mht.lock.Lock()
|
||||
defer mht.lock.Unlock()
|
||||
|
||||
if _, ok := mht.trackers[name]; !ok {
|
||||
mht.trackers[name] = NewHookTracker()
|
||||
}
|
||||
mht.trackers[name].Add(podNamespace, podName, container, source, hookName, hookPhase)
|
||||
}
|
||||
|
||||
// Record records a backup/restore hook execution status
|
||||
func (mht *MultiHookTracker) Record(name, podNamespace, podName, container, source, hookName string, hookPhase hookPhase, hookFailed bool, hookErr error) error {
|
||||
mht.lock.RLock()
|
||||
defer mht.lock.RUnlock()
|
||||
|
||||
var err error
|
||||
if _, ok := mht.trackers[name]; ok {
|
||||
err = mht.trackers[name].Record(podNamespace, podName, container, source, hookName, hookPhase, hookFailed, hookErr)
|
||||
} else {
|
||||
err = fmt.Errorf("the backup/restore not exist in hook tracker, backup/restore name: %s", name)
|
||||
err = fmt.Errorf("hook not exist in hooks tracker, hook key: %v", key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Stat returns the number of attempted hooks and failed hooks for a particular backup/restore
|
||||
func (mht *MultiHookTracker) Stat(name string) (hookAttemptedCnt int, hookFailedCnt int) {
|
||||
mht.lock.RLock()
|
||||
defer mht.lock.RUnlock()
|
||||
// Stat calculates the number of attempted hooks and failed hooks
|
||||
func (ht *HookTracker) Stat() (hookAttemptedCnt int, hookFailed int) {
|
||||
ht.lock.RLock()
|
||||
defer ht.lock.RUnlock()
|
||||
|
||||
if _, ok := mht.trackers[name]; ok {
|
||||
return mht.trackers[name].Stat()
|
||||
for _, hookInfo := range ht.tracker {
|
||||
if hookInfo.hookExecuted {
|
||||
hookAttemptedCnt++
|
||||
if hookInfo.hookFailed {
|
||||
hookFailed++
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes the hook data for a particular backup/restore
|
||||
func (mht *MultiHookTracker) Delete(name string) {
|
||||
mht.lock.Lock()
|
||||
defer mht.lock.Unlock()
|
||||
// GetTracker gets the tracker inside HookTracker
|
||||
func (ht *HookTracker) GetTracker() map[hookTrackerKey]hookTrackerVal {
|
||||
ht.lock.RLock()
|
||||
defer ht.lock.RUnlock()
|
||||
|
||||
delete(mht.trackers, name)
|
||||
}
|
||||
|
||||
// IsComplete returns whether the execution of all hooks for a particular backup/restore has finished or not
|
||||
func (mht *MultiHookTracker) IsComplete(name string) bool {
|
||||
mht.lock.RLock()
|
||||
defer mht.lock.RUnlock()
|
||||
|
||||
if _, ok := mht.trackers[name]; ok {
|
||||
return mht.trackers[name].IsComplete()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HooksErr returns hook execution errors for a particular backup/restore
|
||||
func (mht *MultiHookTracker) HookErrs(name string) []HookErrInfo {
|
||||
mht.lock.RLock()
|
||||
defer mht.lock.RUnlock()
|
||||
|
||||
if _, ok := mht.trackers[name]; ok {
|
||||
return mht.trackers[name].HookErrs()
|
||||
}
|
||||
return nil
|
||||
return ht.tracker
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package hook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -33,13 +32,13 @@ func TestNewHookTracker(t *testing.T) {
|
||||
func TestHookTracker_Add(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
|
||||
key := hookKey{
|
||||
key := hookTrackerKey{
|
||||
podNamespace: "ns1",
|
||||
podName: "pod1",
|
||||
container: "container1",
|
||||
hookPhase: "",
|
||||
hookPhase: PhasePre,
|
||||
hookSource: HookSourceAnnotation,
|
||||
hookName: "h1",
|
||||
}
|
||||
@@ -50,148 +49,45 @@ func TestHookTracker_Add(t *testing.T) {
|
||||
|
||||
func TestHookTracker_Record(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
err := tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
err := tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre, true)
|
||||
|
||||
key := hookKey{
|
||||
key := hookTrackerKey{
|
||||
podNamespace: "ns1",
|
||||
podName: "pod1",
|
||||
container: "container1",
|
||||
hookPhase: "",
|
||||
hookPhase: PhasePre,
|
||||
hookSource: HookSourceAnnotation,
|
||||
hookName: "h1",
|
||||
}
|
||||
|
||||
info := tracker.tracker[key]
|
||||
assert.True(t, info.hookFailed)
|
||||
assert.True(t, info.hookExecuted)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = tracker.Record("ns2", "pod2", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
err = tracker.Record("ns2", "pod2", "container1", HookSourceAnnotation, "h1", PhasePre, true)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
err = tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "", false, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, info.hookFailed)
|
||||
}
|
||||
|
||||
func TestHookTracker_Stat(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
tracker.Add("ns2", "pod2", "container1", HookSourceAnnotation, "h2", "")
|
||||
tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
tracker.Add("ns2", "pod2", "container1", HookSourceAnnotation, "h2", PhasePre)
|
||||
tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre, true)
|
||||
|
||||
attempted, failed := tracker.Stat()
|
||||
assert.Equal(t, 2, attempted)
|
||||
assert.Equal(t, 1, attempted)
|
||||
assert.Equal(t, 1, failed)
|
||||
}
|
||||
|
||||
func TestHookTracker_IsComplete(t *testing.T) {
|
||||
func TestHookTracker_Get(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre, true, fmt.Errorf("err"))
|
||||
assert.True(t, tracker.IsComplete())
|
||||
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
assert.False(t, tracker.IsComplete())
|
||||
}
|
||||
|
||||
func TestHookTracker_HookErrs(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
|
||||
hookErrs := tracker.HookErrs()
|
||||
assert.Len(t, hookErrs, 1)
|
||||
}
|
||||
|
||||
func TestMultiHookTracker_Add(t *testing.T) {
|
||||
mht := NewMultiHookTracker()
|
||||
|
||||
mht.Add("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
|
||||
key := hookKey{
|
||||
podNamespace: "ns1",
|
||||
podName: "pod1",
|
||||
container: "container1",
|
||||
hookPhase: "",
|
||||
hookSource: HookSourceAnnotation,
|
||||
hookName: "h1",
|
||||
}
|
||||
|
||||
_, ok := mht.trackers["restore1"].tracker[key]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestMultiHookTracker_Record(t *testing.T) {
|
||||
mht := NewMultiHookTracker()
|
||||
mht.Add("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
err := mht.Record("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
|
||||
key := hookKey{
|
||||
podNamespace: "ns1",
|
||||
podName: "pod1",
|
||||
container: "container1",
|
||||
hookPhase: "",
|
||||
hookSource: HookSourceAnnotation,
|
||||
hookName: "h1",
|
||||
}
|
||||
|
||||
info := mht.trackers["restore1"].tracker[key]
|
||||
assert.True(t, info.hookFailed)
|
||||
assert.True(t, info.hookExecuted)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = mht.Record("restore1", "ns2", "pod2", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
assert.NotNil(t, err)
|
||||
|
||||
err = mht.Record("restore2", "ns2", "pod2", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestMultiHookTracker_Stat(t *testing.T) {
|
||||
mht := NewMultiHookTracker()
|
||||
|
||||
mht.Add("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
mht.Add("restore1", "ns2", "pod2", "container1", HookSourceAnnotation, "h2", "")
|
||||
mht.Record("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
mht.Record("restore1", "ns2", "pod2", "container1", HookSourceAnnotation, "h2", "", false, nil)
|
||||
|
||||
attempted, failed := mht.Stat("restore1")
|
||||
assert.Equal(t, 2, attempted)
|
||||
assert.Equal(t, 1, failed)
|
||||
}
|
||||
|
||||
func TestMultiHookTracker_Delete(t *testing.T) {
|
||||
mht := NewMultiHookTracker()
|
||||
mht.Add("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
mht.Delete("restore1")
|
||||
|
||||
_, ok := mht.trackers["restore1"]
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestMultiHookTracker_IsComplete(t *testing.T) {
|
||||
mht := NewMultiHookTracker()
|
||||
mht.Add("backup1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
mht.Record("backup1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre, true, fmt.Errorf("err"))
|
||||
assert.True(t, mht.IsComplete("backup1"))
|
||||
|
||||
mht.Add("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
assert.False(t, mht.IsComplete("restore1"))
|
||||
|
||||
assert.True(t, mht.IsComplete("restore2"))
|
||||
}
|
||||
|
||||
func TestMultiHookTracker_HookErrs(t *testing.T) {
|
||||
mht := NewMultiHookTracker()
|
||||
mht.Add("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "")
|
||||
mht.Record("restore1", "ns1", "pod1", "container1", HookSourceAnnotation, "h1", "", true, fmt.Errorf("err"))
|
||||
|
||||
hookErrs := mht.HookErrs("restore1")
|
||||
assert.Len(t, hookErrs, 1)
|
||||
|
||||
hookErrs2 := mht.HookErrs("restore2")
|
||||
assert.Empty(t, hookErrs2)
|
||||
tr := tracker.GetTracker()
|
||||
assert.NotNil(t, tr)
|
||||
|
||||
t.Logf("tracker :%+v", tr)
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
||||
hookLog.WithError(errExec).Error("Error executing hook")
|
||||
hookFailed = true
|
||||
}
|
||||
errTracker := hookTracker.Record(namespace, name, hookFromAnnotations.Container, HookSourceAnnotation, "", phase, hookFailed, errExec)
|
||||
errTracker := hookTracker.Record(namespace, name, hookFromAnnotations.Container, HookSourceAnnotation, "", phase, hookFailed)
|
||||
if errTracker != nil {
|
||||
hookLog.WithError(errTracker).Warn("Error recording the hook in hook tracker")
|
||||
}
|
||||
@@ -291,7 +291,7 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
||||
modeFailError = err
|
||||
}
|
||||
}
|
||||
errTracker := hookTracker.Record(namespace, name, hook.Exec.Container, HookSourceSpec, resourceHook.Name, phase, hookFailed, err)
|
||||
errTracker := hookTracker.Record(namespace, name, hook.Exec.Container, HookSourceSpec, resourceHook.Name, phase, hookFailed)
|
||||
if errTracker != nil {
|
||||
hookLog.WithError(errTracker).Warn("Error recording the hook in hook tracker")
|
||||
}
|
||||
@@ -540,11 +540,10 @@ type PodExecRestoreHook struct {
|
||||
// container name. If an exec hook is defined in annotation that is used, else applicable exec
|
||||
// hooks from the restore resource are accumulated.
|
||||
func GroupRestoreExecHooks(
|
||||
restoreName string,
|
||||
resourceRestoreHooks []ResourceRestoreHook,
|
||||
pod *corev1api.Pod,
|
||||
log logrus.FieldLogger,
|
||||
hookTrack *MultiHookTracker,
|
||||
hookTrack *HookTracker,
|
||||
) (map[string][]PodExecRestoreHook, error) {
|
||||
byContainer := map[string][]PodExecRestoreHook{}
|
||||
|
||||
@@ -561,7 +560,7 @@ func GroupRestoreExecHooks(
|
||||
if hookFromAnnotation.Container == "" {
|
||||
hookFromAnnotation.Container = pod.Spec.Containers[0].Name
|
||||
}
|
||||
hookTrack.Add(restoreName, metadata.GetNamespace(), metadata.GetName(), hookFromAnnotation.Container, HookSourceAnnotation, "<from-annotation>", hookPhase(""))
|
||||
hookTrack.Add(metadata.GetNamespace(), metadata.GetName(), hookFromAnnotation.Container, HookSourceAnnotation, "<from-annotation>", hookPhase(""))
|
||||
byContainer[hookFromAnnotation.Container] = []PodExecRestoreHook{
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
@@ -596,7 +595,7 @@ func GroupRestoreExecHooks(
|
||||
if named.Hook.Container == "" {
|
||||
named.Hook.Container = pod.Spec.Containers[0].Name
|
||||
}
|
||||
hookTrack.Add(restoreName, metadata.GetNamespace(), metadata.GetName(), named.Hook.Container, HookSourceSpec, rrh.Name, hookPhase(""))
|
||||
hookTrack.Add(metadata.GetNamespace(), metadata.GetName(), named.Hook.Container, HookSourceSpec, rrh.Name, hookPhase(""))
|
||||
byContainer[named.Hook.Container] = append(byContainer[named.Hook.Container], named)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1195,10 +1195,10 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
hookTracker := NewMultiHookTracker()
|
||||
hookTracker := NewHookTracker()
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual, err := GroupRestoreExecHooks("restore1", tc.resourceRestoreHooks, tc.pod, velerotest.NewLogger(), hookTracker)
|
||||
actual, err := GroupRestoreExecHooks(tc.resourceRestoreHooks, tc.pod, velerotest.NewLogger(), hookTracker)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.expected, actual)
|
||||
})
|
||||
@@ -1976,7 +1976,7 @@ func TestValidateContainer(t *testing.T) {
|
||||
expectedError := fmt.Errorf("invalid InitContainer in restore hook, it doesn't have Command, Name or Image field")
|
||||
|
||||
// valid string should return nil as result.
|
||||
assert.Nil(t, ValidateContainer([]byte(valid)))
|
||||
assert.Equal(t, nil, ValidateContainer([]byte(valid)))
|
||||
|
||||
// noName string should return expected error as result.
|
||||
assert.Equal(t, expectedError, ValidateContainer([]byte(noName)))
|
||||
@@ -2108,7 +2108,7 @@ func TestBackupHookTracker(t *testing.T) {
|
||||
phase: PhasePre,
|
||||
groupResource: "pods",
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedHookAttempted: 4,
|
||||
expectedHookAttempted: 3,
|
||||
expectedHookFailed: 2,
|
||||
pods: []podWithHook{
|
||||
{
|
||||
@@ -2351,12 +2351,14 @@ func TestBackupHookTracker(t *testing.T) {
|
||||
}
|
||||
}
|
||||
h.HandleHooks(velerotest.NewLogger(), groupResource, pod.item, pod.hooks, test.phase, hookTracker)
|
||||
|
||||
}
|
||||
actualAtemptted, actualFailed := hookTracker.Stat()
|
||||
assert.Equal(t, test.expectedHookAttempted, actualAtemptted)
|
||||
assert.Equal(t, test.expectedHookFailed, actualFailed)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRestoreHookTrackerAdd(t *testing.T) {
|
||||
@@ -2364,14 +2366,14 @@ func TestRestoreHookTrackerAdd(t *testing.T) {
|
||||
name string
|
||||
resourceRestoreHooks []ResourceRestoreHook
|
||||
pod *corev1api.Pod
|
||||
hookTracker *MultiHookTracker
|
||||
hookTracker *HookTracker
|
||||
expectedCnt int
|
||||
}{
|
||||
{
|
||||
name: "neither spec hooks nor annotations hooks are set",
|
||||
resourceRestoreHooks: nil,
|
||||
pod: builder.ForPod("default", "my-pod").Result(),
|
||||
hookTracker: NewMultiHookTracker(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 0,
|
||||
},
|
||||
{
|
||||
@@ -2390,7 +2392,7 @@ func TestRestoreHookTrackerAdd(t *testing.T) {
|
||||
Name: "container1",
|
||||
}).
|
||||
Result(),
|
||||
hookTracker: NewMultiHookTracker(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 1,
|
||||
},
|
||||
{
|
||||
@@ -2428,7 +2430,7 @@ func TestRestoreHookTrackerAdd(t *testing.T) {
|
||||
Name: "container2",
|
||||
}).
|
||||
Result(),
|
||||
hookTracker: NewMultiHookTracker(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 2,
|
||||
},
|
||||
{
|
||||
@@ -2463,19 +2465,16 @@ func TestRestoreHookTrackerAdd(t *testing.T) {
|
||||
Name: "container1",
|
||||
}).
|
||||
Result(),
|
||||
hookTracker: NewMultiHookTracker(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, _ = GroupRestoreExecHooks("restore1", tc.resourceRestoreHooks, tc.pod, velerotest.NewLogger(), tc.hookTracker)
|
||||
if _, ok := tc.hookTracker.trackers["restore1"]; !ok {
|
||||
return
|
||||
}
|
||||
tracker := tc.hookTracker.trackers["restore1"].tracker
|
||||
assert.Len(t, tracker, tc.expectedCnt)
|
||||
_, _ = GroupRestoreExecHooks(tc.resourceRestoreHooks, tc.pod, velerotest.NewLogger(), tc.hookTracker)
|
||||
tracker := tc.hookTracker.GetTracker()
|
||||
assert.Equal(t, tc.expectedCnt, len(tracker))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ type WaitExecHookHandler interface {
|
||||
log logrus.FieldLogger,
|
||||
pod *v1.Pod,
|
||||
byContainer map[string][]PodExecRestoreHook,
|
||||
multiHookTracker *MultiHookTracker,
|
||||
restoreName string,
|
||||
hookTrack *HookTracker,
|
||||
) []error
|
||||
}
|
||||
|
||||
@@ -75,8 +74,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
log logrus.FieldLogger,
|
||||
pod *v1.Pod,
|
||||
byContainer map[string][]PodExecRestoreHook,
|
||||
multiHookTracker *MultiHookTracker,
|
||||
restoreName string,
|
||||
hookTracker *HookTracker,
|
||||
) []error {
|
||||
if pod == nil {
|
||||
return nil
|
||||
@@ -169,7 +167,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
hookLog.Error(err)
|
||||
errors = append(errors, err)
|
||||
|
||||
errTracker := multiHookTracker.Record(restoreName, newPod.Namespace, newPod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), true, err)
|
||||
errTracker := hookTracker.Record(newPod.Namespace, newPod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), true)
|
||||
if errTracker != nil {
|
||||
hookLog.WithError(errTracker).Warn("Error recording the hook in hook tracker")
|
||||
}
|
||||
@@ -195,7 +193,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
hookFailed = true
|
||||
}
|
||||
|
||||
errTracker := multiHookTracker.Record(restoreName, newPod.Namespace, newPod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), hookFailed, hookErr)
|
||||
errTracker := hookTracker.Record(newPod.Namespace, newPod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), hookFailed)
|
||||
if errTracker != nil {
|
||||
hookLog.WithError(errTracker).Warn("Error recording the hook in hook tracker")
|
||||
}
|
||||
@@ -247,7 +245,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
||||
},
|
||||
)
|
||||
|
||||
errTracker := multiHookTracker.Record(restoreName, pod.Namespace, pod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), true, err)
|
||||
errTracker := hookTracker.Record(pod.Namespace, pod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), true)
|
||||
if errTracker != nil {
|
||||
hookLog.WithError(errTracker).Warn("Error recording the hook in hook tracker")
|
||||
}
|
||||
|
||||
@@ -710,6 +710,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
source := fcache.NewFakeControllerSource()
|
||||
go func() {
|
||||
// This is the state of the pod that will be seen by the AddFunc handler.
|
||||
@@ -743,8 +744,8 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
||||
defer ctxCancel()
|
||||
}
|
||||
|
||||
hookTracker := NewMultiHookTracker()
|
||||
errs := h.HandleHooks(ctx, velerotest.NewLogger(), test.initialPod, test.byContainer, hookTracker, "restore1")
|
||||
hookTracker := NewHookTracker()
|
||||
errs := h.HandleHooks(ctx, velerotest.NewLogger(), test.initialPod, test.byContainer, hookTracker)
|
||||
|
||||
// for i, ee := range test.expectedErrors {
|
||||
require.Len(t, errs, len(test.expectedErrors))
|
||||
@@ -1011,18 +1012,15 @@ func TestRestoreHookTrackerUpdate(t *testing.T) {
|
||||
pod *v1.Pod
|
||||
}
|
||||
|
||||
hookTracker1 := NewMultiHookTracker()
|
||||
hookTracker1.Add("restore1", "default", "my-pod", "container1", HookSourceAnnotation, "<from-annotation>", hookPhase(""))
|
||||
hookTracker1 := NewHookTracker()
|
||||
hookTracker1.Add("default", "my-pod", "container1", HookSourceAnnotation, "<from-annotation>", hookPhase(""))
|
||||
|
||||
hookTracker2 := NewMultiHookTracker()
|
||||
hookTracker2.Add("restore1", "default", "my-pod", "container1", HookSourceSpec, "my-hook-1", hookPhase(""))
|
||||
hookTracker2 := NewHookTracker()
|
||||
hookTracker2.Add("default", "my-pod", "container1", HookSourceSpec, "my-hook-1", hookPhase(""))
|
||||
|
||||
hookTracker3 := NewMultiHookTracker()
|
||||
hookTracker3.Add("restore1", "default", "my-pod", "container1", HookSourceSpec, "my-hook-1", hookPhase(""))
|
||||
hookTracker3.Add("restore1", "default", "my-pod", "container2", HookSourceSpec, "my-hook-2", hookPhase(""))
|
||||
|
||||
hookTracker4 := NewMultiHookTracker()
|
||||
hookTracker4.Add("restore1", "default", "my-pod", "container1", HookSourceSpec, "my-hook-1", hookPhase(""))
|
||||
hookTracker3 := NewHookTracker()
|
||||
hookTracker3.Add("default", "my-pod", "container1", HookSourceSpec, "my-hook-1", hookPhase(""))
|
||||
hookTracker3.Add("default", "my-pod", "container2", HookSourceSpec, "my-hook-2", hookPhase(""))
|
||||
|
||||
tests1 := []struct {
|
||||
name string
|
||||
@@ -1030,7 +1028,7 @@ func TestRestoreHookTrackerUpdate(t *testing.T) {
|
||||
groupResource string
|
||||
byContainer map[string][]PodExecRestoreHook
|
||||
expectedExecutions []expectedExecution
|
||||
hookTracker *MultiHookTracker
|
||||
hookTracker *HookTracker
|
||||
expectedFailed int
|
||||
}{
|
||||
{
|
||||
@@ -1162,7 +1160,7 @@ func TestRestoreHookTrackerUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
hookTracker: hookTracker4,
|
||||
hookTracker: hookTracker2,
|
||||
expectedFailed: 1,
|
||||
},
|
||||
{
|
||||
@@ -1246,13 +1244,14 @@ func TestRestoreHookTrackerUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
hookTracker: NewMultiHookTracker(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedFailed: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests1 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
source := fcache.NewFakeControllerSource()
|
||||
go func() {
|
||||
// This is the state of the pod that will be seen by the AddFunc handler.
|
||||
@@ -1274,8 +1273,8 @@ func TestRestoreHookTrackerUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
_ = h.HandleHooks(ctx, velerotest.NewLogger(), test.initialPod, test.byContainer, test.hookTracker, "restore1")
|
||||
_, actualFailed := test.hookTracker.Stat("restore1")
|
||||
_ = h.HandleHooks(ctx, velerotest.NewLogger(), test.initialPod, test.byContainer, test.hookTracker)
|
||||
_, actualFailed := test.hookTracker.Stat()
|
||||
assert.Equal(t, test.expectedFailed, actualFailed)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package resourcemodifiers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -86,22 +86,8 @@ func GetResourceModifiersFromConfig(cm *v1.ConfigMap) (*ResourceModifiers, error
|
||||
|
||||
func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstructured, groupResource string, scheme *runtime.Scheme, log logrus.FieldLogger) []error {
|
||||
var errs []error
|
||||
origin := obj
|
||||
// If there are more than one rules, we need to keep the original object for condition matching
|
||||
if len(p.ResourceModifierRules) > 1 {
|
||||
origin = obj.DeepCopy()
|
||||
}
|
||||
for _, rule := range p.ResourceModifierRules {
|
||||
matched, err := rule.match(origin, groupResource, log)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
} else if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Applying resource modifier patch on %s/%s", origin.GetNamespace(), origin.GetName())
|
||||
err = rule.applyPatch(obj, scheme, log)
|
||||
err := rule.apply(obj, groupResource, scheme, log)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
@@ -110,54 +96,59 @@ func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstruc
|
||||
return errs
|
||||
}
|
||||
|
||||
func (r *ResourceModifierRule) match(obj *unstructured.Unstructured, groupResource string, log logrus.FieldLogger) (bool, error) {
|
||||
func (r *ResourceModifierRule) apply(obj *unstructured.Unstructured, groupResource string, scheme *runtime.Scheme, log logrus.FieldLogger) error {
|
||||
ns := obj.GetNamespace()
|
||||
if ns != "" {
|
||||
namespaceInclusion := collections.NewIncludesExcludes().Includes(r.Conditions.Namespaces...)
|
||||
if !namespaceInclusion.ShouldInclude(ns) {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
g, err := glob.Compile(r.Conditions.GroupResource, '.')
|
||||
if err != nil {
|
||||
log.Errorf("Bad glob pattern of groupResource in condition, groupResource: %s, err: %s", r.Conditions.GroupResource, err)
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
if !g.Match(groupResource) {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.Conditions.ResourceNameRegex != "" {
|
||||
match, err := regexp.MatchString(r.Conditions.ResourceNameRegex, obj.GetName())
|
||||
if err != nil {
|
||||
return false, errors.Errorf("error in matching regex %s", err.Error())
|
||||
return errors.Errorf("error in matching regex %s", err.Error())
|
||||
}
|
||||
if !match {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if r.Conditions.LabelSelector != nil {
|
||||
selector, err := metav1.LabelSelectorAsSelector(r.Conditions.LabelSelector)
|
||||
if err != nil {
|
||||
return false, errors.Errorf("error in creating label selector %s", err.Error())
|
||||
return errors.Errorf("error in creating label selector %s", err.Error())
|
||||
}
|
||||
if !selector.Matches(labels.Set(obj.GetLabels())) {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
match, err := matchConditions(obj, r.Conditions.Matches, log)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return err
|
||||
} else if !match {
|
||||
log.Info("Conditions do not match, skip it")
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
log.Infof("Applying resource modifier patch on %s/%s", obj.GetNamespace(), obj.GetName())
|
||||
err = r.applyPatch(obj, scheme, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchConditions(u *unstructured.Unstructured, rules []MatchRule, _ logrus.FieldLogger) (bool, error) {
|
||||
|
||||
@@ -428,6 +428,7 @@ func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
|
||||
pvcStandardSc := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -456,20 +457,6 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
pvcGoldSc := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "PersistentVolumeClaim",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-pvc",
|
||||
"namespace": "foo",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"storageClassName": "gold",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
deployNginxOneReplica := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -693,110 +680,6 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantObj: pvcPremiumSc.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "pvc with standard storage class should be patched to premium, even when rules are [standard => premium, premium => gold]",
|
||||
fields: fields{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Matches: []MatchRule{
|
||||
{
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "standard",
|
||||
},
|
||||
},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "premium",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Matches: []MatchRule{
|
||||
{
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "premium",
|
||||
},
|
||||
},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "gold",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
obj: pvcStandardSc.DeepCopy(),
|
||||
groupResource: "persistentvolumeclaims",
|
||||
},
|
||||
wantErr: false,
|
||||
wantObj: pvcPremiumSc.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "pvc with standard storage class should be patched to gold, even when rules are [standard => premium, standard => gold]",
|
||||
fields: fields{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Matches: []MatchRule{
|
||||
{
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "standard",
|
||||
},
|
||||
},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "premium",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "persistentvolumeclaims",
|
||||
ResourceNameRegex: ".*",
|
||||
Matches: []MatchRule{
|
||||
{
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "standard",
|
||||
},
|
||||
},
|
||||
},
|
||||
Patches: []JSONPatch{
|
||||
{
|
||||
Operation: "replace",
|
||||
Path: "/spec/storageClassName",
|
||||
Value: "gold",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
obj: pvcStandardSc.DeepCopy(),
|
||||
groupResource: "persistentvolumeclaims",
|
||||
},
|
||||
wantErr: false,
|
||||
wantObj: pvcGoldSc.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "nginx deployment: 1 -> 2 replicas",
|
||||
fields: fields{
|
||||
|
||||
@@ -27,13 +27,8 @@ type VolumeActionType string
|
||||
|
||||
const (
|
||||
// currently only support configmap type of resource config
|
||||
ConfigmapRefType string = "configmap"
|
||||
// skip action implies the volume would be skipped from the backup operation
|
||||
Skip VolumeActionType = "skip"
|
||||
// fs-backup action implies that the volume would be backed up via file system copy method using the uploader(kopia/restic) configured by the user
|
||||
FSBackup VolumeActionType = "fs-backup"
|
||||
// snapshot action can have 3 different meaning based on velero configuration and backup spec - cloud provider based snapshots, local csi snapshots and datamover snapshots
|
||||
Snapshot VolumeActionType = "snapshot"
|
||||
ConfigmapRefType string = "configmap"
|
||||
Skip VolumeActionType = "skip"
|
||||
)
|
||||
|
||||
// Action defined as one action for a specific way of backup
|
||||
@@ -45,16 +40,16 @@ type Action struct {
|
||||
}
|
||||
|
||||
// volumePolicy defined policy to conditions to match Volumes and related action to handle matched Volumes
|
||||
type VolumePolicy struct {
|
||||
type volumePolicy struct {
|
||||
// Conditions defined list of conditions to match Volumes
|
||||
Conditions map[string]interface{} `yaml:"conditions"`
|
||||
Action Action `yaml:"action"`
|
||||
}
|
||||
|
||||
// resourcePolicies currently defined slice of volume policies to handle backup
|
||||
type ResourcePolicies struct {
|
||||
type resourcePolicies struct {
|
||||
Version string `yaml:"version"`
|
||||
VolumePolicies []VolumePolicy `yaml:"volumePolicies"`
|
||||
VolumePolicies []volumePolicy `yaml:"volumePolicies"`
|
||||
// we may support other resource policies in the future, and they could be added separately
|
||||
// OtherResourcePolicies []OtherResourcePolicy
|
||||
}
|
||||
@@ -65,8 +60,8 @@ type Policies struct {
|
||||
// OtherPolicies
|
||||
}
|
||||
|
||||
func unmarshalResourcePolicies(yamlData *string) (*ResourcePolicies, error) {
|
||||
resPolicies := &ResourcePolicies{}
|
||||
func unmarshalResourcePolicies(yamlData *string) (*resourcePolicies, error) {
|
||||
resPolicies := &resourcePolicies{}
|
||||
err := decodeStruct(strings.NewReader(*yamlData), resPolicies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode yaml data into resource policies %v", err)
|
||||
@@ -74,7 +69,7 @@ func unmarshalResourcePolicies(yamlData *string) (*ResourcePolicies, error) {
|
||||
return resPolicies, nil
|
||||
}
|
||||
|
||||
func (p *Policies) BuildPolicy(resPolicies *ResourcePolicies) error {
|
||||
func (p *Policies) buildPolicy(resPolicies *resourcePolicies) error {
|
||||
for _, vp := range resPolicies.VolumePolicies {
|
||||
con, err := unmarshalVolConditions(vp.Conditions)
|
||||
if err != nil {
|
||||
@@ -167,7 +162,7 @@ func GetResourcePoliciesFromConfig(cm *v1.ConfigMap) (*Policies, error) {
|
||||
}
|
||||
|
||||
policies := &Policies{}
|
||||
if err := policies.BuildPolicy(resPolicies); err != nil {
|
||||
if err := policies.buildPolicy(resPolicies); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -121,9 +121,9 @@ func TestLoadResourcePolicies(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetResourceMatchedAction(t *testing.T) {
|
||||
resPolicies := &ResourcePolicies{
|
||||
resPolicies := &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -136,7 +136,7 @@ func TestGetResourceMatchedAction(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: Action{Type: "snapshot"},
|
||||
Action: Action{Type: "volume-snapshot"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "10,100Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
@@ -147,7 +147,7 @@ func TestGetResourceMatchedAction(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: Action{Type: "fs-backup"},
|
||||
Action: Action{Type: "file-system-backup"},
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
@@ -179,10 +179,10 @@ func TestGetResourceMatchedAction(t *testing.T) {
|
||||
storageClass: "ebs-sc",
|
||||
csi: &csiVolumeSource{Driver: "aws.efs.csi.driver"},
|
||||
},
|
||||
expectedAction: &Action{Type: "snapshot"},
|
||||
expectedAction: &Action{Type: "volume-snapshot"},
|
||||
},
|
||||
{
|
||||
name: "mismatch all policies",
|
||||
name: "dismatch all policies",
|
||||
volume: &structuredVolume{
|
||||
capacity: *resource.NewQuantity(50<<30, resource.BinarySI),
|
||||
storageClass: "ebs-sc",
|
||||
@@ -195,7 +195,7 @@ func TestGetResourceMatchedAction(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
policies := &Policies{}
|
||||
err := policies.BuildPolicy(resPolicies)
|
||||
err := policies.buildPolicy(resPolicies)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to build policy with error %v", err)
|
||||
}
|
||||
@@ -237,9 +237,9 @@ func TestGetResourcePoliciesFromConfig(t *testing.T) {
|
||||
// Check that the returned resourcePolicies object contains the expected data
|
||||
assert.Equal(t, "v1", resPolicies.version)
|
||||
assert.Len(t, resPolicies.volumePolicies, 1)
|
||||
policies := ResourcePolicies{
|
||||
policies := resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
@@ -251,7 +251,7 @@ func TestGetResourcePoliciesFromConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
p := &Policies{}
|
||||
err = p.BuildPolicy(&policies)
|
||||
err = p.buildPolicy(&policies)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build policy with error %v", err)
|
||||
}
|
||||
@@ -394,7 +394,7 @@ volumePolicies:
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
name: "mismatch volume by types",
|
||||
name: "dismatch volume by types",
|
||||
yamlData: `version: v1
|
||||
volumePolicies:
|
||||
- conditions:
|
||||
@@ -424,7 +424,7 @@ volumePolicies:
|
||||
}
|
||||
assert.Nil(t, err)
|
||||
policies := &Policies{}
|
||||
err = policies.BuildPolicy(resPolicies)
|
||||
err = policies.buildPolicy(resPolicies)
|
||||
assert.Nil(t, err)
|
||||
action, err := policies.GetMatchAction(tc.vol)
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -86,6 +86,7 @@ func TestCapacityIsInRange(t *testing.T) {
|
||||
actual := test.capacity.isInRange(test.quantity)
|
||||
|
||||
assert.Equal(t, test.isInRange, actual)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,6 +141,7 @@ func TestStorageClassConditionMatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNFSConditionMatch(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition *nfsCondition
|
||||
@@ -165,7 +167,7 @@ func TestNFSConditionMatch(t *testing.T) {
|
||||
expectedMatch: true,
|
||||
},
|
||||
{
|
||||
name: "server mismatch",
|
||||
name: "server dismatch",
|
||||
condition: &nfsCondition{&nFSVolumeSource{Server: "192.168.10.20", Path: ""}},
|
||||
volume: setStructuredVolume(*resource.NewQuantity(0, resource.BinarySI), "", &nFSVolumeSource{Server: ""}, nil),
|
||||
expectedMatch: false,
|
||||
@@ -194,6 +196,7 @@ func TestNFSConditionMatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCSIConditionMatch(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition *csiCondition
|
||||
|
||||
@@ -82,11 +82,7 @@ func decodeStruct(r io.Reader, s interface{}) error {
|
||||
// validate check action format
|
||||
func (a *Action) validate() error {
|
||||
// validate Type
|
||||
valid := false
|
||||
if a.Type == Skip || a.Type == Snapshot || a.Type == FSBackup {
|
||||
valid = true
|
||||
}
|
||||
if !valid {
|
||||
if a.Type != Skip {
|
||||
return fmt.Errorf("invalid action type %s", a.Type)
|
||||
}
|
||||
|
||||
|
||||
@@ -84,14 +84,14 @@ func TestCapacityConditionValidate(t *testing.T) {
|
||||
func TestValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
res *ResourcePolicies
|
||||
res *resourcePolicies
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "unknown key in yaml",
|
||||
res: &ResourcePolicies{
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -110,9 +110,9 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error format of capacity",
|
||||
res: &ResourcePolicies{
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -130,9 +130,9 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error format of storageClass",
|
||||
res: &ResourcePolicies{
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -150,9 +150,9 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error format of csi",
|
||||
res: &ResourcePolicies{
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -167,9 +167,9 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "unsupported version",
|
||||
res: &ResourcePolicies{
|
||||
res: &resourcePolicies{
|
||||
Version: "v2",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -186,9 +186,9 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "unsupported action",
|
||||
res: &ResourcePolicies{
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "unsupported"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -205,9 +205,9 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error format of nfs",
|
||||
res: &ResourcePolicies{
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -221,10 +221,10 @@ func TestValidate(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "supported format volume policies",
|
||||
res: &ResourcePolicies{
|
||||
name: "supported formart volume policies",
|
||||
res: &resourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
VolumePolicies: []volumePolicy{
|
||||
{
|
||||
Action: Action{Type: "skip"},
|
||||
Conditions: map[string]interface{}{
|
||||
@@ -245,86 +245,11 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "supported format volume policies, action type snapshot",
|
||||
res: &ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
{
|
||||
Action: Action{Type: "snapshot"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
"nfs": interface{}(
|
||||
map[string]interface{}{
|
||||
"server": "192.168.20.90",
|
||||
"path": "/mnt/data/",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "supported format volume policies, action type fs-backup",
|
||||
res: &ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
{
|
||||
Action: Action{Type: "fs-backup"},
|
||||
Conditions: map[string]interface{}{
|
||||
"capacity": "0,10Gi",
|
||||
"storageClass": []string{"gp2", "ebs-sc"},
|
||||
"csi": interface{}(
|
||||
map[string]interface{}{
|
||||
"driver": "aws.efs.csi.driver",
|
||||
}),
|
||||
"nfs": interface{}(
|
||||
map[string]interface{}{
|
||||
"server": "192.168.20.90",
|
||||
"path": "/mnt/data/",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "supported format volume policies, action type fs-backup and snapshot",
|
||||
res: &ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []VolumePolicy{
|
||||
{
|
||||
Action: Action{Type: Snapshot},
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: Action{Type: FSBackup},
|
||||
Conditions: map[string]interface{}{
|
||||
"nfs": interface{}(
|
||||
map[string]interface{}{
|
||||
"server": "192.168.20.90",
|
||||
"path": "/mnt/data/",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
policies := &Policies{}
|
||||
err1 := policies.BuildPolicy(tc.res)
|
||||
err1 := policies.buildPolicy(tc.res)
|
||||
err2 := policies.Validate()
|
||||
|
||||
if tc.wantErr {
|
||||
|
||||
@@ -166,10 +166,10 @@ func TestListBackupStorageLocations(t *testing.T) {
|
||||
client := fake.NewClientBuilder().WithScheme(util.VeleroScheme).WithRuntimeObjects(tt.backupLocations).Build()
|
||||
if tt.expectError {
|
||||
_, err := ListBackupStorageLocations(context.Background(), client, "ns-1")
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err).NotTo(BeNil())
|
||||
} else {
|
||||
_, err := ListBackupStorageLocations(context.Background(), client, "ns-1")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(err).To(BeNil())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ func testDefaultImage(t *testing.T, defaultImageFn func() string, imageName stri
|
||||
assert.Equal(t, tc.want, defaultImageFn())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDefaultVeleroImage(t *testing.T) {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
Copyright The Velero Contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// it has to have the same value as "github.com/vmware-tanzu/velero/pkg/restore".ItemRestoreResultCreated
|
||||
const itemRestoreResultCreated = "created"
|
||||
|
||||
// RestoredPVCFromRestoredResourceList returns a set of PVCs that were restored from the given restoredResourceList.
|
||||
func RestoredPVCFromRestoredResourceList(restoredResourceList map[string][]string) map[string]struct{} {
|
||||
pvcKey := "v1/PersistentVolumeClaim"
|
||||
pvcList := make(map[string]struct{})
|
||||
|
||||
for _, pvc := range restoredResourceList[pvcKey] {
|
||||
// the format of pvc string in restoredResourceList is like: "namespace/pvcName(status)"
|
||||
// extract the substring before "(created)" if the status in rightmost Parenthesis is "created"
|
||||
r := regexp.MustCompile(`\(([^)]+)\)`)
|
||||
matches := r.FindAllStringSubmatch(pvc, -1)
|
||||
if len(matches) > 0 && matches[len(matches)-1][1] == itemRestoreResultCreated {
|
||||
pvcList[pvc[:len(pvc)-len("(created)")]] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return pvcList
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright The Velero Contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetRestoredPVCFromRestoredResourceList(t *testing.T) {
|
||||
// test empty list
|
||||
restoredResourceList := map[string][]string{}
|
||||
actual := RestoredPVCFromRestoredResourceList(restoredResourceList)
|
||||
assert.Empty(t, actual)
|
||||
|
||||
// test no match
|
||||
restoredResourceList = map[string][]string{
|
||||
"v1/PersistentVolumeClaim": {
|
||||
"namespace1/pvc1(updated)",
|
||||
},
|
||||
"v1/PersistentVolume": {
|
||||
"namespace1/pv(created)",
|
||||
},
|
||||
}
|
||||
actual = RestoredPVCFromRestoredResourceList(restoredResourceList)
|
||||
assert.Empty(t, actual)
|
||||
|
||||
// test matches
|
||||
restoredResourceList = map[string][]string{
|
||||
"v1/PersistentVolumeClaim": {
|
||||
"namespace1/pvc1(created)",
|
||||
"namespace2/pvc2(updated)",
|
||||
"namespace3/pvc(3)(created)",
|
||||
},
|
||||
}
|
||||
expected := map[string]struct{}{
|
||||
"namespace1/pvc1": {},
|
||||
"namespace3/pvc(3)": {},
|
||||
}
|
||||
actual = RestoredPVCFromRestoredResourceList(restoredResourceList)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
@@ -19,47 +19,34 @@ package volume
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||
"github.com/pkg/errors"
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/features"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
type Method string
|
||||
type VolumeBackupMethod string
|
||||
|
||||
const (
|
||||
NativeSnapshot Method = "NativeSnapshot"
|
||||
PodVolumeBackup Method = "PodVolumeBackup"
|
||||
CSISnapshot Method = "CSISnapshot"
|
||||
PodVolumeRestore Method = "PodVolumeRestore"
|
||||
NativeSnapshot VolumeBackupMethod = "NativeSnapshot"
|
||||
PodVolumeBackup VolumeBackupMethod = "PodVolumeBackup"
|
||||
CSISnapshot VolumeBackupMethod = "CSISnapshot"
|
||||
)
|
||||
|
||||
const (
|
||||
FieldValueIsUnknown string = "unknown"
|
||||
kopia string = "kopia"
|
||||
veleroDatamover string = "velero"
|
||||
|
||||
//TODO reuse these constants from csi-plugin-for-velero after it's merged into the same repo
|
||||
|
||||
CSIDriverNameAnnotation = "velero.io/csi-driver-name"
|
||||
VolumeSnapshotHandleAnnotation = "velero.io/csi-volumesnapshot-handle"
|
||||
)
|
||||
|
||||
type BackupVolumeInfo struct {
|
||||
type VolumeInfo struct {
|
||||
// The PVC's name.
|
||||
PVCName string `json:"pvcName,omitempty"`
|
||||
|
||||
@@ -70,7 +57,7 @@ type BackupVolumeInfo struct {
|
||||
PVName string `json:"pvName,omitempty"`
|
||||
|
||||
// The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup` and `CSISnapshot`.
|
||||
BackupMethod Method `json:"backupMethod,omitempty"`
|
||||
BackupMethod VolumeBackupMethod `json:"backupMethod,omitempty"`
|
||||
|
||||
// Whether the volume's snapshot data is moved to specified storage.
|
||||
SnapshotDataMoved bool `json:"snapshotDataMoved"`
|
||||
@@ -89,40 +76,13 @@ type BackupVolumeInfo struct {
|
||||
// Snapshot starts timestamp.
|
||||
StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"`
|
||||
|
||||
// Snapshot completes timestamp.
|
||||
CompletionTimestamp *metav1.Time `json:"completionTimestamp,omitempty"`
|
||||
|
||||
CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
|
||||
SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
|
||||
NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
|
||||
PVBInfo *PodVolumeInfo `json:"pvbInfo,omitempty"`
|
||||
PVBInfo *PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
|
||||
PVInfo *PVInfo `json:"pvInfo,omitempty"`
|
||||
}
|
||||
|
||||
type RestoreVolumeInfo struct {
|
||||
// The name of the restored PVC
|
||||
PVCName string `json:"pvcName,omitempty"`
|
||||
|
||||
// The namespace of the restored PVC
|
||||
PVCNamespace string `json:"pvcNamespace,omitempty"`
|
||||
|
||||
// The name of the restored PV, it is possible that in one item there is only PVC or PV info.
|
||||
// But if both PVC and PV exist in one item of volume info, they should matched, and if the PV is bound to a PVC,
|
||||
// they should coexist in one item.
|
||||
PVName string `json:"pvName,omitempty"`
|
||||
|
||||
// The way the volume data is restored.
|
||||
RestoreMethod Method `json:"restoreMethod,omitempty"`
|
||||
|
||||
// Whether the volume's data are restored via data movement
|
||||
SnapshotDataMoved bool `json:"snapshotDataMoved"`
|
||||
|
||||
CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
|
||||
SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
|
||||
NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
|
||||
PVRInfo *PodVolumeInfo `json:"pvrInfo,omitempty"`
|
||||
}
|
||||
|
||||
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
||||
type CSISnapshotInfo struct {
|
||||
// It's the storage provider's snapshot ID for CSI.
|
||||
@@ -138,7 +98,7 @@ type CSISnapshotInfo struct {
|
||||
VSCName string `json:"vscName"`
|
||||
|
||||
// The Async Operation's ID.
|
||||
OperationID string `json:"operationID,omitempty"`
|
||||
OperationID string `json:"operationID"`
|
||||
}
|
||||
|
||||
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
|
||||
@@ -152,16 +112,13 @@ type SnapshotDataMovementInfo struct {
|
||||
// The name or ID of the snapshot associated object(SAO).
|
||||
// SAO is used to support local snapshots for the snapshot data mover,
|
||||
// e.g. it could be a VolumeSnapshot for CSI snapshot data movement.
|
||||
RetainedSnapshot string `json:"retainedSnapshot,omitempty"`
|
||||
RetainedSnapshot string `json:"retainedSnapshot"`
|
||||
|
||||
// It's the filesystem repository's snapshot ID.
|
||||
SnapshotHandle string `json:"snapshotHandle"`
|
||||
|
||||
// The Async Operation's ID.
|
||||
OperationID string `json:"operationID"`
|
||||
|
||||
// Moved snapshot data size.
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// NativeSnapshotInfo is used for displaying the Velero native snapshot status.
|
||||
@@ -182,26 +139,13 @@ type NativeSnapshotInfo struct {
|
||||
IOPS string `json:"iops"`
|
||||
}
|
||||
|
||||
func newNativeSnapshotInfo(s *Snapshot) *NativeSnapshotInfo {
|
||||
var iops int64
|
||||
if s.Spec.VolumeIOPS != nil {
|
||||
iops = *s.Spec.VolumeIOPS
|
||||
}
|
||||
return &NativeSnapshotInfo{
|
||||
SnapshotHandle: s.Status.ProviderSnapshotID,
|
||||
VolumeType: s.Spec.VolumeType,
|
||||
VolumeAZ: s.Spec.VolumeAZ,
|
||||
IOPS: strconv.FormatInt(iops, 10),
|
||||
}
|
||||
}
|
||||
|
||||
// PodVolumeInfo is used for displaying the PodVolumeBackup/PodVolumeRestore snapshot status.
|
||||
type PodVolumeInfo struct {
|
||||
// It's the file-system uploader's snapshot ID for PodVolumeBackup/PodVolumeRestore.
|
||||
SnapshotHandle string `json:"snapshotHandle,omitempty"`
|
||||
// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status.
|
||||
type PodVolumeBackupInfo struct {
|
||||
// It's the file-system uploader's snapshot ID for PodVolumeBackup.
|
||||
SnapshotHandle string `json:"snapshotHandle"`
|
||||
|
||||
// The snapshot corresponding volume size.
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Size int64 `json:"size"`
|
||||
|
||||
// The type of the uploader that uploads the data. The valid values are `kopia` and `restic`.
|
||||
UploaderType string `json:"uploaderType"`
|
||||
@@ -217,31 +161,7 @@ type PodVolumeInfo struct {
|
||||
PodNamespace string `json:"podNamespace"`
|
||||
|
||||
// The PVB-taken k8s node's name.
|
||||
// This field will be empty when the struct is used to represent a podvolumerestore.
|
||||
NodeName string `json:"nodeName,omitempty"`
|
||||
}
|
||||
|
||||
func newPodVolumeInfoFromPVB(pvb *velerov1api.PodVolumeBackup) *PodVolumeInfo {
|
||||
return &PodVolumeInfo{
|
||||
SnapshotHandle: pvb.Status.SnapshotID,
|
||||
Size: pvb.Status.Progress.TotalBytes,
|
||||
UploaderType: pvb.Spec.UploaderType,
|
||||
VolumeName: pvb.Spec.Volume,
|
||||
PodName: pvb.Spec.Pod.Name,
|
||||
PodNamespace: pvb.Spec.Pod.Namespace,
|
||||
NodeName: pvb.Spec.Node,
|
||||
}
|
||||
}
|
||||
|
||||
func newPodVolumeInfoFromPVR(pvr *velerov1api.PodVolumeRestore) *PodVolumeInfo {
|
||||
return &PodVolumeInfo{
|
||||
SnapshotHandle: pvr.Spec.SnapshotID,
|
||||
Size: pvr.Status.Progress.TotalBytes,
|
||||
UploaderType: pvr.Spec.UploaderType,
|
||||
VolumeName: pvr.Spec.Volume,
|
||||
PodName: pvr.Spec.Pod.Name,
|
||||
PodNamespace: pvr.Spec.Pod.Namespace,
|
||||
}
|
||||
NodeName string `json:"nodeName"`
|
||||
}
|
||||
|
||||
// PVInfo is used to store some PV information modified after creation.
|
||||
@@ -254,12 +174,12 @@ type PVInfo struct {
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
||||
// BackupVolumesInformation contains the information needs by generating
|
||||
// the backup BackupVolumeInfo array.
|
||||
type BackupVolumesInformation struct {
|
||||
// VolumesInformation contains the information needs by generating
|
||||
// the backup VolumeInfo array.
|
||||
type VolumesInformation struct {
|
||||
// A map contains the backup-included PV detail content. The key is PV name.
|
||||
pvMap *pvcPvMap
|
||||
volumeInfos []*BackupVolumeInfo
|
||||
pvMap map[string]pvcPvInfo
|
||||
volumeInfos []*VolumeInfo
|
||||
|
||||
logger logrus.FieldLogger
|
||||
crClient kbclient.Client
|
||||
@@ -267,7 +187,7 @@ type BackupVolumesInformation struct {
|
||||
volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent
|
||||
volumeSnapshotClasses []snapshotv1api.VolumeSnapshotClass
|
||||
SkippedPVs map[string]string
|
||||
NativeSnapshots []*Snapshot
|
||||
NativeSnapshots []*volume.Snapshot
|
||||
PodVolumeBackups []*velerov1api.PodVolumeBackup
|
||||
BackupOperations []*itemoperation.BackupOperation
|
||||
BackupName string
|
||||
@@ -279,27 +199,30 @@ type pvcPvInfo struct {
|
||||
PV corev1api.PersistentVolume
|
||||
}
|
||||
|
||||
func (v *BackupVolumesInformation) Init() {
|
||||
v.pvMap = &pvcPvMap{
|
||||
data: make(map[string]pvcPvInfo),
|
||||
}
|
||||
v.volumeInfos = make([]*BackupVolumeInfo, 0)
|
||||
func (v *VolumesInformation) Init() {
|
||||
v.pvMap = make(map[string]pvcPvInfo)
|
||||
v.volumeInfos = make([]*VolumeInfo, 0)
|
||||
}
|
||||
|
||||
func (v *BackupVolumesInformation) InsertPVMap(pv corev1api.PersistentVolume, pvcName, pvcNamespace string) {
|
||||
func (v *VolumesInformation) InsertPVMap(pv corev1api.PersistentVolume, pvcName, pvcNamespace string) {
|
||||
if v.pvMap == nil {
|
||||
v.Init()
|
||||
}
|
||||
v.pvMap.insert(pv, pvcName, pvcNamespace)
|
||||
|
||||
v.pvMap[pv.Name] = pvcPvInfo{
|
||||
PVCName: pvcName,
|
||||
PVCNamespace: pvcNamespace,
|
||||
PV: pv,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *BackupVolumesInformation) Result(
|
||||
func (v *VolumesInformation) Result(
|
||||
csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent,
|
||||
csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||
crClient kbclient.Client,
|
||||
logger logrus.FieldLogger,
|
||||
) []*BackupVolumeInfo {
|
||||
) []*VolumeInfo {
|
||||
v.logger = logger
|
||||
v.crClient = crClient
|
||||
v.volumeSnapshots = csiVolumeSnapshots
|
||||
@@ -316,12 +239,12 @@ func (v *BackupVolumesInformation) Result(
|
||||
}
|
||||
|
||||
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
|
||||
func (v *BackupVolumesInformation) generateVolumeInfoForSkippedPV() {
|
||||
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||
func (v *VolumesInformation) generateVolumeInfoForSkippedPV() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for pvName, skippedReason := range v.SkippedPVs {
|
||||
if pvcPVInfo := v.pvMap.retrieve(pvName, "", ""); pvcPVInfo != nil {
|
||||
volumeInfo := &BackupVolumeInfo{
|
||||
if pvcPVInfo := v.retrievePvcPvInfo(pvName, "", ""); pvcPVInfo != nil {
|
||||
volumeInfo := &VolumeInfo{
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvName,
|
||||
@@ -344,24 +267,35 @@ func (v *BackupVolumesInformation) generateVolumeInfoForSkippedPV() {
|
||||
}
|
||||
|
||||
// generateVolumeInfoForVeleroNativeSnapshot generate VolumeInfos for Velero native snapshot
|
||||
func (v *BackupVolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
|
||||
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||
func (v *VolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for _, nativeSnapshot := range v.NativeSnapshots {
|
||||
if pvcPVInfo := v.pvMap.retrieve(nativeSnapshot.Spec.PersistentVolumeName, "", ""); pvcPVInfo != nil {
|
||||
volumeInfo := &BackupVolumeInfo{
|
||||
BackupMethod: NativeSnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
NativeSnapshotInfo: newNativeSnapshotInfo(nativeSnapshot),
|
||||
var iops int64
|
||||
if nativeSnapshot.Spec.VolumeIOPS != nil {
|
||||
iops = *nativeSnapshot.Spec.VolumeIOPS
|
||||
}
|
||||
|
||||
if pvcPVInfo := v.retrievePvcPvInfo(nativeSnapshot.Spec.PersistentVolumeName, "", ""); pvcPVInfo != nil {
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: NativeSnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
NativeSnapshotInfo: &NativeSnapshotInfo{
|
||||
SnapshotHandle: nativeSnapshot.Status.ProviderSnapshotID,
|
||||
VolumeType: nativeSnapshot.Spec.VolumeType,
|
||||
VolumeAZ: nativeSnapshot.Spec.VolumeAZ,
|
||||
IOPS: strconv.FormatInt(iops, 10),
|
||||
},
|
||||
PVInfo: &PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
v.logger.Warnf("cannot find info for PV %s", nativeSnapshot.Spec.PersistentVolumeName)
|
||||
@@ -373,8 +307,8 @@ func (v *BackupVolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
|
||||
}
|
||||
|
||||
// generateVolumeInfoForCSIVolumeSnapshot generate VolumeInfos for CSI VolumeSnapshot
|
||||
func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||
func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for _, volumeSnapshot := range v.volumeSnapshots {
|
||||
var volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
||||
@@ -436,8 +370,8 @@ func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||
if volumeSnapshotContent.Status.SnapshotHandle != nil {
|
||||
snapshotHandle = *volumeSnapshotContent.Status.SnapshotHandle
|
||||
}
|
||||
if pvcPVInfo := v.pvMap.retrieve("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
|
||||
volumeInfo := &BackupVolumeInfo{
|
||||
if pvcPVInfo := v.retrievePvcPvInfo("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: CSISnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
@@ -445,6 +379,7 @@ func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||
Skipped: false,
|
||||
SnapshotDataMoved: false,
|
||||
PreserveLocalSnapshot: true,
|
||||
StartTimestamp: &(volumeSnapshot.CreationTimestamp),
|
||||
CSISnapshotInfo: &CSISnapshotInfo{
|
||||
VSCName: *volumeSnapshot.Status.BoundVolumeSnapshotContentName,
|
||||
Size: size,
|
||||
@@ -458,10 +393,6 @@ func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||
},
|
||||
}
|
||||
|
||||
if volumeSnapshot.Status.CreationTime != nil {
|
||||
volumeInfo.StartTimestamp = volumeSnapshot.Status.CreationTime
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
v.logger.Warnf("cannot find info for PVC %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Spec.Source.PersistentVolumeClaimName)
|
||||
@@ -472,25 +403,42 @@ func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
// generateVolumeInfoFromPVB generate BackupVolumeInfo for PVB.
|
||||
func (v *BackupVolumesInformation) generateVolumeInfoFromPVB() {
|
||||
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||
// generateVolumeInfoFromPVB generate VolumeInfo for PVB.
|
||||
func (v *VolumesInformation) generateVolumeInfoFromPVB() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for _, pvb := range v.PodVolumeBackups {
|
||||
volumeInfo := &BackupVolumeInfo{
|
||||
BackupMethod: PodVolumeBackup,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
StartTimestamp: pvb.Status.StartTimestamp,
|
||||
CompletionTimestamp: pvb.Status.CompletionTimestamp,
|
||||
PVBInfo: newPodVolumeInfoFromPVB(pvb),
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: PodVolumeBackup,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
StartTimestamp: pvb.Status.StartTimestamp,
|
||||
PVBInfo: &PodVolumeBackupInfo{
|
||||
SnapshotHandle: pvb.Status.SnapshotID,
|
||||
Size: pvb.Status.Progress.TotalBytes,
|
||||
UploaderType: pvb.Spec.UploaderType,
|
||||
VolumeName: pvb.Spec.Volume,
|
||||
PodName: pvb.Spec.Pod.Name,
|
||||
PodNamespace: pvb.Spec.Pod.Namespace,
|
||||
NodeName: pvb.Spec.Node,
|
||||
},
|
||||
}
|
||||
pvcName, err := pvcByPodvolume(context.TODO(), v.crClient, pvb.Spec.Pod.Name, pvb.Spec.Pod.Namespace, pvb.Spec.Volume)
|
||||
|
||||
pod := new(corev1api.Pod)
|
||||
pvcName := ""
|
||||
err := v.crClient.Get(context.TODO(), kbclient.ObjectKey{Namespace: pvb.Spec.Pod.Namespace, Name: pvb.Spec.Pod.Name}, pod)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Warn("Fail to get PVC from PodVolumeBackup: ", pvb.Name)
|
||||
v.logger.WithError(err).Warn("Fail to get pod for PodVolumeBackup: ", pvb.Name)
|
||||
continue
|
||||
}
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == pvb.Spec.Volume && volume.PersistentVolumeClaim != nil {
|
||||
pvcName = volume.PersistentVolumeClaim.ClaimName
|
||||
}
|
||||
}
|
||||
|
||||
if pvcName != "" {
|
||||
if pvcPVInfo := v.pvMap.retrieve("", pvcName, pvb.Spec.Pod.Namespace); pvcPVInfo != nil {
|
||||
if pvcPVInfo := v.retrievePvcPvInfo("", pvcName, pod.Namespace); pvcPVInfo != nil {
|
||||
volumeInfo.PVCName = pvcPVInfo.PVCName
|
||||
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
|
||||
volumeInfo.PVName = pvcPVInfo.PV.Name
|
||||
@@ -499,25 +447,22 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromPVB() {
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
}
|
||||
} else {
|
||||
v.logger.Warnf("Cannot find info for PVC %s/%s", pvb.Spec.Pod.Namespace, pvcName)
|
||||
v.logger.Warnf("Cannot find info for PVC %s/%s", pod.Namespace, pvcName)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
v.logger.Debug("The PVB %s doesn't have a corresponding PVC", pvb.Name)
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
}
|
||||
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
// generateVolumeInfoFromDataUpload generate BackupVolumeInfo for DataUpload.
|
||||
func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||
if !features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
||||
v.logger.Debug("Skip generating BackupVolumeInfo when the CSI feature is disabled.")
|
||||
return
|
||||
}
|
||||
|
||||
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||
// generateVolumeInfoFromDataUpload generate VolumeInfo for DataUpload.
|
||||
func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
vsClassList := new(snapshotv1api.VolumeSnapshotClassList)
|
||||
if err := v.crClient.List(context.TODO(), vsClassList); err != nil {
|
||||
v.logger.WithError(err).Errorf("cannot list VolumeSnapshotClass %s", err.Error())
|
||||
@@ -559,19 +504,20 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||
}
|
||||
}
|
||||
|
||||
if pvcPVInfo := v.pvMap.retrieve("", operation.Spec.ResourceIdentifier.Name, operation.Spec.ResourceIdentifier.Namespace); pvcPVInfo != nil {
|
||||
dataMover := veleroDatamover
|
||||
if pvcPVInfo := v.retrievePvcPvInfo("", operation.Spec.ResourceIdentifier.Name, operation.Spec.ResourceIdentifier.Namespace); pvcPVInfo != nil {
|
||||
dataMover := "velero"
|
||||
if dataUpload.Spec.DataMover != "" {
|
||||
dataMover = dataUpload.Spec.DataMover
|
||||
}
|
||||
|
||||
volumeInfo := &BackupVolumeInfo{
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: CSISnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
SnapshotDataMoved: true,
|
||||
Skipped: false,
|
||||
StartTimestamp: operation.Status.Created,
|
||||
CSISnapshotInfo: &CSISnapshotInfo{
|
||||
SnapshotHandle: FieldValueIsUnknown,
|
||||
VSCName: FieldValueIsUnknown,
|
||||
@@ -580,7 +526,7 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||
},
|
||||
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
|
||||
DataMover: dataMover,
|
||||
UploaderType: kopia,
|
||||
UploaderType: "kopia",
|
||||
OperationID: operation.Spec.OperationID,
|
||||
},
|
||||
PVInfo: &PVInfo{
|
||||
@@ -589,10 +535,6 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||
},
|
||||
}
|
||||
|
||||
if dataUpload.Status.StartTimestamp != nil {
|
||||
volumeInfo.StartTimestamp = dataUpload.Status.StartTimestamp
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
v.logger.Warnf("Cannot find info for PVC %s/%s", operation.Spec.ResourceIdentifier.Namespace, operation.Spec.ResourceIdentifier.Name)
|
||||
@@ -604,21 +546,12 @@ func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
type pvcPvMap struct {
|
||||
data map[string]pvcPvInfo
|
||||
}
|
||||
|
||||
func (m *pvcPvMap) insert(pv corev1api.PersistentVolume, pvcName, pvcNamespace string) {
|
||||
m.data[pv.Name] = pvcPvInfo{
|
||||
PVCName: pvcName,
|
||||
PVCNamespace: pvcNamespace,
|
||||
PV: pv,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pvcPvMap) retrieve(pvName, pvcName, pvcNS string) *pvcPvInfo {
|
||||
// retrievePvcPvInfo gets the PvcPvInfo from the PVMap.
|
||||
// support retrieve info by PV's name, or by PVC's name
|
||||
// and namespace.
|
||||
func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS string) *pvcPvInfo {
|
||||
if pvName != "" {
|
||||
if info, ok := m.data[pvName]; ok {
|
||||
if info, ok := v.pvMap[pvName]; ok {
|
||||
return &info
|
||||
}
|
||||
return nil
|
||||
@@ -628,7 +561,7 @@ func (m *pvcPvMap) retrieve(pvName, pvcName, pvcNS string) *pvcPvInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, info := range m.data {
|
||||
for _, info := range v.pvMap {
|
||||
if pvcNS == info.PVCNamespace && pvcName == info.PVCName {
|
||||
return &info
|
||||
}
|
||||
@@ -636,241 +569,3 @@ func (m *pvcPvMap) retrieve(pvName, pvcName, pvcNS string) *pvcPvInfo {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pvcByPodvolume(ctx context.Context, crClient kbclient.Client, podName, podNamespace, volumeName string) (string, error) {
|
||||
pod := new(corev1api.Pod)
|
||||
err := crClient.Get(ctx, kbclient.ObjectKey{Namespace: podNamespace, Name: podName}, pod)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to get pod")
|
||||
}
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == volumeName && volume.PersistentVolumeClaim != nil {
|
||||
return volume.PersistentVolumeClaim.ClaimName, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// RestoreVolumeInfoTracker is used to track the volume information during restore.
|
||||
// It is used to generate the RestoreVolumeInfo array.
|
||||
type RestoreVolumeInfoTracker struct {
|
||||
*sync.Mutex
|
||||
restore *velerov1api.Restore
|
||||
log logrus.FieldLogger
|
||||
client kbclient.Client
|
||||
pvPvc *pvcPvMap
|
||||
|
||||
// map of PV name to the NativeSnapshotInfo from which the PV is restored
|
||||
pvNativeSnapshotMap map[string]*NativeSnapshotInfo
|
||||
// map of PVC object to the CSISnapshot object from which the PV is restored
|
||||
// the key is in the form of $pvc-ns/$pvc-name
|
||||
pvcCSISnapshotMap map[string]snapshotv1api.VolumeSnapshot
|
||||
datadownloadList *velerov2alpha1.DataDownloadList
|
||||
pvrs []*velerov1api.PodVolumeRestore
|
||||
}
|
||||
|
||||
// Populate data objects in the tracker, which will be used to generate the RestoreVolumeInfo array in Result()
|
||||
// The input param resourceList should be the final result of the restore.
|
||||
func (t *RestoreVolumeInfoTracker) Populate(ctx context.Context, restoredResourceList map[string][]string) {
|
||||
pvcs := RestoredPVCFromRestoredResourceList(restoredResourceList)
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
for item := range pvcs {
|
||||
n := strings.Split(item, "/")
|
||||
pvcNS, pvcName := n[0], n[1]
|
||||
log := t.log.WithField("namespace", pvcNS).WithField("name", pvcName)
|
||||
pvc := &corev1api.PersistentVolumeClaim{}
|
||||
if err := t.client.Get(ctx, kbclient.ObjectKey{Namespace: pvcNS, Name: pvcName}, pvc); err != nil {
|
||||
log.WithError(err).Error("Failed to get PVC")
|
||||
continue
|
||||
}
|
||||
// Collect the CSI VolumeSnapshot objects referenced by the restored PVCs,
|
||||
if pvc.Spec.DataSource != nil && pvc.Spec.DataSource.Kind == "VolumeSnapshot" {
|
||||
vs := &snapshotv1api.VolumeSnapshot{}
|
||||
if err := t.client.Get(ctx, kbclient.ObjectKey{Namespace: pvcNS, Name: pvc.Spec.DataSource.Name}, vs); err != nil {
|
||||
log.WithError(err).Error("Failed to get VolumeSnapshot")
|
||||
} else {
|
||||
t.pvcCSISnapshotMap[pvc.Namespace+"/"+pvcName] = *vs
|
||||
}
|
||||
}
|
||||
if pvc.Status.Phase == corev1api.ClaimBound && pvc.Spec.VolumeName != "" {
|
||||
pv := &corev1api.PersistentVolume{}
|
||||
if err := t.client.Get(ctx, kbclient.ObjectKey{Name: pvc.Spec.VolumeName}, pv); err != nil {
|
||||
log.WithError(err).Error("Failed to get PV")
|
||||
} else {
|
||||
t.pvPvc.insert(*pv, pvcName, pvcNS)
|
||||
}
|
||||
} else {
|
||||
log.Warn("PVC is not bound or has no volume name")
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := t.client.List(ctx, t.datadownloadList, &kbclient.ListOptions{
|
||||
Namespace: t.restore.Namespace,
|
||||
LabelSelector: label.NewSelectorForRestore(t.restore.Name),
|
||||
}); err != nil {
|
||||
t.log.WithError(err).Error("Failed to List DataDownloads")
|
||||
}
|
||||
}
|
||||
|
||||
// Result generates the RestoreVolumeInfo array, the data should come from the Tracker itself and it should not connect tokkkk API
|
||||
// server again.
|
||||
func (t *RestoreVolumeInfoTracker) Result() []*RestoreVolumeInfo {
|
||||
volumeInfos := make([]*RestoreVolumeInfo, 0)
|
||||
|
||||
// Generate RestoreVolumeInfo for PVRs
|
||||
for _, pvr := range t.pvrs {
|
||||
volumeInfo := &RestoreVolumeInfo{
|
||||
SnapshotDataMoved: false,
|
||||
PVRInfo: newPodVolumeInfoFromPVR(pvr),
|
||||
RestoreMethod: PodVolumeRestore,
|
||||
}
|
||||
pvcName, err := pvcByPodvolume(context.TODO(), t.client, pvr.Spec.Pod.Name, pvr.Spec.Pod.Namespace, pvr.Spec.Volume)
|
||||
if err != nil {
|
||||
t.log.WithError(err).Warn("Fail to get PVC from PodVolumeRestore: ", pvr.Name)
|
||||
continue
|
||||
}
|
||||
if pvcName != "" {
|
||||
volumeInfo.PVCName = pvcName
|
||||
volumeInfo.PVCNamespace = pvr.Spec.Pod.Namespace
|
||||
if pvcPVInfo := t.pvPvc.retrieve("", pvcName, pvr.Spec.Pod.Namespace); pvcPVInfo != nil {
|
||||
volumeInfo.PVName = pvcPVInfo.PV.Name
|
||||
}
|
||||
} else {
|
||||
// In this case, the volume is not bound to a PVC and
|
||||
// the PVR will not be able to populate into the volume, so we'll skip it
|
||||
t.log.Warnf("unable to get PVC for PodVolumeRestore %s/%s, pod: %s/%s, volume: %s",
|
||||
pvr.Namespace, pvr.Name, pvr.Spec.Pod.Namespace, pvr.Spec.Pod.Name, pvr.Spec.Volume)
|
||||
continue
|
||||
}
|
||||
volumeInfos = append(volumeInfos, volumeInfo)
|
||||
}
|
||||
|
||||
// Generate RestoreVolumeInfo for PVs restored from NativeSnapshots
|
||||
for pvName, snapshotInfo := range t.pvNativeSnapshotMap {
|
||||
volumeInfo := &RestoreVolumeInfo{
|
||||
PVName: pvName,
|
||||
SnapshotDataMoved: false,
|
||||
NativeSnapshotInfo: snapshotInfo,
|
||||
RestoreMethod: NativeSnapshot,
|
||||
}
|
||||
if pvcPVInfo := t.pvPvc.retrieve(pvName, "", ""); pvcPVInfo != nil {
|
||||
volumeInfo.PVCName = pvcPVInfo.PVCName
|
||||
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
|
||||
}
|
||||
volumeInfos = append(volumeInfos, volumeInfo)
|
||||
}
|
||||
|
||||
// Generate RestoreVolumeInfo for PVs restored from CSISnapshots
|
||||
for pvc, csiSnapshot := range t.pvcCSISnapshotMap {
|
||||
n := strings.Split(pvc, "/")
|
||||
if len(n) != 2 {
|
||||
t.log.Warnf("Invalid PVC key '%s' in the pvc-CSISnapshot map, skip populating it to volume info", pvc)
|
||||
continue
|
||||
}
|
||||
pvcNS, pvcName := n[0], n[1]
|
||||
var restoreSize int64 = 0
|
||||
if csiSnapshot.Status != nil && csiSnapshot.Status.RestoreSize != nil {
|
||||
restoreSize = csiSnapshot.Status.RestoreSize.Value()
|
||||
}
|
||||
vscName := ""
|
||||
if csiSnapshot.Spec.Source.VolumeSnapshotContentName != nil {
|
||||
vscName = *csiSnapshot.Spec.Source.VolumeSnapshotContentName
|
||||
}
|
||||
volumeInfo := &RestoreVolumeInfo{
|
||||
PVCNamespace: pvcNS,
|
||||
PVCName: pvcName,
|
||||
SnapshotDataMoved: false,
|
||||
RestoreMethod: CSISnapshot,
|
||||
CSISnapshotInfo: &CSISnapshotInfo{
|
||||
SnapshotHandle: csiSnapshot.Annotations[VolumeSnapshotHandleAnnotation],
|
||||
Size: restoreSize,
|
||||
Driver: csiSnapshot.Annotations[CSIDriverNameAnnotation],
|
||||
VSCName: vscName,
|
||||
},
|
||||
}
|
||||
if pvcPVInfo := t.pvPvc.retrieve("", pvcName, pvcNS); pvcPVInfo != nil {
|
||||
volumeInfo.PVName = pvcPVInfo.PV.Name
|
||||
}
|
||||
volumeInfos = append(volumeInfos, volumeInfo)
|
||||
}
|
||||
|
||||
for _, dd := range t.datadownloadList.Items {
|
||||
var pvcName, pvcNS, pvName string
|
||||
if pvcPVInfo := t.pvPvc.retrieve(dd.Spec.TargetVolume.PV, dd.Spec.TargetVolume.PVC, dd.Spec.TargetVolume.Namespace); pvcPVInfo != nil {
|
||||
pvcName = pvcPVInfo.PVCName
|
||||
pvcNS = pvcPVInfo.PVCNamespace
|
||||
pvName = pvcPVInfo.PV.Name
|
||||
} else {
|
||||
pvcName = dd.Spec.TargetVolume.PVC
|
||||
pvName = dd.Spec.TargetVolume.PV
|
||||
pvcNS = dd.Spec.TargetVolume.Namespace
|
||||
}
|
||||
operationID := dd.Labels[velerov1api.AsyncOperationIDLabel]
|
||||
dataMover := veleroDatamover
|
||||
if dd.Spec.DataMover != "" {
|
||||
dataMover = dd.Spec.DataMover
|
||||
}
|
||||
volumeInfo := &RestoreVolumeInfo{
|
||||
PVName: pvName,
|
||||
PVCNamespace: pvcNS,
|
||||
PVCName: pvcName,
|
||||
SnapshotDataMoved: true,
|
||||
// The method will be CSI always no CSI related CRs are created during restore, because
|
||||
// the datadownload was initiated in CSI plugin
|
||||
// For the same reason, no CSI snapshot info will be populated into volumeInfo
|
||||
RestoreMethod: CSISnapshot,
|
||||
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
|
||||
DataMover: dataMover,
|
||||
UploaderType: kopia,
|
||||
SnapshotHandle: dd.Spec.SnapshotID,
|
||||
OperationID: operationID,
|
||||
},
|
||||
}
|
||||
|
||||
volumeInfos = append(volumeInfos, volumeInfo)
|
||||
}
|
||||
|
||||
return volumeInfos
|
||||
}
|
||||
|
||||
func NewRestoreVolInfoTracker(restore *velerov1api.Restore, logger logrus.FieldLogger, client kbclient.Client) *RestoreVolumeInfoTracker {
|
||||
return &RestoreVolumeInfoTracker{
|
||||
Mutex: &sync.Mutex{},
|
||||
client: client,
|
||||
log: logger,
|
||||
restore: restore,
|
||||
pvPvc: &pvcPvMap{
|
||||
data: make(map[string]pvcPvInfo),
|
||||
},
|
||||
pvNativeSnapshotMap: make(map[string]*NativeSnapshotInfo),
|
||||
pvcCSISnapshotMap: make(map[string]snapshotv1api.VolumeSnapshot),
|
||||
datadownloadList: &velerov2alpha1.DataDownloadList{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *RestoreVolumeInfoTracker) TrackNativeSnapshot(pvName string, snapshotHandle, volumeType, volumeAZ string, iops int64) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.pvNativeSnapshotMap[pvName] = &NativeSnapshotInfo{
|
||||
SnapshotHandle: snapshotHandle,
|
||||
VolumeType: volumeType,
|
||||
VolumeAZ: volumeAZ,
|
||||
IOPS: strconv.FormatInt(iops, 10),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *RestoreVolumeInfoTracker) RenamePVForNativeSnapshot(oldName, newName string) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if snapshotInfo, ok := t.pvNativeSnapshotMap[oldName]; ok {
|
||||
t.pvNativeSnapshotMap[newName] = snapshotInfo
|
||||
delete(t.pvNativeSnapshotMap, oldName)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *RestoreVolumeInfoTracker) TrackPodVolume(pvr *velerov1api.PodVolumeRestore) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.pvrs = append(t.pvrs, pvr)
|
||||
}
|
||||
|
||||
@@ -18,12 +18,9 @@ package volume
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
@@ -34,11 +31,11 @@ import (
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/features"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/logging"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
@@ -46,7 +43,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
name string
|
||||
skippedPVName string
|
||||
pvMap map[string]pvcPvInfo
|
||||
expectedVolumeInfos []*BackupVolumeInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Cannot find info for PV",
|
||||
@@ -66,7 +63,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal Skipped PV info",
|
||||
@@ -99,7 +96,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
@@ -119,7 +116,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := BackupVolumesInformation{}
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
|
||||
if tc.skippedPVName != "" {
|
||||
@@ -130,9 +127,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
if k == v.PV.Name {
|
||||
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
|
||||
}
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
@@ -146,29 +141,29 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nativeSnapshot Snapshot
|
||||
nativeSnapshot volume.Snapshot
|
||||
pvMap map[string]pvcPvInfo
|
||||
expectedVolumeInfos []*BackupVolumeInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Native snapshot's IPOS pointer is nil",
|
||||
nativeSnapshot: Snapshot{
|
||||
Spec: SnapshotSpec{
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: nil,
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find info for the PV",
|
||||
nativeSnapshot: Snapshot{
|
||||
Spec: SnapshotSpec{
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: int64Ptr(100),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find PV info in pvMap",
|
||||
@@ -187,18 +182,18 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nativeSnapshot: Snapshot{
|
||||
Spec: SnapshotSpec{
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: int64Ptr(100),
|
||||
VolumeType: "ssd",
|
||||
VolumeAZ: "us-central1-a",
|
||||
},
|
||||
Status: SnapshotStatus{
|
||||
Status: volume.SnapshotStatus{
|
||||
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal native snapshot",
|
||||
@@ -217,18 +212,18 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
nativeSnapshot: Snapshot{
|
||||
Spec: SnapshotSpec{
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: int64Ptr(100),
|
||||
VolumeType: "ssd",
|
||||
VolumeAZ: "us-central1-a",
|
||||
},
|
||||
Status: SnapshotStatus{
|
||||
Status: volume.SnapshotStatus{
|
||||
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
@@ -253,14 +248,12 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := BackupVolumesInformation{}
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
volumesInfo.NativeSnapshots = append(volumesInfo.NativeSnapshots, &tc.nativeSnapshot)
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
if k == v.PV.Name {
|
||||
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
|
||||
}
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
@@ -281,7 +274,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
volumeSnapshotClass snapshotv1api.VolumeSnapshotClass
|
||||
pvMap map[string]pvcPvInfo
|
||||
operation *itemoperation.BackupOperation
|
||||
expectedVolumeInfos []*BackupVolumeInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "VS doesn't have VolumeSnapshotClass name",
|
||||
@@ -292,7 +285,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "VS doesn't have status",
|
||||
@@ -305,7 +298,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "VS doesn't have PVC",
|
||||
@@ -321,7 +314,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find VSC for VS",
|
||||
@@ -340,10 +333,10 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find BackupVolumeInfo for PVC",
|
||||
name: "Cannot find VolumeInfo for PVC",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
@@ -361,7 +354,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
},
|
||||
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal VolumeSnapshot case",
|
||||
@@ -379,7 +372,6 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
},
|
||||
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||
CreationTime: &now,
|
||||
RestoreSize: &resourceQuantity,
|
||||
},
|
||||
},
|
||||
@@ -413,7 +405,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
@@ -441,14 +433,12 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := BackupVolumesInformation{}
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
if k == v.PV.Name {
|
||||
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
|
||||
}
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,18 +458,17 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
now := metav1.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
pvb *velerov1api.PodVolumeBackup
|
||||
pod *corev1api.Pod
|
||||
pvMap map[string]pvcPvInfo
|
||||
expectedVolumeInfos []*BackupVolumeInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "cannot find PVB's pod, should fail",
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "PVB doesn't have a related PVC",
|
||||
@@ -500,13 +489,13 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "",
|
||||
PVCNamespace: "",
|
||||
PVName: "",
|
||||
BackupMethod: PodVolumeBackup,
|
||||
PVBInfo: &PodVolumeInfo{
|
||||
PVBInfo: &PodVolumeBackupInfo{
|
||||
PodName: "testPod",
|
||||
PodNamespace: "velero",
|
||||
},
|
||||
@@ -534,7 +523,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "PVB's volume has a PVC",
|
||||
@@ -553,7 +542,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").StartTimestamp(&now).CompletionTimestamp(&now).Result(),
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||
pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{
|
||||
Name: "test",
|
||||
VolumeMounts: []corev1api.VolumeMount{
|
||||
@@ -572,15 +561,13 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: PodVolumeBackup,
|
||||
StartTimestamp: &now,
|
||||
CompletionTimestamp: &now,
|
||||
PVBInfo: &PodVolumeInfo{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: PodVolumeBackup,
|
||||
PVBInfo: &PodVolumeBackupInfo{
|
||||
PodName: "testPod",
|
||||
PodNamespace: "velero",
|
||||
},
|
||||
@@ -595,7 +582,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := BackupVolumesInformation{}
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
volumesInfo.crClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||
|
||||
@@ -603,9 +590,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
if k == v.PV.Name {
|
||||
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
|
||||
}
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
if tc.pod != nil {
|
||||
@@ -620,19 +605,14 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
// The unstructured conversion will loose the time precision to second
|
||||
// level. To make test pass. Set the now precision at second at the
|
||||
// beginning.
|
||||
now := metav1.Now().Rfc3339Copy()
|
||||
features.Enable(velerov1api.CSIFeatureFlag)
|
||||
defer features.Disable(velerov1api.CSIFeatureFlag)
|
||||
now := metav1.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
||||
dataUpload *velerov2alpha1.DataUpload
|
||||
operation *itemoperation.BackupOperation
|
||||
pvMap map[string]pvcPvInfo
|
||||
expectedVolumeInfos []*BackupVolumeInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Operation is not for PVC",
|
||||
@@ -646,7 +626,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Operation doesn't have DataUpload PostItemOperation",
|
||||
@@ -670,7 +650,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "DataUpload cannot be found for operation",
|
||||
@@ -697,13 +677,13 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "VolumeSnapshotClass cannot be found for operation",
|
||||
dataUpload: builder.ForDataUpload("velero", "testDU").DataMover("velero").CSISnapshot(&velerov2alpha1.CSISnapshotSpec{
|
||||
VolumeSnapshot: "testVS",
|
||||
}).SnapshotID("testSnapshotHandle").StartTimestamp(&now).Result(),
|
||||
}).SnapshotID("testSnapshotHandle").Result(),
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
OperationID: "testOperation",
|
||||
@@ -742,14 +722,13 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
StartTimestamp: &now,
|
||||
CSISnapshotInfo: &CSISnapshotInfo{
|
||||
SnapshotHandle: FieldValueIsUnknown,
|
||||
VSCName: FieldValueIsUnknown,
|
||||
@@ -773,7 +752,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
dataUpload: builder.ForDataUpload("velero", "testDU").DataMover("velero").CSISnapshot(&velerov2alpha1.CSISnapshotSpec{
|
||||
VolumeSnapshot: "testVS",
|
||||
SnapshotClass: "testClass",
|
||||
}).SnapshotID("testSnapshotHandle").StartTimestamp(&now).Result(),
|
||||
}).SnapshotID("testSnapshotHandle").Result(),
|
||||
volumeSnapshotClass: builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
@@ -797,6 +776,9 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: itemoperation.OperationStatus{
|
||||
Created: &now,
|
||||
},
|
||||
},
|
||||
pvMap: map[string]pvcPvInfo{
|
||||
"testPV": {
|
||||
@@ -813,7 +795,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
@@ -844,7 +826,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := BackupVolumesInformation{}
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
|
||||
if tc.operation != nil {
|
||||
@@ -853,9 +835,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
if k == v.PV.Name {
|
||||
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
|
||||
}
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,297 +851,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumesInfo.generateVolumeInfoFromDataUpload()
|
||||
|
||||
if len(tc.expectedVolumeInfos) > 0 {
|
||||
require.Equal(t, tc.expectedVolumeInfos[0].PVInfo, volumesInfo.volumeInfos[0].PVInfo)
|
||||
require.Equal(t, tc.expectedVolumeInfos[0].SnapshotDataMovementInfo, volumesInfo.volumeInfos[0].SnapshotDataMovementInfo)
|
||||
require.Equal(t, tc.expectedVolumeInfos[0].CSISnapshotInfo, volumesInfo.volumeInfos[0].CSISnapshotInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreVolumeInfoTrackNativeSnapshot(t *testing.T) {
|
||||
fakeCilent := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
|
||||
restore := builder.ForRestore("velero", "testRestore").Result()
|
||||
tracker := NewRestoreVolInfoTracker(restore, logrus.New(), fakeCilent)
|
||||
tracker.TrackNativeSnapshot("testPV", "snap-001", "ebs", "us-west-1", 10000)
|
||||
assert.Equal(t, *tracker.pvNativeSnapshotMap["testPV"], NativeSnapshotInfo{
|
||||
SnapshotHandle: "snap-001",
|
||||
VolumeType: "ebs",
|
||||
VolumeAZ: "us-west-1",
|
||||
IOPS: "10000",
|
||||
})
|
||||
tracker.TrackNativeSnapshot("testPV", "snap-002", "ebs", "us-west-2", 15000)
|
||||
assert.Equal(t, *tracker.pvNativeSnapshotMap["testPV"], NativeSnapshotInfo{
|
||||
SnapshotHandle: "snap-002",
|
||||
VolumeType: "ebs",
|
||||
VolumeAZ: "us-west-2",
|
||||
IOPS: "15000",
|
||||
})
|
||||
tracker.RenamePVForNativeSnapshot("testPV", "newPV")
|
||||
_, ok := tracker.pvNativeSnapshotMap["testPV"]
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, *tracker.pvNativeSnapshotMap["newPV"], NativeSnapshotInfo{
|
||||
SnapshotHandle: "snap-002",
|
||||
VolumeType: "ebs",
|
||||
VolumeAZ: "us-west-2",
|
||||
IOPS: "15000",
|
||||
})
|
||||
}
|
||||
|
||||
func TestRestoreVolumeInfoResult(t *testing.T) {
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t,
|
||||
builder.ForPod("testNS", "testPod").
|
||||
Volumes(builder.ForVolume("data-volume-1").PersistentVolumeClaimSource("testPVC2").Result()).
|
||||
Result())
|
||||
testRestore := builder.ForRestore("velero", "testRestore").Result()
|
||||
tests := []struct {
|
||||
name string
|
||||
tracker *RestoreVolumeInfoTracker
|
||||
expectResultValues []RestoreVolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
tracker: &RestoreVolumeInfoTracker{
|
||||
Mutex: &sync.Mutex{},
|
||||
client: fakeClient,
|
||||
log: logrus.New(),
|
||||
restore: testRestore,
|
||||
pvPvc: &pvcPvMap{
|
||||
data: make(map[string]pvcPvInfo),
|
||||
},
|
||||
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{},
|
||||
pvcCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{},
|
||||
datadownloadList: &velerov2alpha1.DataDownloadList{},
|
||||
pvrs: []*velerov1api.PodVolumeRestore{},
|
||||
},
|
||||
expectResultValues: []RestoreVolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "native snapshot and podvolumes",
|
||||
tracker: &RestoreVolumeInfoTracker{
|
||||
Mutex: &sync.Mutex{},
|
||||
client: fakeClient,
|
||||
log: logrus.New(),
|
||||
restore: testRestore,
|
||||
pvPvc: &pvcPvMap{
|
||||
data: map[string]pvcPvInfo{
|
||||
"testPV": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV").Result(),
|
||||
},
|
||||
"testPV2": {
|
||||
PVCName: "testPVC2",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV2").Result(),
|
||||
},
|
||||
},
|
||||
},
|
||||
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{
|
||||
"testPV": {
|
||||
SnapshotHandle: "snap-001",
|
||||
VolumeType: "ebs",
|
||||
VolumeAZ: "us-west-1",
|
||||
IOPS: "10000",
|
||||
},
|
||||
},
|
||||
pvcCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{},
|
||||
datadownloadList: &velerov2alpha1.DataDownloadList{},
|
||||
pvrs: []*velerov1api.PodVolumeRestore{
|
||||
builder.ForPodVolumeRestore("velero", "testRestore-1234").
|
||||
PodNamespace("testNS").
|
||||
PodName("testPod").
|
||||
Volume("data-volume-1").
|
||||
UploaderType("kopia").
|
||||
SnapshotID("pvr-snap-001").Result(),
|
||||
},
|
||||
},
|
||||
expectResultValues: []RestoreVolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC2",
|
||||
PVCNamespace: "testNS",
|
||||
PVName: "testPV2",
|
||||
RestoreMethod: PodVolumeRestore,
|
||||
SnapshotDataMoved: false,
|
||||
PVRInfo: &PodVolumeInfo{
|
||||
SnapshotHandle: "pvr-snap-001",
|
||||
PodName: "testPod",
|
||||
PodNamespace: "testNS",
|
||||
UploaderType: "kopia",
|
||||
VolumeName: "data-volume-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PVName: "testPV",
|
||||
RestoreMethod: NativeSnapshot,
|
||||
SnapshotDataMoved: false,
|
||||
NativeSnapshotInfo: &NativeSnapshotInfo{
|
||||
SnapshotHandle: "snap-001",
|
||||
VolumeType: "ebs",
|
||||
VolumeAZ: "us-west-1",
|
||||
IOPS: "10000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CSI snapshot without datamovement and podvolumes",
|
||||
tracker: &RestoreVolumeInfoTracker{
|
||||
Mutex: &sync.Mutex{},
|
||||
client: fakeClient,
|
||||
log: logrus.New(),
|
||||
restore: testRestore,
|
||||
pvPvc: &pvcPvMap{
|
||||
data: map[string]pvcPvInfo{
|
||||
"testPV": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV").Result(),
|
||||
},
|
||||
"testPV2": {
|
||||
PVCName: "testPVC2",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV2").Result(),
|
||||
},
|
||||
},
|
||||
},
|
||||
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{},
|
||||
pvcCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{
|
||||
"testNS/testPVC": *builder.ForVolumeSnapshot("sourceNS", "testCSISnapshot").
|
||||
ObjectMeta(
|
||||
builder.WithAnnotations(VolumeSnapshotHandleAnnotation, "csi-snap-001",
|
||||
CSIDriverNameAnnotation, "test-csi-driver"),
|
||||
).SourceVolumeSnapshotContentName("test-vsc-001").
|
||||
Status().RestoreSize("1Gi").Result(),
|
||||
},
|
||||
datadownloadList: &velerov2alpha1.DataDownloadList{},
|
||||
pvrs: []*velerov1api.PodVolumeRestore{
|
||||
builder.ForPodVolumeRestore("velero", "testRestore-1234").
|
||||
PodNamespace("testNS").
|
||||
PodName("testPod").
|
||||
Volume("data-volume-1").
|
||||
UploaderType("kopia").
|
||||
SnapshotID("pvr-snap-001").Result(),
|
||||
},
|
||||
},
|
||||
expectResultValues: []RestoreVolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC2",
|
||||
PVCNamespace: "testNS",
|
||||
PVName: "testPV2",
|
||||
RestoreMethod: PodVolumeRestore,
|
||||
SnapshotDataMoved: false,
|
||||
PVRInfo: &PodVolumeInfo{
|
||||
SnapshotHandle: "pvr-snap-001",
|
||||
PodName: "testPod",
|
||||
PodNamespace: "testNS",
|
||||
UploaderType: "kopia",
|
||||
VolumeName: "data-volume-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PVName: "testPV",
|
||||
RestoreMethod: CSISnapshot,
|
||||
SnapshotDataMoved: false,
|
||||
CSISnapshotInfo: &CSISnapshotInfo{
|
||||
SnapshotHandle: "csi-snap-001",
|
||||
VSCName: "test-vsc-001",
|
||||
Size: 1073741824,
|
||||
Driver: "test-csi-driver",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CSI snapshot with datamovement",
|
||||
tracker: &RestoreVolumeInfoTracker{
|
||||
Mutex: &sync.Mutex{},
|
||||
client: fakeClient,
|
||||
log: logrus.New(),
|
||||
restore: testRestore,
|
||||
pvPvc: &pvcPvMap{
|
||||
data: map[string]pvcPvInfo{
|
||||
"testPV": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV").Result(),
|
||||
},
|
||||
"testPV2": {
|
||||
PVCName: "testPVC2",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV2").Result(),
|
||||
},
|
||||
},
|
||||
},
|
||||
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{},
|
||||
pvcCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{},
|
||||
datadownloadList: &velerov2alpha1.DataDownloadList{
|
||||
Items: []velerov2alpha1.DataDownload{
|
||||
*builder.ForDataDownload("velero", "testDataDownload-1").
|
||||
ObjectMeta(builder.WithLabels(velerov1api.AsyncOperationIDLabel, "dd-operation-001")).
|
||||
SnapshotID("dd-snap-001").
|
||||
TargetVolume(velerov2alpha1.TargetVolumeSpec{
|
||||
PVC: "testPVC",
|
||||
Namespace: "testNS",
|
||||
}).
|
||||
Result(),
|
||||
*builder.ForDataDownload("velero", "testDataDownload-2").
|
||||
ObjectMeta(builder.WithLabels(velerov1api.AsyncOperationIDLabel, "dd-operation-002")).
|
||||
SnapshotID("dd-snap-002").
|
||||
TargetVolume(velerov2alpha1.TargetVolumeSpec{
|
||||
PVC: "testPVC2",
|
||||
Namespace: "testNS",
|
||||
}).
|
||||
Result(),
|
||||
},
|
||||
},
|
||||
pvrs: []*velerov1api.PodVolumeRestore{},
|
||||
},
|
||||
expectResultValues: []RestoreVolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PVName: "testPV",
|
||||
RestoreMethod: CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
UploaderType: kopia,
|
||||
SnapshotHandle: "dd-snap-001",
|
||||
OperationID: "dd-operation-001",
|
||||
},
|
||||
},
|
||||
{
|
||||
PVCName: "testPVC2",
|
||||
PVCNamespace: "testNS",
|
||||
PVName: "testPV2",
|
||||
RestoreMethod: CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
UploaderType: kopia,
|
||||
SnapshotHandle: "dd-snap-002",
|
||||
OperationID: "dd-operation-002",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := tc.tracker.Result()
|
||||
valuesList := []RestoreVolumeInfo{}
|
||||
for _, item := range result {
|
||||
valuesList = append(valuesList, *item)
|
||||
}
|
||||
assert.Equal(t, tc.expectResultValues, valuesList)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumesInfo.volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
package volumehelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
crclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
podvolumeutil "github.com/vmware-tanzu/velero/pkg/util/podvolume"
|
||||
)
|
||||
|
||||
type VolumeHelper interface {
|
||||
ShouldPerformSnapshot(obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error)
|
||||
ShouldPerformFSBackup(volume corev1api.Volume, pod corev1api.Pod) (bool, error)
|
||||
}
|
||||
|
||||
type volumeHelperImpl struct {
|
||||
volumePolicy *resourcepolicies.Policies
|
||||
snapshotVolumes *bool
|
||||
logger logrus.FieldLogger
|
||||
client crclient.Client
|
||||
defaultVolumesToFSBackup bool
|
||||
// This parameter is used to align the fs-backup with snapshot action,
|
||||
// because PVC is already filtered by the resource filter before getting
|
||||
// to the volume policy check, but fs-backup is based on the pod resource,
|
||||
// the resource filter on PVC and PV doesn't work on this scenario.
|
||||
backupExcludePVC bool
|
||||
}
|
||||
|
||||
func NewVolumeHelperImpl(
|
||||
volumePolicy *resourcepolicies.Policies,
|
||||
snapshotVolumes *bool,
|
||||
logger logrus.FieldLogger,
|
||||
client crclient.Client,
|
||||
defaultVolumesToFSBackup bool,
|
||||
backupExcludePVC bool,
|
||||
) VolumeHelper {
|
||||
return &volumeHelperImpl{
|
||||
volumePolicy: volumePolicy,
|
||||
snapshotVolumes: snapshotVolumes,
|
||||
logger: logger,
|
||||
client: client,
|
||||
defaultVolumesToFSBackup: defaultVolumesToFSBackup,
|
||||
backupExcludePVC: backupExcludePVC,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *volumeHelperImpl) ShouldPerformSnapshot(obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error) {
|
||||
// check if volume policy exists and also check if the object(pv/pvc) fits a volume policy criteria and see if the associated action is snapshot
|
||||
// if it is not snapshot then skip the code path for snapshotting the PV/PVC
|
||||
pvc := new(corev1api.PersistentVolumeClaim)
|
||||
pv := new(corev1api.PersistentVolume)
|
||||
var err error
|
||||
|
||||
if groupResource == kuberesource.PersistentVolumeClaims {
|
||||
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &pvc); err != nil {
|
||||
v.logger.WithError(err).Error("fail to convert unstructured into PVC")
|
||||
return false, err
|
||||
}
|
||||
|
||||
pv, err = kubeutil.GetPVForPVC(pvc, v.client)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get PV for PVC %s", pvc.Namespace+"/"+pvc.Name)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.PersistentVolumes {
|
||||
if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &pv); err != nil {
|
||||
v.logger.WithError(err).Error("fail to convert unstructured into PV")
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if v.volumePolicy != nil {
|
||||
action, err := v.volumePolicy.GetMatchAction(pv)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get VolumePolicy match action for PV %s", pv.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If there is a match action, and the action type is snapshot, return true,
|
||||
// or the action type is not snapshot, then return false.
|
||||
// If there is no match action, go on to the next check.
|
||||
if action != nil {
|
||||
if action.Type == resourcepolicies.Snapshot {
|
||||
v.logger.Infof(fmt.Sprintf("performing snapshot action for pv %s", pv.Name))
|
||||
return true, nil
|
||||
} else {
|
||||
v.logger.Infof("Skip snapshot action for pv %s as the action type is %s", pv.Name, action.Type)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this PV is claimed, see if we've already taken a (pod volume backup)
|
||||
// snapshot of the contents of this PV. If so, don't take a snapshot.
|
||||
if pv.Spec.ClaimRef != nil {
|
||||
pods, err := podvolumeutil.GetPodsUsingPVC(
|
||||
pv.Spec.ClaimRef.Namespace,
|
||||
pv.Spec.ClaimRef.Name,
|
||||
v.client,
|
||||
)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get pod for PV %s", pv.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, pod := range pods {
|
||||
for _, vol := range pod.Spec.Volumes {
|
||||
if vol.PersistentVolumeClaim != nil &&
|
||||
vol.PersistentVolumeClaim.ClaimName == pv.Spec.ClaimRef.Name &&
|
||||
v.shouldPerformFSBackupLegacy(vol, pod) {
|
||||
v.logger.Infof("Skipping snapshot of pv %s because it is backed up with PodVolumeBackup.", pv.Name)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !boolptr.IsSetToFalse(v.snapshotVolumes) {
|
||||
// If the backup.Spec.SnapshotVolumes is not set, or set to true, then should take the snapshot.
|
||||
v.logger.Infof("performing snapshot action for pv %s as the snapshotVolumes is not set to false")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
v.logger.Infof(fmt.Sprintf("skipping snapshot action for pv %s possibly due to no volume policy setting or snapshotVolumes is false", pv.Name))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (v volumeHelperImpl) ShouldPerformFSBackup(volume corev1api.Volume, pod corev1api.Pod) (bool, error) {
|
||||
if !v.shouldIncludeVolumeInBackup(volume) {
|
||||
v.logger.Debugf("skip fs-backup action for pod %s's volume %s, due to not pass volume check.", pod.Namespace+"/"+pod.Name, volume.Name)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if v.volumePolicy != nil {
|
||||
pvc, err := kubeutil.GetPVCForPodVolume(&volume, &pod, v.client)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get PVC for pod %s", pod.Namespace+"/"+pod.Name)
|
||||
return false, err
|
||||
}
|
||||
pv, err := kubeutil.GetPVForPVC(pvc, v.client)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get PV for PVC %s", pvc.Namespace+"/"+pvc.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
action, err := v.volumePolicy.GetMatchAction(pv)
|
||||
if err != nil {
|
||||
v.logger.WithError(err).Errorf("fail to get VolumePolicy match action for PV %s", pv.Name)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if action != nil {
|
||||
if action.Type == resourcepolicies.FSBackup {
|
||||
v.logger.Infof("Perform fs-backup action for volume %s of pod %s due to volume policy match",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name)
|
||||
return true, nil
|
||||
} else {
|
||||
v.logger.Infof("Skip fs-backup action for volume %s for pod %s because the action type is %s",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name, action.Type)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.shouldPerformFSBackupLegacy(volume, pod) {
|
||||
v.logger.Infof("Perform fs-backup action for volume %s of pod %s due to opt-in/out way",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name)
|
||||
return true, nil
|
||||
} else {
|
||||
v.logger.Infof("Skip fs-backup action for volume %s of pod %s due to opt-in/out way",
|
||||
volume.Name, pod.Namespace+"/"+pod.Name)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v volumeHelperImpl) shouldPerformFSBackupLegacy(
|
||||
volume corev1api.Volume,
|
||||
pod corev1api.Pod,
|
||||
) bool {
|
||||
// Check volume in opt-in way
|
||||
if !v.defaultVolumesToFSBackup {
|
||||
optInVolumeNames := podvolumeutil.GetVolumesToBackup(&pod)
|
||||
for _, volumeName := range optInVolumeNames {
|
||||
if volume.Name == volumeName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
} else {
|
||||
// Check volume in opt-out way
|
||||
optOutVolumeNames := podvolumeutil.GetVolumesToExclude(&pod)
|
||||
for _, volumeName := range optOutVolumeNames {
|
||||
if volume.Name == volumeName {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (v *volumeHelperImpl) shouldIncludeVolumeInBackup(vol corev1api.Volume) bool {
|
||||
includeVolumeInBackup := true
|
||||
// cannot backup hostpath volumes as they are not mounted into /var/lib/kubelet/pods
|
||||
// and therefore not accessible to the node agent daemon set.
|
||||
if vol.HostPath != nil {
|
||||
includeVolumeInBackup = false
|
||||
}
|
||||
// don't backup volumes mounting secrets. Secrets will be backed up separately.
|
||||
if vol.Secret != nil {
|
||||
includeVolumeInBackup = false
|
||||
}
|
||||
// don't backup volumes mounting ConfigMaps. ConfigMaps will be backed up separately.
|
||||
if vol.ConfigMap != nil {
|
||||
includeVolumeInBackup = false
|
||||
}
|
||||
// don't backup volumes mounted as projected volumes, all data in those come from kube state.
|
||||
if vol.Projected != nil {
|
||||
includeVolumeInBackup = false
|
||||
}
|
||||
// don't backup DownwardAPI volumes, all data in those come from kube state.
|
||||
if vol.DownwardAPI != nil {
|
||||
includeVolumeInBackup = false
|
||||
}
|
||||
if vol.PersistentVolumeClaim != nil && v.backupExcludePVC {
|
||||
includeVolumeInBackup = false
|
||||
}
|
||||
// don't include volumes that mount the default service account token.
|
||||
if strings.HasPrefix(vol.Name, "default-token") {
|
||||
includeVolumeInBackup = false
|
||||
}
|
||||
return includeVolumeInBackup
|
||||
}
|
||||
@@ -1,682 +0,0 @@
|
||||
/*
|
||||
Copyright the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package volumehelper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
func TestVolumeHelperImpl_ShouldPerformSnapshot(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
inputObj runtime.Object
|
||||
groupResource schema.GroupResource
|
||||
pod *corev1.Pod
|
||||
resourcePolicies *resourcepolicies.ResourcePolicies
|
||||
snapshotVolumesFlag *bool
|
||||
defaultVolumesToFSBackup bool
|
||||
shouldSnapshot bool
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "VolumePolicy match, returns true and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp2-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
shouldSnapshot: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy match, snapshotVolumes is false, return true and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp2-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(false),
|
||||
shouldSnapshot: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy match but action is unexpected, return false and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp2-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.FSBackup,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
shouldSnapshot: false,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy not match, not selected by fs-backup as opt-out way, snapshotVolumes is true, returns true and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp3-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
pod: builder.ForPod("ns", "pod-1").Result(),
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
shouldSnapshot: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy not match, selected by fs-backup as opt-out way, snapshotVolumes is true, returns false and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp3-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
pod: builder.ForPod("ns", "pod-1").Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
defaultVolumesToFSBackup: true,
|
||||
shouldSnapshot: false,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy not match, selected by fs-backup as opt-out way, snapshotVolumes is true, returns false and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp3-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
ObjectMeta(builder.WithAnnotations(velerov1api.VolumesToExcludeAnnotation, "volume")).
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
defaultVolumesToFSBackup: true,
|
||||
shouldSnapshot: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy not match, not selected by fs-backup as opt-in way, snapshotVolumes is true, returns false and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp3-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
ObjectMeta(builder.WithAnnotations(velerov1api.VolumesToBackupAnnotation, "volume")).
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
defaultVolumesToFSBackup: false,
|
||||
shouldSnapshot: false,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy not match, not selected by fs-backup as opt-in way, snapshotVolumes is true, returns true and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp3-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
defaultVolumesToFSBackup: false,
|
||||
shouldSnapshot: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "No VolumePolicy, not selected by fs-backup, snapshotVolumes is true, returns true and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp3-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
resourcePolicies: nil,
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
shouldSnapshot: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "No VolumePolicy, not selected by fs-backup, snapshotVolumes is false, returns false and no error",
|
||||
inputObj: builder.ForPersistentVolume("example-pv").StorageClass("gp3-csi").ClaimRef("ns", "pvc-1").Result(),
|
||||
groupResource: kuberesource.PersistentVolumes,
|
||||
resourcePolicies: nil,
|
||||
snapshotVolumesFlag: ptr.To(false),
|
||||
shouldSnapshot: false,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "PVC not having PV, return false and error case PV not found",
|
||||
inputObj: builder.ForPersistentVolumeClaim("default", "example-pvc").StorageClass("gp2-csi").Result(),
|
||||
groupResource: kuberesource.PersistentVolumeClaims,
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
},
|
||||
snapshotVolumesFlag: ptr.To(true),
|
||||
shouldSnapshot: false,
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
objs := []runtime.Object{
|
||||
&corev1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns",
|
||||
Name: "pvc-1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, objs...)
|
||||
if tc.pod != nil {
|
||||
fakeClient.Create(context.Background(), tc.pod)
|
||||
}
|
||||
|
||||
var p *resourcepolicies.Policies = nil
|
||||
if tc.resourcePolicies != nil {
|
||||
p = &resourcepolicies.Policies{}
|
||||
err := p.BuildPolicy(tc.resourcePolicies)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build policy with error %v", err)
|
||||
}
|
||||
}
|
||||
vh := NewVolumeHelperImpl(
|
||||
p,
|
||||
tc.snapshotVolumesFlag,
|
||||
logrus.StandardLogger(),
|
||||
fakeClient,
|
||||
tc.defaultVolumesToFSBackup,
|
||||
false,
|
||||
)
|
||||
|
||||
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.inputObj)
|
||||
require.NoError(t, err)
|
||||
|
||||
actualShouldSnapshot, actualError := vh.ShouldPerformSnapshot(&unstructured.Unstructured{Object: obj}, tc.groupResource)
|
||||
if tc.expectedErr {
|
||||
require.NotNil(t, actualError, "Want error; Got nil error")
|
||||
return
|
||||
}
|
||||
|
||||
require.Equalf(t, tc.shouldSnapshot, actualShouldSnapshot, "Want shouldSnapshot as %t; Got shouldSnapshot as %t", tc.shouldSnapshot, actualShouldSnapshot)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeHelperImpl_ShouldIncludeVolumeInBackup(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
vol corev1.Volume
|
||||
backupExcludePVC bool
|
||||
shouldInclude bool
|
||||
}{
|
||||
{
|
||||
name: "volume has host path so do not include",
|
||||
vol: corev1.Volume{
|
||||
Name: "sample-volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: "some-path",
|
||||
},
|
||||
},
|
||||
},
|
||||
backupExcludePVC: false,
|
||||
shouldInclude: false,
|
||||
},
|
||||
{
|
||||
name: "volume has secret mounted so do not include",
|
||||
vol: corev1.Volume{
|
||||
Name: "sample-volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "sample-secret",
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: "username",
|
||||
Path: "my-username",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
backupExcludePVC: false,
|
||||
shouldInclude: false,
|
||||
},
|
||||
{
|
||||
name: "volume has configmap so do not include",
|
||||
vol: corev1.Volume{
|
||||
Name: "sample-volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "sample-cm",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
backupExcludePVC: false,
|
||||
shouldInclude: false,
|
||||
},
|
||||
{
|
||||
name: "volume is mounted as project volume so do not include",
|
||||
vol: corev1.Volume{
|
||||
Name: "sample-volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{},
|
||||
},
|
||||
},
|
||||
},
|
||||
backupExcludePVC: false,
|
||||
shouldInclude: false,
|
||||
},
|
||||
{
|
||||
name: "volume has downwardAPI so do not include",
|
||||
vol: corev1.Volume{
|
||||
Name: "sample-volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
DownwardAPI: &corev1.DownwardAPIVolumeSource{
|
||||
Items: []corev1.DownwardAPIVolumeFile{
|
||||
{
|
||||
Path: "labels",
|
||||
FieldRef: &corev1.ObjectFieldSelector{
|
||||
FieldPath: "metadata.labels",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
backupExcludePVC: false,
|
||||
shouldInclude: false,
|
||||
},
|
||||
{
|
||||
name: "volume has pvc and backupExcludePVC is true so do not include",
|
||||
vol: corev1.Volume{
|
||||
Name: "sample-volume",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "sample-pvc",
|
||||
},
|
||||
},
|
||||
},
|
||||
backupExcludePVC: true,
|
||||
shouldInclude: false,
|
||||
},
|
||||
{
|
||||
name: "volume name has prefix default-token so do not include",
|
||||
vol: corev1.Volume{
|
||||
Name: "default-token-vol-name",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "sample-pvc",
|
||||
},
|
||||
},
|
||||
},
|
||||
backupExcludePVC: false,
|
||||
shouldInclude: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
resourcePolicies := resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
policies := resourcePolicies
|
||||
p := &resourcepolicies.Policies{}
|
||||
err := p.BuildPolicy(&policies)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build policy with error %v", err)
|
||||
}
|
||||
vh := &volumeHelperImpl{
|
||||
volumePolicy: p,
|
||||
snapshotVolumes: ptr.To(true),
|
||||
logger: velerotest.NewLogger(),
|
||||
backupExcludePVC: tc.backupExcludePVC,
|
||||
}
|
||||
actualShouldInclude := vh.shouldIncludeVolumeInBackup(tc.vol)
|
||||
assert.Equalf(t, actualShouldInclude, tc.shouldInclude, "Want shouldInclude as %v; Got actualShouldInclude as %v", tc.shouldInclude, actualShouldInclude)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeHelperImpl_ShouldPerformFSBackup(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
pod *corev1.Pod
|
||||
resources []runtime.Object
|
||||
resourcePolicies *resourcepolicies.ResourcePolicies
|
||||
snapshotVolumesFlag *bool
|
||||
defaultVolumesToFSBackup bool
|
||||
shouldFSBackup bool
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "HostPath volume should be skipped.",
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: "/mnt/test",
|
||||
},
|
||||
},
|
||||
}).Result(),
|
||||
shouldFSBackup: false,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy match, return true and no error",
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
}).Result(),
|
||||
resources: []runtime.Object{
|
||||
builder.ForPersistentVolumeClaim("ns", "pvc-1").
|
||||
VolumeName("pv-1").
|
||||
StorageClass("gp2-csi").Phase(corev1.ClaimBound).Result(),
|
||||
builder.ForPersistentVolume("pv-1").StorageClass("gp2-csi").Result(),
|
||||
},
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.FSBackup,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFSBackup: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy match, action type is not fs-backup, return false and no error",
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
}).Result(),
|
||||
resources: []runtime.Object{
|
||||
builder.ForPersistentVolumeClaim("ns", "pvc-1").
|
||||
VolumeName("pv-1").
|
||||
StorageClass("gp2-csi").Phase(corev1.ClaimBound).Result(),
|
||||
builder.ForPersistentVolume("pv-1").StorageClass("gp2-csi").Result(),
|
||||
},
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp2-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.Snapshot,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFSBackup: false,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "VolumePolicy not match, selected by opt-in way, return true and no error",
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
ObjectMeta(builder.WithAnnotations(velerov1api.VolumesToBackupAnnotation, "pvc-1")).
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "pvc-1",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
}).Result(),
|
||||
resources: []runtime.Object{
|
||||
builder.ForPersistentVolumeClaim("ns", "pvc-1").
|
||||
VolumeName("pv-1").
|
||||
StorageClass("gp2-csi").Phase(corev1.ClaimBound).Result(),
|
||||
builder.ForPersistentVolume("pv-1").StorageClass("gp2-csi").Result(),
|
||||
},
|
||||
resourcePolicies: &resourcepolicies.ResourcePolicies{
|
||||
Version: "v1",
|
||||
VolumePolicies: []resourcepolicies.VolumePolicy{
|
||||
{
|
||||
Conditions: map[string]interface{}{
|
||||
"storageClass": []string{"gp3-csi"},
|
||||
},
|
||||
Action: resourcepolicies.Action{
|
||||
Type: resourcepolicies.FSBackup,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFSBackup: true,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "No VolumePolicy, not selected by opt-out way, return false and no error",
|
||||
pod: builder.ForPod("ns", "pod-1").
|
||||
ObjectMeta(builder.WithAnnotations(velerov1api.VolumesToExcludeAnnotation, "pvc-1")).
|
||||
Volumes(
|
||||
&corev1.Volume{
|
||||
Name: "pvc-1",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "pvc-1",
|
||||
},
|
||||
},
|
||||
}).Result(),
|
||||
resources: []runtime.Object{
|
||||
builder.ForPersistentVolumeClaim("ns", "pvc-1").
|
||||
VolumeName("pv-1").
|
||||
StorageClass("gp2-csi").Phase(corev1.ClaimBound).Result(),
|
||||
builder.ForPersistentVolume("pv-1").StorageClass("gp2-csi").Result(),
|
||||
},
|
||||
defaultVolumesToFSBackup: true,
|
||||
shouldFSBackup: false,
|
||||
expectedErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, tc.resources...)
|
||||
if tc.pod != nil {
|
||||
fakeClient.Create(context.Background(), tc.pod)
|
||||
}
|
||||
|
||||
var p *resourcepolicies.Policies = nil
|
||||
if tc.resourcePolicies != nil {
|
||||
p = &resourcepolicies.Policies{}
|
||||
err := p.BuildPolicy(tc.resourcePolicies)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build policy with error %v", err)
|
||||
}
|
||||
}
|
||||
vh := NewVolumeHelperImpl(
|
||||
p,
|
||||
tc.snapshotVolumesFlag,
|
||||
logrus.StandardLogger(),
|
||||
fakeClient,
|
||||
tc.defaultVolumesToFSBackup,
|
||||
false,
|
||||
)
|
||||
|
||||
actualShouldFSBackup, actualError := vh.ShouldPerformFSBackup(tc.pod.Spec.Volumes[0], *tc.pod)
|
||||
if tc.expectedErr {
|
||||
require.NotNil(t, actualError, "Want error; Got nil error")
|
||||
return
|
||||
}
|
||||
|
||||
require.Equalf(t, tc.shouldFSBackup, actualShouldFSBackup, "Want shouldFSBackup as %t; Got shouldFSBackup as %t", tc.shouldFSBackup, actualShouldFSBackup)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ type BackupSpec struct {
|
||||
CSISnapshotTimeout metav1.Duration `json:"csiSnapshotTimeout,omitempty"`
|
||||
|
||||
// ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations
|
||||
// The default value is 4 hour.
|
||||
// The default value is 1 hour.
|
||||
// +optional
|
||||
ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"`
|
||||
// ResourcePolicy specifies the referenced resource policies that backup should follow
|
||||
|
||||
@@ -25,7 +25,7 @@ type DownloadRequestSpec struct {
|
||||
}
|
||||
|
||||
// DownloadTargetKind represents what type of file to download.
|
||||
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;BackupResults;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents;BackupVolumeInfos;RestoreVolumeInfo
|
||||
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;BackupResults;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents;BackupVolumeInfos
|
||||
type DownloadTargetKind string
|
||||
|
||||
const (
|
||||
@@ -42,7 +42,6 @@ const (
|
||||
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
|
||||
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
|
||||
DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos"
|
||||
DownloadTargetKindRestoreVolumeInfo DownloadTargetKind = "RestoreVolumeInfo"
|
||||
)
|
||||
|
||||
// DownloadTarget is the specification for what kind of file to download, and the name of the
|
||||
|
||||
@@ -97,10 +97,6 @@ const (
|
||||
// VolumesToExcludeAnnotation is the annotation on a pod whose mounted volumes
|
||||
// should be excluded from pod volume backup.
|
||||
VolumesToExcludeAnnotation = "backup.velero.io/backup-volumes-excludes"
|
||||
|
||||
// ExcludeFromBackupLabel is the label to exclude k8s resource from backup,
|
||||
// even if the resource contains a matching selector label.
|
||||
ExcludeFromBackupLabel = "velero.io/exclude-from-backup"
|
||||
)
|
||||
|
||||
type AsyncOperationIDPrefix string
|
||||
@@ -115,37 +111,3 @@ type VeleroResourceUsage string
|
||||
const (
|
||||
VeleroResourceUsageDataUploadResult VeleroResourceUsage = "DataUpload"
|
||||
)
|
||||
|
||||
// CSI related plugin actions' constant variable
|
||||
const (
|
||||
VolumeSnapshotLabel = "velero.io/volume-snapshot-name"
|
||||
VolumeSnapshotHandleAnnotation = "velero.io/csi-volumesnapshot-handle"
|
||||
VolumeSnapshotRestoreSize = "velero.io/csi-volumesnapshot-restore-size"
|
||||
DriverNameAnnotation = "velero.io/csi-driver-name"
|
||||
VSCDeletionPolicyAnnotation = "velero.io/csi-vsc-deletion-policy"
|
||||
VolumeSnapshotClassSelectorLabel = "velero.io/csi-volumesnapshot-class"
|
||||
VolumeSnapshotClassDriverBackupAnnotationPrefix = "velero.io/csi-volumesnapshot-class"
|
||||
VolumeSnapshotClassDriverPVCAnnotation = "velero.io/csi-volumesnapshot-class"
|
||||
|
||||
// There is no release w/ these constants exported. Using the strings for now.
|
||||
// CSI Annotation volumesnapshotclass
|
||||
// https://github.com/kubernetes-csi/external-snapshotter/blob/master/pkg/utils/util.go#L59-L60
|
||||
PrefixedListSecretNameAnnotation = "csi.storage.k8s.io/snapshotter-list-secret-name" // #nosec G101
|
||||
PrefixedListSecretNamespaceAnnotation = "csi.storage.k8s.io/snapshotter-list-secret-namespace" // #nosec G101
|
||||
|
||||
// CSI Annotation volumesnapshotcontents
|
||||
PrefixedSecretNameAnnotation = "csi.storage.k8s.io/snapshotter-secret-name" // #nosec G101
|
||||
PrefixedSecretNamespaceAnnotation = "csi.storage.k8s.io/snapshotter-secret-namespace" // #nosec G101
|
||||
|
||||
// Velero checks this annotation to determine whether to skip resource excluding check.
|
||||
MustIncludeAdditionalItemAnnotation = "backup.velero.io/must-include-additional-items"
|
||||
// SkippedNoCSIPVAnnotation - Velero checks this annotation on processed PVC to
|
||||
// find out if the snapshot was skipped b/c the PV is not provisioned via CSI
|
||||
SkippedNoCSIPVAnnotation = "backup.velero.io/skipped-no-csi-pv"
|
||||
|
||||
// DynamicPVRestoreLabel is the label key for dynamic PV restore
|
||||
DynamicPVRestoreLabel = "velero.io/dynamic-pv-restore"
|
||||
|
||||
// DataUploadNameAnnotation is the label key for the DataUpload name
|
||||
DataUploadNameAnnotation = "velero.io/data-upload-name"
|
||||
)
|
||||
|
||||
@@ -61,8 +61,8 @@ func CustomResources() map[string]typeInfo {
|
||||
}
|
||||
|
||||
// CustomResourceKinds returns a list of all custom resources kinds within the Velero
|
||||
func CustomResourceKinds() sets.Set[string] {
|
||||
kinds := sets.New[string]()
|
||||
func CustomResourceKinds() sets.String {
|
||||
kinds := sets.NewString()
|
||||
|
||||
resources := CustomResources()
|
||||
for kind := range resources {
|
||||
|
||||
@@ -26,8 +26,7 @@ import (
|
||||
type RestoreSpec struct {
|
||||
// BackupName is the unique name of the Velero backup to restore
|
||||
// from.
|
||||
// +optional
|
||||
BackupName string `json:"backupName,omitempty"`
|
||||
BackupName string `json:"backupName"`
|
||||
|
||||
// ScheduleName is the unique name of the Velero schedule to restore
|
||||
// from. If specified, and BackupName is empty, Velero will restore
|
||||
@@ -116,7 +115,7 @@ type RestoreSpec struct {
|
||||
ExistingResourcePolicy PolicyType `json:"existingResourcePolicy,omitempty"`
|
||||
|
||||
// ItemOperationTimeout specifies the time used to wait for RestoreItemAction operations
|
||||
// The default value is 4 hour.
|
||||
// The default value is 1 hour.
|
||||
// +optional
|
||||
ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"`
|
||||
|
||||
@@ -137,9 +136,6 @@ type UploaderConfigForRestore struct {
|
||||
// +optional
|
||||
// +nullable
|
||||
WriteSparseFiles *bool `json:"writeSparseFiles,omitempty"`
|
||||
// ParallelFilesDownload is the concurrency number setting for restore.
|
||||
// +optional
|
||||
ParallelFilesDownload int `json:"parallelFilesDownload,omitempty"`
|
||||
}
|
||||
|
||||
// RestoreHooks contains custom behaviors that should be executed during or post restore.
|
||||
@@ -253,7 +249,7 @@ type InitRestoreHook struct {
|
||||
|
||||
// RestorePhase is a string representation of the lifecycle phase
|
||||
// of a Velero restore
|
||||
// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Completed;PartiallyFailed;Failed;Finalizing;FinalizingPartiallyFailed
|
||||
// +kubebuilder:validation:Enum=New;FailedValidation;InProgress;WaitingForPluginOperations;WaitingForPluginOperationsPartiallyFailed;Completed;PartiallyFailed;Failed
|
||||
type RestorePhase string
|
||||
|
||||
const (
|
||||
@@ -281,19 +277,6 @@ const (
|
||||
// ongoing. The restore is not complete yet.
|
||||
RestorePhaseWaitingForPluginOperationsPartiallyFailed RestorePhase = "WaitingForPluginOperationsPartiallyFailed"
|
||||
|
||||
// RestorePhaseFinalizing means the restore of
|
||||
// Kubernetes resources and other async plugin operations were successful and
|
||||
// other plugin operations are now complete, but the restore is awaiting
|
||||
// the completion of wrap-up tasks before the restore process enters terminal phase.
|
||||
RestorePhaseFinalizing RestorePhase = "Finalizing"
|
||||
|
||||
// RestorePhaseFinalizingPartiallyFailed means the restore of
|
||||
// Kubernetes resources and other async plugin operations were successful and
|
||||
// other plugin operations are now complete, but one or more errors
|
||||
// occurred during restore or async operation processing. The restore is awaiting
|
||||
// the completion of wrap-up tasks before the restore process enters terminal phase.
|
||||
RestorePhaseFinalizingPartiallyFailed RestorePhase = "FinalizingPartiallyFailed"
|
||||
|
||||
// RestorePhaseCompleted means the restore has run successfully
|
||||
// without errors.
|
||||
RestorePhaseCompleted RestorePhase = "Completed"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ func CustomResources() map[string]typeInfo {
|
||||
}
|
||||
|
||||
// CustomResourceKinds returns a list of all custom resources kinds within the Velero
|
||||
func CustomResourceKinds() sets.Set[string] {
|
||||
kinds := sets.New[string]()
|
||||
func CustomResourceKinds() sets.String {
|
||||
kinds := sets.NewString()
|
||||
|
||||
resources := CustomResources()
|
||||
for kind := range resources {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user