mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-01-30 08:39:29 +00:00
Merge pull request #88 from ceph/devel
Sync rhs/ceph-csi:devel with ceph/ceph-csi:devel
This commit is contained in:
commit
79aedad86b
@ -41,3 +41,4 @@ rules:
|
|||||||
- rebase
|
- rebase
|
||||||
- revert
|
- revert
|
||||||
- util
|
- util
|
||||||
|
- nfs
|
||||||
|
2
.github/workflows/retest.yml
vendored
2
.github/workflows/retest.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
# path to the retest action
|
# path to the retest action
|
||||||
- uses: ceph/ceph-csi/actions/retest@devel
|
- uses: ceph/ceph-csi/actions/retest@devel
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.CEPH_CSI_BOT_TOKEN }}
|
||||||
required-label: "ci/retry/e2e"
|
required-label: "ci/retry/e2e"
|
||||||
max-retry: "5"
|
max-retry: "5"
|
||||||
required-approve-count: "2"
|
required-approve-count: "2"
|
||||||
|
@ -309,6 +309,13 @@ pull_request_rules:
|
|||||||
label:
|
label:
|
||||||
add:
|
add:
|
||||||
- component/cephfs
|
- component/cephfs
|
||||||
|
- name: title contains NFS
|
||||||
|
conditions:
|
||||||
|
- "title~=nfs: "
|
||||||
|
actions:
|
||||||
|
label:
|
||||||
|
add:
|
||||||
|
- component/nfs
|
||||||
- name: title contains RBD
|
- name: title contains RBD
|
||||||
conditions:
|
conditions:
|
||||||
- "title~=rbd: "
|
- "title~=rbd: "
|
||||||
|
21
Makefile
21
Makefile
@ -46,22 +46,19 @@ endif
|
|||||||
|
|
||||||
GO_PROJECT=github.com/ceph/ceph-csi
|
GO_PROJECT=github.com/ceph/ceph-csi
|
||||||
|
|
||||||
|
CEPH_VERSION ?= $(shell . $(CURDIR)/build.env ; echo $${CEPH_VERSION})
|
||||||
|
# TODO: ceph_preview tag may be removed with go-ceph 0.16.0
|
||||||
|
GO_TAGS_LIST ?= $(CEPH_VERSION) ceph_preview
|
||||||
|
|
||||||
# go build flags
|
# go build flags
|
||||||
LDFLAGS ?=
|
LDFLAGS ?=
|
||||||
LDFLAGS += -X $(GO_PROJECT)/internal/util.GitCommit=$(GIT_COMMIT)
|
LDFLAGS += -X $(GO_PROJECT)/internal/util.GitCommit=$(GIT_COMMIT)
|
||||||
# CSI_IMAGE_VERSION will be considered as the driver version
|
# CSI_IMAGE_VERSION will be considered as the driver version
|
||||||
LDFLAGS += -X $(GO_PROJECT)/internal/util.DriverVersion=$(CSI_IMAGE_VERSION)
|
LDFLAGS += -X $(GO_PROJECT)/internal/util.DriverVersion=$(CSI_IMAGE_VERSION)
|
||||||
|
GO_TAGS ?= -tags=$(shell echo $(GO_TAGS_LIST) | tr ' ' ',')
|
||||||
|
|
||||||
BASE_IMAGE ?= $(shell . $(CURDIR)/build.env ; echo $${BASE_IMAGE})
|
BASE_IMAGE ?= $(shell . $(CURDIR)/build.env ; echo $${BASE_IMAGE})
|
||||||
|
|
||||||
ifndef CEPH_VERSION
|
|
||||||
CEPH_VERSION = $(shell . $(CURDIR)/build.env ; echo $${CEPH_VERSION})
|
|
||||||
endif
|
|
||||||
ifdef CEPH_VERSION
|
|
||||||
# pass -tags to go commands (for go-ceph build constraints)
|
|
||||||
GO_TAGS = -tags=$(CEPH_VERSION)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# passing TARGET=static-check on the 'make containerized-test' or 'make
|
# passing TARGET=static-check on the 'make containerized-test' or 'make
|
||||||
# containerized-build' commandline will run the selected target instead of
|
# containerized-build' commandline will run the selected target instead of
|
||||||
# 'make test' in the container. Obviously other targets can be passed as well,
|
# 'make test' in the container. Obviously other targets can be passed as well,
|
||||||
@ -109,8 +106,12 @@ mod-check: check-env
|
|||||||
@echo 'running: go mod verify'
|
@echo 'running: go mod verify'
|
||||||
@go mod verify && [ "$(shell sha512sum go.mod)" = "`sha512sum go.mod`" ] || ( echo "ERROR: go.mod was modified by 'go mod verify'" && false )
|
@go mod verify && [ "$(shell sha512sum go.mod)" = "`sha512sum go.mod`" ] || ( echo "ERROR: go.mod was modified by 'go mod verify'" && false )
|
||||||
|
|
||||||
scripts/golangci.yml: build.env scripts/golangci.yml.in
|
scripts/golangci.yml: scripts/golangci.yml.in
|
||||||
sed "s/@@CEPH_VERSION@@/$(CEPH_VERSION)/g" < scripts/golangci.yml.in > scripts/golangci.yml
|
rm -f scripts/golangci.yml.buildtags.in
|
||||||
|
for tag in $(GO_TAGS_LIST); do \
|
||||||
|
echo " - $$tag" >> scripts/golangci.yml.buildtags.in ; \
|
||||||
|
done
|
||||||
|
sed "/@@BUILD_TAGS@@/r scripts/golangci.yml.buildtags.in" scripts/golangci.yml.in | sed '/@@BUILD_TAGS@@/d' > scripts/golangci.yml
|
||||||
|
|
||||||
go-lint: scripts/golangci.yml
|
go-lint: scripts/golangci.yml
|
||||||
./scripts/lint-go.sh
|
./scripts/lint-go.sh
|
||||||
|
@ -105,6 +105,10 @@ for its support details.
|
|||||||
| | Provision volume from another volume | GA | >= v3.1.0 | >= v1.0.0 | Octopus (>=v15.2.4) | >= v1.16.0 |
|
| | Provision volume from another volume | GA | >= v3.1.0 | >= v1.0.0 | Octopus (>=v15.2.4) | >= v1.16.0 |
|
||||||
| | Expand volume | Beta | >= v2.0.0 | >= v1.1.0 | Nautilus (>=v14.2.2) | >= v1.15.0 |
|
| | Expand volume | Beta | >= v2.0.0 | >= v1.1.0 | Nautilus (>=v14.2.2) | >= v1.15.0 |
|
||||||
| | Volume/PV Metrics of File Mode Volume | GA | >= v1.2.0 | >= v1.1.0 | Nautilus (>=v14.2.2) | >= v1.15.0 |
|
| | Volume/PV Metrics of File Mode Volume | GA | >= v1.2.0 | >= v1.1.0 | Nautilus (>=v14.2.2) | >= v1.15.0 |
|
||||||
|
| NFS | Dynamically provision, de-provision File mode RWO volume | Alpha | >= v3.6.0 | >= v1.0.0 | Pacific (>=16.2.0) | >= v1.14.0 |
|
||||||
|
| | Dynamically provision, de-provision File mode RWX volume | Alpha | >= v3.6.0 | >= v1.0.0 | Pacific (>=16.2.0) | >= v1.14.0 |
|
||||||
|
| | Dynamically provision, de-provision File mode ROX volume | Alpha | >= v3.6.0 | >= v1.0.0 | Pacific (>=16.2.0) | >= v1.14.0 |
|
||||||
|
| | Dynamically provision, de-provision File mode RWOP volume | Alpha | >= v3.6.0 | >= v1.5.0 | Pacific (>=16.2.0) | >= v1.22.0 |
|
||||||
|
|
||||||
`NOTE`: The `Alpha` status reflects possible non-backward
|
`NOTE`: The `Alpha` status reflects possible non-backward
|
||||||
compatible changes in the future, and is thus not recommended
|
compatible changes in the future, and is thus not recommended
|
||||||
|
@ -143,7 +143,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
statusList := filterStatusList(rs)
|
statusList := filterStatusList(rs)
|
||||||
|
failedTestFound := false
|
||||||
for _, r := range statusList {
|
for _, r := range statusList {
|
||||||
log.Printf("found context %s with status %s\n", r.GetContext(), r.GetState())
|
log.Printf("found context %s with status %s\n", r.GetContext(), r.GetState())
|
||||||
if contains([]string{"failed", "failure"}, r.GetState()) {
|
if contains([]string{"failed", "failure"}, r.GetState()) {
|
||||||
@ -176,6 +176,20 @@ func main() {
|
|||||||
log.Printf("failed to create comment %v\n", err)
|
log.Printf("failed to create comment %v\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
failedTestFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if failedTestFound {
|
||||||
|
// comment `@Mergifyio refresh` so mergifyio adds the pr back into the queue.
|
||||||
|
msg := "@Mergifyio refresh"
|
||||||
|
comment := &github.IssueComment{
|
||||||
|
Body: github.String(msg),
|
||||||
|
}
|
||||||
|
_, _, err = c.client.Issues.CreateComment(context.TODO(), c.owner, c.repo, prNumber, comment)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to create comment %q: %v\n", msg, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
74
api/deploy/kubernetes/nfs/csidriver.go
Normal file
74
api/deploy/kubernetes/nfs/csidriver.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 nfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
storagev1 "k8s.io/api/storage/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed csidriver.yaml
|
||||||
|
var csiDriver string
|
||||||
|
|
||||||
|
type CSIDriverValues struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var CSIDriverDefaults = CSIDriverValues{
|
||||||
|
Name: "nfs.csi.ceph.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCSIDriver takes a driver name from the CSIDriverValues struct and
|
||||||
|
// replaces the value in the template. A CSIDriver object is returned which can
|
||||||
|
// be created in the Kubernetes cluster.
|
||||||
|
func NewCSIDriver(values CSIDriverValues) (*storagev1.CSIDriver, error) {
|
||||||
|
data, err := NewCSIDriverYAML(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := &storagev1.CSIDriver{}
|
||||||
|
err = yaml.Unmarshal([]byte(data), driver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed convert YAML to %T: %w", driver, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCSIDriverYAML takes a driver name from the CSIDriverValues struct and
|
||||||
|
// replaces the value in the template. A CSIDriver object in YAML is returned
|
||||||
|
// which can be created in the Kubernetes cluster.
|
||||||
|
func NewCSIDriverYAML(values CSIDriverValues) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
tmpl, err := template.New("CSIDriver").Parse(csiDriver)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse template: %w", err)
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(&buf, values)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to replace values in template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
9
api/deploy/kubernetes/nfs/csidriver.yaml
Normal file
9
api/deploy/kubernetes/nfs/csidriver.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: CSIDriver
|
||||||
|
metadata:
|
||||||
|
name: "{{ .Name }}"
|
||||||
|
spec:
|
||||||
|
attachRequired: false
|
||||||
|
volumeLifecycleModes:
|
||||||
|
- Persistent
|
38
api/deploy/kubernetes/nfs/csidriver_test.go
Normal file
38
api/deploy/kubernetes/nfs/csidriver_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 nfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCSIDriver(t *testing.T) {
|
||||||
|
driver, err := NewCSIDriver(CSIDriverDefaults)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, driver)
|
||||||
|
require.Equal(t, driver.Name, CSIDriverDefaults.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCSIDriverYAML(t *testing.T) {
|
||||||
|
yaml, err := NewCSIDriverYAML(CSIDriverDefaults)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEqual(t, "", yaml)
|
||||||
|
}
|
@ -38,7 +38,7 @@ SNAPSHOT_VERSION=v5.0.1
|
|||||||
HELM_VERSION=v3.1.2
|
HELM_VERSION=v3.1.2
|
||||||
|
|
||||||
# minikube settings
|
# minikube settings
|
||||||
MINIKUBE_VERSION=v1.25.0
|
MINIKUBE_VERSION=v1.25.2
|
||||||
VM_DRIVER=none
|
VM_DRIVER=none
|
||||||
CHANGE_MINIKUBE_NONE_USER=true
|
CHANGE_MINIKUBE_NONE_USER=true
|
||||||
|
|
||||||
|
@ -126,6 +126,8 @@ spec:
|
|||||||
mountPath: /etc/ceph-csi-config/
|
mountPath: /etc/ceph-csi-config/
|
||||||
- name: keys-tmp-dir
|
- name: keys-tmp-dir
|
||||||
mountPath: /tmp/csi/keys
|
mountPath: /tmp/csi/keys
|
||||||
|
- name: ceph-csi-mountinfo
|
||||||
|
mountPath: /csi/mountinfo
|
||||||
resources:
|
resources:
|
||||||
{{ toYaml .Values.nodeplugin.plugin.resources | indent 12 }}
|
{{ toYaml .Values.nodeplugin.plugin.resources | indent 12 }}
|
||||||
{{- if .Values.nodeplugin.httpMetrics.enabled }}
|
{{- if .Values.nodeplugin.httpMetrics.enabled }}
|
||||||
@ -207,6 +209,10 @@ spec:
|
|||||||
emptyDir: {
|
emptyDir: {
|
||||||
medium: "Memory"
|
medium: "Memory"
|
||||||
}
|
}
|
||||||
|
- name: ceph-csi-mountinfo
|
||||||
|
hostPath:
|
||||||
|
path: {{ .Values.kubeletDir }}/plugins/{{ .Values.driverName }}/mountinfo
|
||||||
|
type: DirectoryOrCreate
|
||||||
{{- if .Values.nodeplugin.affinity }}
|
{{- if .Values.nodeplugin.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{ toYaml .Values.nodeplugin.affinity | indent 8 -}}
|
{{ toYaml .Values.nodeplugin.affinity | indent 8 -}}
|
||||||
|
@ -133,6 +133,9 @@ spec:
|
|||||||
mountPath: /tmp/csi/keys
|
mountPath: /tmp/csi/keys
|
||||||
- name: ceph-logdir
|
- name: ceph-logdir
|
||||||
mountPath: /var/log/ceph
|
mountPath: /var/log/ceph
|
||||||
|
- name: oidc-token
|
||||||
|
mountPath: /run/secrets/tokens
|
||||||
|
readOnly: true
|
||||||
resources:
|
resources:
|
||||||
{{ toYaml .Values.nodeplugin.plugin.resources | indent 12 }}
|
{{ toYaml .Values.nodeplugin.plugin.resources | indent 12 }}
|
||||||
{{- if .Values.nodeplugin.httpMetrics.enabled }}
|
{{- if .Values.nodeplugin.httpMetrics.enabled }}
|
||||||
@ -221,6 +224,13 @@ spec:
|
|||||||
emptyDir: {
|
emptyDir: {
|
||||||
medium: "Memory"
|
medium: "Memory"
|
||||||
}
|
}
|
||||||
|
- name: oidc-token
|
||||||
|
projected:
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
path: oidc-token
|
||||||
|
expirationSeconds: 3600
|
||||||
|
audience: ceph-csi-kms
|
||||||
{{- if .Values.nodeplugin.affinity }}
|
{{- if .Values.nodeplugin.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{ toYaml .Values.nodeplugin.affinity | indent 8 -}}
|
{{ toYaml .Values.nodeplugin.affinity | indent 8 -}}
|
||||||
|
@ -183,6 +183,9 @@ spec:
|
|||||||
mountPath: /etc/ceph-csi-encryption-kms-config/
|
mountPath: /etc/ceph-csi-encryption-kms-config/
|
||||||
- name: keys-tmp-dir
|
- name: keys-tmp-dir
|
||||||
mountPath: /tmp/csi/keys
|
mountPath: /tmp/csi/keys
|
||||||
|
- name: oidc-token
|
||||||
|
mountPath: /run/secrets/tokens
|
||||||
|
readOnly: true
|
||||||
resources:
|
resources:
|
||||||
{{ toYaml .Values.nodeplugin.plugin.resources | indent 12 }}
|
{{ toYaml .Values.nodeplugin.plugin.resources | indent 12 }}
|
||||||
{{- if .Values.provisioner.deployController }}
|
{{- if .Values.provisioner.deployController }}
|
||||||
@ -271,6 +274,13 @@ spec:
|
|||||||
emptyDir: {
|
emptyDir: {
|
||||||
medium: "Memory"
|
medium: "Memory"
|
||||||
}
|
}
|
||||||
|
- name: oidc-token
|
||||||
|
projected:
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
path: oidc-token
|
||||||
|
expirationSeconds: 3600
|
||||||
|
audience: ceph-csi-kms
|
||||||
{{- if .Values.provisioner.affinity }}
|
{{- if .Values.provisioner.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{ toYaml .Values.provisioner.affinity | indent 8 -}}
|
{{ toYaml .Values.provisioner.affinity | indent 8 -}}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/ceph/ceph-csi/internal/controller"
|
"github.com/ceph/ceph-csi/internal/controller"
|
||||||
"github.com/ceph/ceph-csi/internal/controller/persistentvolume"
|
"github.com/ceph/ceph-csi/internal/controller/persistentvolume"
|
||||||
"github.com/ceph/ceph-csi/internal/liveness"
|
"github.com/ceph/ceph-csi/internal/liveness"
|
||||||
|
nfsdriver "github.com/ceph/ceph-csi/internal/nfs/driver"
|
||||||
rbddriver "github.com/ceph/ceph-csi/internal/rbd/driver"
|
rbddriver "github.com/ceph/ceph-csi/internal/rbd/driver"
|
||||||
"github.com/ceph/ceph-csi/internal/util"
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
"github.com/ceph/ceph-csi/internal/util/log"
|
"github.com/ceph/ceph-csi/internal/util/log"
|
||||||
@ -37,11 +38,13 @@ import (
|
|||||||
const (
|
const (
|
||||||
rbdType = "rbd"
|
rbdType = "rbd"
|
||||||
cephFSType = "cephfs"
|
cephFSType = "cephfs"
|
||||||
|
nfsType = "nfs"
|
||||||
livenessType = "liveness"
|
livenessType = "liveness"
|
||||||
controllerType = "controller"
|
controllerType = "controller"
|
||||||
|
|
||||||
rbdDefaultName = "rbd.csi.ceph.com"
|
rbdDefaultName = "rbd.csi.ceph.com"
|
||||||
cephFSDefaultName = "cephfs.csi.ceph.com"
|
cephFSDefaultName = "cephfs.csi.ceph.com"
|
||||||
|
nfsDefaultName = "nfs.csi.ceph.com"
|
||||||
livenessDefaultName = "liveness.csi.ceph.com"
|
livenessDefaultName = "liveness.csi.ceph.com"
|
||||||
|
|
||||||
pollTime = 60 // seconds
|
pollTime = 60 // seconds
|
||||||
@ -58,7 +61,7 @@ var conf util.Config
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// common flags
|
// common flags
|
||||||
flag.StringVar(&conf.Vtype, "type", "", "driver type [rbd|cephfs|liveness|controller]")
|
flag.StringVar(&conf.Vtype, "type", "", "driver type [rbd|cephfs|nfs|liveness|controller]")
|
||||||
flag.StringVar(&conf.Endpoint, "endpoint", "unix:///tmp/csi.sock", "CSI endpoint")
|
flag.StringVar(&conf.Endpoint, "endpoint", "unix:///tmp/csi.sock", "CSI endpoint")
|
||||||
flag.StringVar(&conf.DriverName, "drivername", "", "name of the driver")
|
flag.StringVar(&conf.DriverName, "drivername", "", "name of the driver")
|
||||||
flag.StringVar(&conf.DriverNamespace, "drivernamespace", defaultNS, "namespace in which driver is deployed")
|
flag.StringVar(&conf.DriverNamespace, "drivernamespace", defaultNS, "namespace in which driver is deployed")
|
||||||
@ -149,6 +152,8 @@ func getDriverName() string {
|
|||||||
return rbdDefaultName
|
return rbdDefaultName
|
||||||
case cephFSType:
|
case cephFSType:
|
||||||
return cephFSDefaultName
|
return cephFSDefaultName
|
||||||
|
case nfsType:
|
||||||
|
return nfsDefaultName
|
||||||
case livenessType:
|
case livenessType:
|
||||||
return livenessDefaultName
|
return livenessDefaultName
|
||||||
default:
|
default:
|
||||||
@ -156,16 +161,20 @@ func getDriverName() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printVersion() {
|
||||||
|
fmt.Println("Cephcsi Version:", util.DriverVersion)
|
||||||
|
fmt.Println("Git Commit:", util.GitCommit)
|
||||||
|
fmt.Println("Go Version:", runtime.Version())
|
||||||
|
fmt.Println("Compiler:", runtime.Compiler)
|
||||||
|
fmt.Printf("Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||||
|
if kv, err := util.GetKernelVersion(); err == nil {
|
||||||
|
fmt.Println("Kernel:", kv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if conf.Version {
|
if conf.Version {
|
||||||
fmt.Println("Cephcsi Version:", util.DriverVersion)
|
printVersion()
|
||||||
fmt.Println("Git Commit:", util.GitCommit)
|
|
||||||
fmt.Println("Go Version:", runtime.Version())
|
|
||||||
fmt.Println("Compiler:", runtime.Compiler)
|
|
||||||
fmt.Printf("Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
|
||||||
if kv, err := util.GetKernelVersion(); err == nil {
|
|
||||||
fmt.Println("Kernel:", kv)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
log.DefaultLog("Driver version: %s and Git version: %s", util.DriverVersion, util.GitCommit)
|
log.DefaultLog("Driver version: %s and Git version: %s", util.DriverVersion, util.GitCommit)
|
||||||
@ -229,6 +238,10 @@ func main() {
|
|||||||
driver := cephfs.NewDriver()
|
driver := cephfs.NewDriver()
|
||||||
driver.Run(&conf)
|
driver.Run(&conf)
|
||||||
|
|
||||||
|
case nfsType:
|
||||||
|
driver := nfsdriver.NewDriver()
|
||||||
|
driver.Run(&conf)
|
||||||
|
|
||||||
case livenessType:
|
case livenessType:
|
||||||
liveness.Run(&conf)
|
liveness.Run(&conf)
|
||||||
|
|
||||||
|
@ -15,12 +15,16 @@
|
|||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: \
|
all: \
|
||||||
scc.yaml \
|
scc.yaml \
|
||||||
|
nfs/kubernetes/csidriver.yaml \
|
||||||
rbd/kubernetes/csidriver.yaml \
|
rbd/kubernetes/csidriver.yaml \
|
||||||
rbd/kubernetes/csi-config-map.yaml
|
rbd/kubernetes/csi-config-map.yaml
|
||||||
|
|
||||||
scc.yaml: ../api/deploy/ocp/scc.yaml ../api/deploy/ocp/scc.go
|
scc.yaml: ../api/deploy/ocp/scc.yaml ../api/deploy/ocp/scc.go
|
||||||
$(MAKE) -C ../tools generate-deploy
|
$(MAKE) -C ../tools generate-deploy
|
||||||
|
|
||||||
|
nfs/kubernetes/csidriver.yaml: ../api/deploy/kubernetes/nfs/csidriver.yaml ../api/deploy/kubernetes/nfs/csidriver.go
|
||||||
|
$(MAKE) -C ../tools generate-deploy
|
||||||
|
|
||||||
rbd/kubernetes/csidriver.yaml: ../api/deploy/kubernetes/rbd/csidriver.yaml ../api/deploy/kubernetes/rbd/csidriver.go
|
rbd/kubernetes/csidriver.yaml: ../api/deploy/kubernetes/rbd/csidriver.yaml ../api/deploy/kubernetes/rbd/csidriver.go
|
||||||
$(MAKE) -C ../tools generate-deploy
|
$(MAKE) -C ../tools generate-deploy
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@ RUN dnf -y install \
|
|||||||
/usr/bin/cc \
|
/usr/bin/cc \
|
||||||
make \
|
make \
|
||||||
git \
|
git \
|
||||||
|
&& dnf clean all \
|
||||||
|
&& rm -rf /var/cache/yum \
|
||||||
&& true
|
&& true
|
||||||
|
|
||||||
ENV GOROOT=${GOROOT} \
|
ENV GOROOT=${GOROOT} \
|
||||||
|
@ -100,6 +100,8 @@ spec:
|
|||||||
mountPath: /etc/ceph-csi-config/
|
mountPath: /etc/ceph-csi-config/
|
||||||
- name: keys-tmp-dir
|
- name: keys-tmp-dir
|
||||||
mountPath: /tmp/csi/keys
|
mountPath: /tmp/csi/keys
|
||||||
|
- name: ceph-csi-mountinfo
|
||||||
|
mountPath: /csi/mountinfo
|
||||||
- name: liveness-prometheus
|
- name: liveness-prometheus
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
@ -164,6 +166,10 @@ spec:
|
|||||||
emptyDir: {
|
emptyDir: {
|
||||||
medium: "Memory"
|
medium: "Memory"
|
||||||
}
|
}
|
||||||
|
- name: ceph-csi-mountinfo
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kubelet/plugins/cephfs.csi.ceph.com/mountinfo
|
||||||
|
type: DirectoryOrCreate
|
||||||
---
|
---
|
||||||
# This is a service to expose the liveness metrics
|
# This is a service to expose the liveness metrics
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
16
deploy/nfs/kubernetes/csidriver.yaml
Normal file
16
deploy/nfs/kubernetes/csidriver.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#
|
||||||
|
# /!\ DO NOT MODIFY THIS FILE
|
||||||
|
#
|
||||||
|
# This file has been automatically generated by Ceph-CSI yamlgen.
|
||||||
|
# The source for the contents can be found in the api/deploy directory, make
|
||||||
|
# your modifications there.
|
||||||
|
#
|
||||||
|
---
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: CSIDriver
|
||||||
|
metadata:
|
||||||
|
name: "nfs.csi.ceph.com"
|
||||||
|
spec:
|
||||||
|
attachRequired: false
|
||||||
|
volumeLifecycleModes:
|
||||||
|
- Persistent
|
@ -163,6 +163,9 @@ spec:
|
|||||||
mountPath: /tmp/csi/keys
|
mountPath: /tmp/csi/keys
|
||||||
- name: ceph-config
|
- name: ceph-config
|
||||||
mountPath: /etc/ceph/
|
mountPath: /etc/ceph/
|
||||||
|
- name: oidc-token
|
||||||
|
mountPath: /run/secrets/tokens
|
||||||
|
readOnly: true
|
||||||
- name: csi-rbdplugin-controller
|
- name: csi-rbdplugin-controller
|
||||||
# for stable functionality replace canary with latest release version
|
# for stable functionality replace canary with latest release version
|
||||||
image: quay.io/cephcsi/cephcsi:canary
|
image: quay.io/cephcsi/cephcsi:canary
|
||||||
@ -231,3 +234,10 @@ spec:
|
|||||||
emptyDir: {
|
emptyDir: {
|
||||||
medium: "Memory"
|
medium: "Memory"
|
||||||
}
|
}
|
||||||
|
- name: oidc-token
|
||||||
|
projected:
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
path: oidc-token
|
||||||
|
expirationSeconds: 3600
|
||||||
|
audience: ceph-csi-kms
|
||||||
|
@ -118,6 +118,9 @@ spec:
|
|||||||
mountPath: /var/log/ceph
|
mountPath: /var/log/ceph
|
||||||
- name: ceph-config
|
- name: ceph-config
|
||||||
mountPath: /etc/ceph/
|
mountPath: /etc/ceph/
|
||||||
|
- name: oidc-token
|
||||||
|
mountPath: /run/secrets/tokens
|
||||||
|
readOnly: true
|
||||||
- name: liveness-prometheus
|
- name: liveness-prometheus
|
||||||
securityContext:
|
securityContext:
|
||||||
privileged: true
|
privileged: true
|
||||||
@ -189,6 +192,13 @@ spec:
|
|||||||
emptyDir: {
|
emptyDir: {
|
||||||
medium: "Memory"
|
medium: "Memory"
|
||||||
}
|
}
|
||||||
|
- name: oidc-token
|
||||||
|
projected:
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
path: oidc-token
|
||||||
|
expirationSeconds: 3600
|
||||||
|
audience: ceph-csi-kms
|
||||||
---
|
---
|
||||||
# This is a service to expose the liveness metrics
|
# This is a service to expose the liveness metrics
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
|
45
docs/ceph-fuse-corruption.md
Normal file
45
docs/ceph-fuse-corruption.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# ceph-fuse: detection of corrupted mounts and their recovery
|
||||||
|
|
||||||
|
Mounts managed by ceph-fuse may get corrupted by e.g. the ceph-fuse process
|
||||||
|
exiting abruptly, or its parent Node Plugin container being terminated, taking
|
||||||
|
down its child processes with it.
|
||||||
|
|
||||||
|
This may manifest in concerned workloads like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
# mount | grep fuse
|
||||||
|
ceph-fuse on /cephfs-share type fuse.ceph-fuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other)
|
||||||
|
# ls /cephfs-share
|
||||||
|
ls: /cephfs-share: Socket not connected
|
||||||
|
```
|
||||||
|
|
||||||
|
or,
|
||||||
|
|
||||||
|
```
|
||||||
|
# stat /home/kubelet/pods/ae344b80-3b07-4589-b1a1-ca75fa9debf2/volumes/kubernetes.io~csi/pvc-ec69de59-7823-4840-8eee-544f8261fef0/mount: transport endpoint is not connected
|
||||||
|
```
|
||||||
|
|
||||||
|
This feature allows CSI CephFS plugin to be able to detect if a ceph-fuse mount
|
||||||
|
is corrupted during the volume publishing phase, and will attempt to recover it
|
||||||
|
for the newly scheduled pod. Pods that already reside on a node whose
|
||||||
|
ceph-fuse mountpoints were broken may still need to be restarted, however.
|
||||||
|
|
||||||
|
## Detection
|
||||||
|
|
||||||
|
A mountpoint is deemed corrupted if `stat()`-ing it returns one of the
|
||||||
|
following errors:
|
||||||
|
|
||||||
|
* `ENOTCONN`
|
||||||
|
* `ESTALE`
|
||||||
|
* `EIO`
|
||||||
|
* `EACCES`
|
||||||
|
* `EHOSTDOWN`
|
||||||
|
|
||||||
|
## Recovery
|
||||||
|
|
||||||
|
Once a mountpoint corruption is detected, its recovery is performed by
|
||||||
|
remounting the volume associated with it.
|
||||||
|
|
||||||
|
Recovery is attempted only if `/csi/mountinfo` directory is made available to
|
||||||
|
CSI CephFS plugin (available by default in the Helm chart and Kubernetes
|
||||||
|
manifests).
|
@ -382,6 +382,34 @@ the AWS KMS is expected to contain:
|
|||||||
This Secret is expected to be created by the administrator who deployed
|
This Secret is expected to be created by the administrator who deployed
|
||||||
Ceph-CSI.
|
Ceph-CSI.
|
||||||
|
|
||||||
|
#### Configuring Amazon KMS with Amazon STS
|
||||||
|
|
||||||
|
Ceph-CSI can be configured to use
|
||||||
|
[Amazon STS](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html),
|
||||||
|
when kubernetes cluster is configured with OIDC identity provider to fetch
|
||||||
|
credentials to access Amazon KMS. Other functionalities is the same as
|
||||||
|
[Amazon KMS encryption](#configuring-amazon-kms).
|
||||||
|
|
||||||
|
There are a few settings that need to be included in the [KMS configuration
|
||||||
|
file](../examples/kms/vault/kms-config.yaml):
|
||||||
|
|
||||||
|
1. `encryptionKMSType`: should be set to `aws-sts-metadata`.
|
||||||
|
1. `secretName`: name of the Kubernetes Secret (in the Namespace where
|
||||||
|
PVC is created) which contains the credentials for communicating with
|
||||||
|
AWS. This defaults to `ceph-csi-aws-credentials`.
|
||||||
|
|
||||||
|
The [Secret with credentials](../examples/kms/vault/aws-sts-credentials.yaml) for
|
||||||
|
the AWS KMS is expected to contain:
|
||||||
|
|
||||||
|
1. `awsRoleARN`: Role which will be used access credentials from AWS STS
|
||||||
|
and access AWS KMS for encryption.
|
||||||
|
1. `awsCMKARN`: Custom Master Key, ARN for the key used to encrypt the
|
||||||
|
passphrase
|
||||||
|
1. `awsRegion`: the region where the AWS STS and KMS service is available.
|
||||||
|
|
||||||
|
This Secret is expected to be created by the tenant/user in each namespace where
|
||||||
|
Ceph-CSI is used to create encrypted rbd volumes.
|
||||||
|
|
||||||
### Encryption prerequisites
|
### Encryption prerequisites
|
||||||
|
|
||||||
In order for encryption to work you need to make sure that `dm-crypt` kernel
|
In order for encryption to work you need to make sure that `dm-crypt` kernel
|
||||||
|
70
docs/design/proposals/nfs.md
Normal file
70
docs/design/proposals/nfs.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Dynamic provisioning of NFS volumes
|
||||||
|
|
||||||
|
Ceph has [support for NFS-Ganesha to export directories][ceph_mgr_nfs] on
|
||||||
|
CephFS. This can be used to export CephFS based volumes over NFS.
|
||||||
|
|
||||||
|
## Node-Plugin for mounting NFS-exports
|
||||||
|
|
||||||
|
The Kubernetes CSI community provides and maintains a [NFS CSI][nfs_csi]
|
||||||
|
driver. This driver can be used as a Node-Plugin so that NFS CSI volumes can be
|
||||||
|
mounted. When a CSI volume has the `server` and `share`
|
||||||
|
[parameters][nfs_csi_params], the Node-Plugin will be able to mount the
|
||||||
|
NFS-export.
|
||||||
|
|
||||||
|
## Exporting CephFS based volumes over NFS
|
||||||
|
|
||||||
|
Ceph-CSI already creates CephFS volumes, that can be mounted over the native
|
||||||
|
CephFS protocol. A new provisioner in Ceph-CSI can create CephFS volumes, and
|
||||||
|
include the required [NFS CSI parameters][nfs_csi_params] so that the [NFS CSI
|
||||||
|
driver][nfs_csi] can mount the CephFS volume over NFS.
|
||||||
|
|
||||||
|
The provisioner that handles the CSI requests for NFS volume can call the [Ceph
|
||||||
|
Mgr commands to export/unexport][ceph_mgr_nfs] the CephFS volumes. The CephFS
|
||||||
|
volumes would be internally managed by the NFS provisioner, and only be exposed
|
||||||
|
as NFS CSI volumes towards the consumers.
|
||||||
|
|
||||||
|
### `CreateVolume` CSI operation
|
||||||
|
|
||||||
|
When the Ceph-CSI NFS provisioner is requested to create a NFS CSI volume, the
|
||||||
|
following steps need to be taken:
|
||||||
|
|
||||||
|
1. create a CephFS volume, use the CephFS `CreateVolume` call or other internal
|
||||||
|
API
|
||||||
|
1. call the Ceph Mgr API to export the CephFS volume with NFS-Ganesha
|
||||||
|
1. return the NFS CSI volume, with `server` and `share` parameters (other
|
||||||
|
parameters that are useful for CephFS volume management may be kept)
|
||||||
|
|
||||||
|
The 2nd step requires a NFS-cluster name for the Ceph Mgr call(s). The name of
|
||||||
|
the NFS-cluster as managed by Ceph should be provided in the parameters of the
|
||||||
|
`CreateVolume` operation. For Kubernetes that means the parameters is set as an
|
||||||
|
option in the `StorageClass`.
|
||||||
|
|
||||||
|
The `server` parameter of the volume is an other option that is managed by the
|
||||||
|
Ceph (or Rook) infrastructure. This parameter is also required to be provided
|
||||||
|
in the `CreateVolume` parameters.
|
||||||
|
|
||||||
|
Removing the NFS-export for the volume (or other operations) requires the name
|
||||||
|
of the NFS-cluster, as it is needed for the Ceph Mgr API. Like other parameters
|
||||||
|
of the CephFS volume, it will be needed to store the NFS-cluster name in the
|
||||||
|
OMAP journalling.
|
||||||
|
|
||||||
|
### `DeleteVolume` CSI operation
|
||||||
|
|
||||||
|
The `DeleteVolume` operation only receives the `volume_id` parameter, which
|
||||||
|
is to be used by the CSI Controller (provisioner) to locate the backing volume.
|
||||||
|
The `DeleteVolume` operation for the CephFS provisioner already knows how to
|
||||||
|
delete volumes by ID.
|
||||||
|
|
||||||
|
In order to remove the exported volume from the NFS-cluster, the operation
|
||||||
|
needs to fetch the name of the NFS-cluster from the journal where it was stored
|
||||||
|
during `CreateVolume`.
|
||||||
|
|
||||||
|
### Additional CSI operations
|
||||||
|
|
||||||
|
`CreateVolume` and `DeleteVolume` are the only required operations for the CSI
|
||||||
|
Controller (provisioner). Additional features as they are supported by CephFS
|
||||||
|
can forward the operations to the CephFS provisioner at a later time.
|
||||||
|
|
||||||
|
[ceph_mgr_nfs]: https://docs.ceph.com/en/latest/mgr/nfs/
|
||||||
|
[nfs_csi]: https://github.com/kubernetes-csi/csi-driver-nfs
|
||||||
|
[nfs_csi_params]: https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/docs/driver-parameters.md
|
@ -228,7 +228,7 @@ static CephFS PV
|
|||||||
| :----------: | :--------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
|
| :----------: | :--------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
|
||||||
| clusterID | The clusterID is used by the CSI plugin to uniquely identify and use a Ceph cluster (this is the key in configmap created duing ceph-csi deployment) | Yes |
|
| clusterID | The clusterID is used by the CSI plugin to uniquely identify and use a Ceph cluster (this is the key in configmap created duing ceph-csi deployment) | Yes |
|
||||||
| fsName | CephFS filesystem name into which the subvolume should be created/present | Yes |
|
| fsName | CephFS filesystem name into which the subvolume should be created/present | Yes |
|
||||||
| staticVolume | Value must be set to `true` to mount and unmount static rbd PVC | Yes |
|
| staticVolume | Value must be set to `true` to mount and unmount static cephFS PVC | Yes |
|
||||||
| rootPath | Actual path of the subvolume in ceph cluster, can be retrieved by issuing getpath command as described above | Yes |
|
| rootPath | Actual path of the subvolume in ceph cluster, can be retrieved by issuing getpath command as described above | Yes |
|
||||||
|
|
||||||
**Note** ceph-csi does not supports CephFS subvolume deletion for static PV.
|
**Note** ceph-csi does not supports CephFS subvolume deletion for static PV.
|
||||||
|
129
e2e/cephfs.go
129
e2e/cephfs.go
@ -280,6 +280,7 @@ var _ = Describe("cephfs", func() {
|
|||||||
It("Test CephFS CSI", func() {
|
It("Test CephFS CSI", func() {
|
||||||
pvcPath := cephFSExamplePath + "pvc.yaml"
|
pvcPath := cephFSExamplePath + "pvc.yaml"
|
||||||
appPath := cephFSExamplePath + "pod.yaml"
|
appPath := cephFSExamplePath + "pod.yaml"
|
||||||
|
deplPath := cephFSExamplePath + "deployment.yaml"
|
||||||
appRWOPPath := cephFSExamplePath + "pod-rwop.yaml"
|
appRWOPPath := cephFSExamplePath + "pod-rwop.yaml"
|
||||||
pvcClonePath := cephFSExamplePath + "pvc-restore.yaml"
|
pvcClonePath := cephFSExamplePath + "pvc-restore.yaml"
|
||||||
pvcSmartClonePath := cephFSExamplePath + "pvc-clone.yaml"
|
pvcSmartClonePath := cephFSExamplePath + "pvc-clone.yaml"
|
||||||
@ -504,6 +505,134 @@ var _ = Describe("cephfs", func() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
By("verifying that ceph-fuse recovery works for new pods", func() {
|
||||||
|
err := deleteResource(cephFSExamplePath + "storageclass.yaml")
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to delete CephFS storageclass: %v", err)
|
||||||
|
}
|
||||||
|
err = createCephfsStorageClass(f.ClientSet, f, true, map[string]string{
|
||||||
|
"mounter": "fuse",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to create CephFS storageclass: %v", err)
|
||||||
|
}
|
||||||
|
replicas := int32(2)
|
||||||
|
pvc, depl, err := validatePVCAndDeploymentAppBinding(
|
||||||
|
f, pvcPath, deplPath, f.UniqueName, &replicas, deployTimeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to create PVC and Deployment: %v", err)
|
||||||
|
}
|
||||||
|
deplPods, err := listPods(f, depl.Namespace, &metav1.ListOptions{
|
||||||
|
LabelSelector: fmt.Sprintf("app=%s", depl.Labels["app"]),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to list pods for Deployment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doStat := func(podName string) (stdErr string, err error) {
|
||||||
|
_, stdErr, err = execCommandInContainerByPodName(
|
||||||
|
f,
|
||||||
|
fmt.Sprintf("stat %s", depl.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath),
|
||||||
|
depl.Namespace,
|
||||||
|
podName,
|
||||||
|
depl.Spec.Template.Spec.Containers[0].Name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return stdErr, err
|
||||||
|
}
|
||||||
|
ensureStatSucceeds := func(podName string) error {
|
||||||
|
stdErr, statErr := doStat(podName)
|
||||||
|
if statErr != nil || stdErr != "" {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"expected stat to succeed without error output ; got err %w, stderr %s",
|
||||||
|
statErr, stdErr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pod1Name, pod2Name := deplPods[0].Name, deplPods[1].Name
|
||||||
|
|
||||||
|
// stat() ceph-fuse mountpoints to make sure they are working.
|
||||||
|
for i := range deplPods {
|
||||||
|
err = ensureStatSucceeds(deplPods[i].Name)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Kill ceph-fuse in cephfs-csi node plugin Pods.
|
||||||
|
nodePluginSelector, err := getDaemonSetLabelSelector(f, cephCSINamespace, cephFSDeamonSetName)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to get node plugin DaemonSet label selector: %v", err)
|
||||||
|
}
|
||||||
|
_, stdErr, err := execCommandInContainer(
|
||||||
|
f, "killall -9 ceph-fuse", cephCSINamespace, "csi-cephfsplugin", &metav1.ListOptions{
|
||||||
|
LabelSelector: nodePluginSelector,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("killall command failed: err %v, stderr %s", err, stdErr)
|
||||||
|
}
|
||||||
|
// Verify Pod podName2 that stat()-ing the mountpoint results in ENOTCONN.
|
||||||
|
stdErr, err = doStat(pod2Name)
|
||||||
|
if err == nil || !strings.Contains(stdErr, "not connected") {
|
||||||
|
e2elog.Failf(
|
||||||
|
"expected stat to fail with 'Transport endpoint not connected' or 'Socket not connected'; got err %v, stderr %s",
|
||||||
|
err, stdErr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Delete podName2 Pod. This serves two purposes: it verifies that deleting pods with
|
||||||
|
// corrupted ceph-fuse mountpoints works, and it lets the replicaset controller recreate
|
||||||
|
// the pod with hopefully mounts working again.
|
||||||
|
err = deletePod(pod2Name, depl.Namespace, c, deployTimeout)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf(err.Error())
|
||||||
|
}
|
||||||
|
// Wait for the second Pod to be recreated.
|
||||||
|
err = waitForDeploymentComplete(c, depl.Name, depl.Namespace, deployTimeout)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf(err.Error())
|
||||||
|
}
|
||||||
|
// List Deployment's pods again to get name of the new pod.
|
||||||
|
deplPods, err = listPods(f, depl.Namespace, &metav1.ListOptions{
|
||||||
|
LabelSelector: fmt.Sprintf("app=%s", depl.Labels["app"]),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to list pods for Deployment: %v", err)
|
||||||
|
}
|
||||||
|
for i := range deplPods {
|
||||||
|
if deplPods[i].Name != pod1Name {
|
||||||
|
pod2Name = deplPods[i].Name
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pod2Name == "" {
|
||||||
|
podNames := make([]string, len(deplPods))
|
||||||
|
for i := range deplPods {
|
||||||
|
podNames[i] = deplPods[i].Name
|
||||||
|
}
|
||||||
|
e2elog.Failf("no new replica found ; found replicas %v", podNames)
|
||||||
|
}
|
||||||
|
// Verify Pod podName3 has its ceph-fuse mount working again.
|
||||||
|
err = ensureStatSucceeds(pod2Name)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete created resources.
|
||||||
|
err = deletePVCAndDeploymentApp(f, pvc, depl)
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to delete PVC and Deployment: %v", err)
|
||||||
|
}
|
||||||
|
err = deleteResource(cephFSExamplePath + "storageclass.yaml")
|
||||||
|
if err != nil {
|
||||||
|
e2elog.Failf("failed to delete CephFS storageclass: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
By("create a PVC and bind it to an app", func() {
|
By("create a PVC and bind it to an app", func() {
|
||||||
err := createCephfsStorageClass(f.ClientSet, f, false, nil)
|
err := createCephfsStorageClass(f.ClientSet, f, false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
23
e2e/pod.go
23
e2e/pod.go
@ -214,6 +214,29 @@ func execCommandInContainer(
|
|||||||
return stdOut, stdErr, err
|
return stdOut, stdErr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execCommandInContainerByPodName(
|
||||||
|
f *framework.Framework, shellCmd, namespace, podName, containerName string,
|
||||||
|
) (string, string, error) {
|
||||||
|
cmd := []string{"/bin/sh", "-c", shellCmd}
|
||||||
|
execOpts := framework.ExecOptions{
|
||||||
|
Command: cmd,
|
||||||
|
PodName: podName,
|
||||||
|
Namespace: namespace,
|
||||||
|
ContainerName: containerName,
|
||||||
|
Stdin: nil,
|
||||||
|
CaptureStdout: true,
|
||||||
|
CaptureStderr: true,
|
||||||
|
PreserveWhitespace: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
stdOut, stdErr, err := f.ExecWithOptions(execOpts)
|
||||||
|
if stdErr != "" {
|
||||||
|
e2elog.Logf("stdErr occurred: %v", stdErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdOut, stdErr, err
|
||||||
|
}
|
||||||
|
|
||||||
func execCommandInToolBoxPod(f *framework.Framework, c, ns string) (string, string, error) {
|
func execCommandInToolBoxPod(f *framework.Framework, c, ns string) (string, string, error) {
|
||||||
opt := &metav1.ListOptions{
|
opt := &metav1.ListOptions{
|
||||||
LabelSelector: rookToolBoxPodLabel,
|
LabelSelector: rookToolBoxPodLabel,
|
||||||
|
12
e2e/rbd.go
12
e2e/rbd.go
@ -984,7 +984,7 @@ var _ = Describe("RBD", func() {
|
|||||||
}
|
}
|
||||||
app.Namespace = f.UniqueName
|
app.Namespace = f.UniqueName
|
||||||
|
|
||||||
err = createPVCAndDeploymentApp(f, "", pvc, app, deployTimeout)
|
err = createPVCAndDeploymentApp(f, pvc, app, deployTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e2elog.Failf("failed to create PVC and application: %v", err)
|
e2elog.Failf("failed to create PVC and application: %v", err)
|
||||||
}
|
}
|
||||||
@ -1014,7 +1014,7 @@ var _ = Describe("RBD", func() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = deletePVCAndDeploymentApp(f, "", pvc, app)
|
err = deletePVCAndDeploymentApp(f, pvc, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e2elog.Failf("failed to delete PVC and application: %v", err)
|
e2elog.Failf("failed to delete PVC and application: %v", err)
|
||||||
}
|
}
|
||||||
@ -1093,7 +1093,7 @@ var _ = Describe("RBD", func() {
|
|||||||
appClone.Namespace = f.UniqueName
|
appClone.Namespace = f.UniqueName
|
||||||
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = pvcClone.Name
|
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = pvcClone.Name
|
||||||
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ReadOnly = true
|
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ReadOnly = true
|
||||||
err = createPVCAndDeploymentApp(f, "", pvcClone, appClone, deployTimeout)
|
err = createPVCAndDeploymentApp(f, pvcClone, appClone, deployTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e2elog.Failf("failed to create PVC and application: %v", err)
|
e2elog.Failf("failed to create PVC and application: %v", err)
|
||||||
}
|
}
|
||||||
@ -1131,7 +1131,7 @@ var _ = Describe("RBD", func() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = deletePVCAndDeploymentApp(f, "", pvcClone, appClone)
|
err = deletePVCAndDeploymentApp(f, pvcClone, appClone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e2elog.Failf("failed to delete PVC and application: %v", err)
|
e2elog.Failf("failed to delete PVC and application: %v", err)
|
||||||
}
|
}
|
||||||
@ -1217,7 +1217,7 @@ var _ = Describe("RBD", func() {
|
|||||||
appClone.Namespace = f.UniqueName
|
appClone.Namespace = f.UniqueName
|
||||||
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = pvcClone.Name
|
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = pvcClone.Name
|
||||||
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ReadOnly = true
|
appClone.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ReadOnly = true
|
||||||
err = createPVCAndDeploymentApp(f, "", pvcClone, appClone, deployTimeout)
|
err = createPVCAndDeploymentApp(f, pvcClone, appClone, deployTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e2elog.Failf("failed to create PVC and application: %v", err)
|
e2elog.Failf("failed to create PVC and application: %v", err)
|
||||||
}
|
}
|
||||||
@ -1254,7 +1254,7 @@ var _ = Describe("RBD", func() {
|
|||||||
e2elog.Failf(stdErr)
|
e2elog.Failf(stdErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = deletePVCAndDeploymentApp(f, "", pvcClone, appClone)
|
err = deletePVCAndDeploymentApp(f, pvcClone, appClone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e2elog.Failf("failed to delete PVC and application: %v", err)
|
e2elog.Failf("failed to delete PVC and application: %v", err)
|
||||||
}
|
}
|
||||||
|
60
e2e/utils.go
60
e2e/utils.go
@ -191,19 +191,12 @@ func createPVCAndApp(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// createPVCAndDeploymentApp creates pvc and deployment, if name is not empty
|
// createPVCAndDeploymentApp creates pvc and deployment.
|
||||||
// same will be set as pvc and app name.
|
|
||||||
func createPVCAndDeploymentApp(
|
func createPVCAndDeploymentApp(
|
||||||
f *framework.Framework,
|
f *framework.Framework,
|
||||||
name string,
|
|
||||||
pvc *v1.PersistentVolumeClaim,
|
pvc *v1.PersistentVolumeClaim,
|
||||||
app *appsv1.Deployment,
|
app *appsv1.Deployment,
|
||||||
pvcTimeout int) error {
|
pvcTimeout int) error {
|
||||||
if name != "" {
|
|
||||||
pvc.Name = name
|
|
||||||
app.Name = name
|
|
||||||
app.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = name
|
|
||||||
}
|
|
||||||
err := createPVCAndvalidatePV(f.ClientSet, pvc, pvcTimeout)
|
err := createPVCAndvalidatePV(f.ClientSet, pvc, pvcTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -213,19 +206,50 @@ func createPVCAndDeploymentApp(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePVCAndDeploymentApp deletes pvc and deployment, if name is not empty
|
// validatePVCAndDeploymentAppBinding creates PVC and Deployment, and waits until
|
||||||
// same will be set as pvc and app name.
|
// all its replicas are Running. Use `replicas` to override default number of replicas
|
||||||
func deletePVCAndDeploymentApp(
|
// defined in `deploymentPath` Deployment manifest.
|
||||||
|
func validatePVCAndDeploymentAppBinding(
|
||||||
f *framework.Framework,
|
f *framework.Framework,
|
||||||
name string,
|
pvcPath string,
|
||||||
pvc *v1.PersistentVolumeClaim,
|
deploymentPath string,
|
||||||
app *appsv1.Deployment) error {
|
namespace string,
|
||||||
if name != "" {
|
replicas *int32,
|
||||||
pvc.Name = name
|
pvcTimeout int,
|
||||||
app.Name = name
|
) (*v1.PersistentVolumeClaim, *appsv1.Deployment, error) {
|
||||||
app.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = name
|
pvc, err := loadPVC(pvcPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to load PVC: %w", err)
|
||||||
|
}
|
||||||
|
pvc.Namespace = namespace
|
||||||
|
|
||||||
|
depl, err := loadAppDeployment(deploymentPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to load Deployment: %w", err)
|
||||||
|
}
|
||||||
|
depl.Namespace = f.UniqueName
|
||||||
|
if replicas != nil {
|
||||||
|
depl.Spec.Replicas = replicas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = createPVCAndDeploymentApp(f, pvc, depl, pvcTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = waitForDeploymentComplete(f.ClientSet, depl.Name, depl.Namespace, deployTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pvc, depl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePVCAndDeploymentApp deletes pvc and deployment.
|
||||||
|
func deletePVCAndDeploymentApp(
|
||||||
|
f *framework.Framework,
|
||||||
|
pvc *v1.PersistentVolumeClaim,
|
||||||
|
app *appsv1.Deployment) error {
|
||||||
err := deleteDeploymentApp(f.ClientSet, app.Name, app.Namespace, deployTimeout)
|
err := deleteDeploymentApp(f.ClientSet, app.Name, app.Namespace, deployTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
13
examples/kms/vault/aws-sts-credentials.yaml
Normal file
13
examples/kms/vault/aws-sts-credentials.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
# This is an example Kubernetes Secret that can be created in the Kubernetes
|
||||||
|
# Namespace where Ceph-CSI is deployed. The contents of this Secret will be
|
||||||
|
# used to fetch credentials from Amazon STS and use it connect to the
|
||||||
|
# Amazon KMS.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: ceph-csi-aws-credentials
|
||||||
|
stringData:
|
||||||
|
awsRoleARN: "arn:aws:iam::111122223333:role/aws-sts-kms"
|
||||||
|
awsCMKARN: "arn:aws:kms:us-west-2:111123:key/1234cd-12ab-34cd-56ef-123590ab"
|
||||||
|
awsRegion: "us-west-2"
|
@ -67,7 +67,12 @@ data:
|
|||||||
"IBM_KP_SERVICE_INSTANCE_ID": "7abef064-01dd-4237-9ea5-8b3890970be3",
|
"IBM_KP_SERVICE_INSTANCE_ID": "7abef064-01dd-4237-9ea5-8b3890970be3",
|
||||||
"IBM_KP_BASE_URL": "https://us-south.kms.cloud.ibm.com",
|
"IBM_KP_BASE_URL": "https://us-south.kms.cloud.ibm.com",
|
||||||
"IBM_KP_TOKEN_URL": "https://iam.cloud.ibm.com/oidc/token",
|
"IBM_KP_TOKEN_URL": "https://iam.cloud.ibm.com/oidc/token",
|
||||||
"IBM_KP_REGION": "us-south-2",
|
"IBM_KP_REGION": "us-south-2"
|
||||||
|
}
|
||||||
|
aws-sts-metadata-test: |-
|
||||||
|
{
|
||||||
|
"encryptionKMSType": "aws-sts-metadata",
|
||||||
|
"secretName": "ceph-csi-aws-credentials"
|
||||||
}
|
}
|
||||||
metadata:
|
metadata:
|
||||||
name: csi-kms-connection-details
|
name: csi-kms-connection-details
|
||||||
|
@ -96,6 +96,10 @@ data:
|
|||||||
"secretName": "ceph-csi-kp-credentials",
|
"secretName": "ceph-csi-kp-credentials",
|
||||||
"keyProtectRegionKey": "us-south-2",
|
"keyProtectRegionKey": "us-south-2",
|
||||||
"keyProtectServiceInstanceID": "7abef064-01dd-4237-9ea5-8b3890970be3"
|
"keyProtectServiceInstanceID": "7abef064-01dd-4237-9ea5-8b3890970be3"
|
||||||
|
},
|
||||||
|
"aws-sts-metadata-test": {
|
||||||
|
"encryptionKMSType": "aws-sts-metadata",
|
||||||
|
"secretName": "ceph-csi-aws-credentials"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata:
|
metadata:
|
||||||
|
70
examples/nfs/README.md
Normal file
70
examples/nfs/README.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Dynamic provisioning with NFS
|
||||||
|
|
||||||
|
The easiest way to try out the examples for dynamic provisioning with NFS, is
|
||||||
|
to use [Rook Ceph with CephNFS][rook_ceph]. Rook can be used to deploy a Ceph
|
||||||
|
cluster. Ceph is able to maintain a NFS-Ganesha service with a few commands,
|
||||||
|
making configuring the Ceph cluster a minimal effort.
|
||||||
|
|
||||||
|
## Enabling the Ceph NFS-service
|
||||||
|
|
||||||
|
Ceph does not enable the NFS-service by default. In order for Rook Ceph to be
|
||||||
|
able to configure NFS-exports, the NFS-service needs to be configured first.
|
||||||
|
|
||||||
|
In the [Rook Toolbox][rook_toolbox], run the following commands:
|
||||||
|
|
||||||
|
```console
|
||||||
|
ceph osd pool create nfs-ganesha
|
||||||
|
ceph mgr module enable rook
|
||||||
|
ceph mgr module enable nfs
|
||||||
|
ceph orch set backend rook
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a NFS-cluster
|
||||||
|
|
||||||
|
In the directory where this `README` is located, there is an example
|
||||||
|
`rook-nfs.yaml` file. This file can be used to create a Ceph managed
|
||||||
|
NFS-cluster with the name "my-nfs".
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl create -f rook-nfs.yaml
|
||||||
|
cephnfs.ceph.rook.io/my-nfs created
|
||||||
|
```
|
||||||
|
|
||||||
|
The CephNFS resource will create a NFS-Ganesha Pod and Service with label
|
||||||
|
`app=rook-ceph-nfs`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl get pods -l app=rook-ceph-nfs
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
rook-ceph-nfs-my-nfs-a-5d47f66977-sc2rk 2/2 Running 0 61s
|
||||||
|
$ kubectl get service -l app=rook-ceph-nfs
|
||||||
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||||
|
rook-ceph-nfs-my-nfs-a ClusterIP 172.30.218.195 <none> 2049/TCP 2m58s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a StorageClass
|
||||||
|
|
||||||
|
The parameters of the StorageClass reflect mostly what CephFS requires to
|
||||||
|
connect to the Ceph cluster. All required options are commented clearly in the
|
||||||
|
`storageclass.yaml` file.
|
||||||
|
|
||||||
|
In addition to the CephFS parameters, there are:
|
||||||
|
|
||||||
|
- `nfsCluster`: name of the Ceph managed NFS-cluster (here `my-nfs`)
|
||||||
|
- `server`: hostname/IP/service of the NFS-server (here `172.30.218.195`)
|
||||||
|
|
||||||
|
Edit `storageclass.yaml`, and create the resource:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl create -f storageclass.yaml
|
||||||
|
storageclass.storage.k8s.io/csi-nfs-sc created
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO: next steps
|
||||||
|
|
||||||
|
- deploy the NFS-provisioner
|
||||||
|
- deploy the kubernetes-csi/csi-driver-nfs
|
||||||
|
- create the CSIDriver object
|
||||||
|
|
||||||
|
[rook_ceph]: https://rook.io/docs/rook/latest/ceph-nfs-crd.html
|
||||||
|
[rook_toolbox]: https://rook.io/docs/rook/latest/ceph-toolbox.html
|
17
examples/nfs/pod.yaml
Normal file
17
examples/nfs/pod.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: cephcsi-nfs-demo-pod
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: web-server
|
||||||
|
image: docker.io/library/nginx:latest
|
||||||
|
volumeMounts:
|
||||||
|
- name: mypvc
|
||||||
|
mountPath: /var/lib/www
|
||||||
|
volumes:
|
||||||
|
- name: mypvc
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: cephcsi-nfs-pvc
|
||||||
|
readOnly: false
|
12
examples/nfs/pvc.yaml
Normal file
12
examples/nfs/pvc.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: cephcsi-nfs-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: csi-nfs-sc
|
19
examples/nfs/rook-nfs.yaml
Normal file
19
examples/nfs/rook-nfs.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
apiVersion: ceph.rook.io/v1
|
||||||
|
kind: CephNFS
|
||||||
|
metadata:
|
||||||
|
name: my-nfs
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
# For Ceph v15, the rados block is required. It is ignored for Ceph v16.
|
||||||
|
rados:
|
||||||
|
# Ceph v16 always uses/expects "nfs-ganesha"
|
||||||
|
pool: nfs-ganesha
|
||||||
|
# RADOS namespace where NFS client recovery data is stored in the pool.
|
||||||
|
# fixed value for Ceph v16: the name of this CephNFS object
|
||||||
|
namespace: my-nfs
|
||||||
|
|
||||||
|
# Settings for the NFS server
|
||||||
|
server:
|
||||||
|
# the number of active NFS servers
|
||||||
|
active: 1
|
49
examples/nfs/storageclass.yaml
Normal file
49
examples/nfs/storageclass.yaml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: StorageClass
|
||||||
|
metadata:
|
||||||
|
name: csi-nfs-sc
|
||||||
|
provisioner: nfs.csi.ceph.com
|
||||||
|
parameters:
|
||||||
|
# (required) Name of the NFS-cluster as managed by Ceph.
|
||||||
|
nfsCluster: <nfs-cluster>
|
||||||
|
|
||||||
|
# (required) Hostname, ip-address or service that points to the Ceph managed
|
||||||
|
# NFS-server that will be used for mounting the NFS-export.
|
||||||
|
server: <nfs-server>
|
||||||
|
|
||||||
|
#
|
||||||
|
# The parameters below are standard CephFS options, these are used for
|
||||||
|
# managing the underlying CephFS volume.
|
||||||
|
#
|
||||||
|
|
||||||
|
# (required) String representing a Ceph cluster to provision storage from.
|
||||||
|
# Should be unique across all Ceph clusters in use for provisioning,
|
||||||
|
# cannot be greater than 36 bytes in length, and should remain immutable for
|
||||||
|
# the lifetime of the StorageClass in use.
|
||||||
|
# Ensure to create an entry in the configmap named ceph-csi-config, based on
|
||||||
|
# csi-config-map-sample.yaml, to accompany the string chosen to
|
||||||
|
# represent the Ceph cluster in clusterID below
|
||||||
|
clusterID: <cluster-id>
|
||||||
|
|
||||||
|
# (required) CephFS filesystem name into which the volume shall be created
|
||||||
|
# eg: fsName: myfs
|
||||||
|
fsName: <cephfs-name>
|
||||||
|
|
||||||
|
# (optional) Ceph pool into which volume data shall be stored
|
||||||
|
# pool: <cephfs-data-pool>
|
||||||
|
|
||||||
|
# The secrets have to contain user and/or Ceph admin credentials.
|
||||||
|
csi.storage.k8s.io/provisioner-secret-name: csi-cephfs-secret
|
||||||
|
csi.storage.k8s.io/provisioner-secret-namespace: default
|
||||||
|
csi.storage.k8s.io/controller-expand-secret-name: csi-cephfs-secret
|
||||||
|
csi.storage.k8s.io/controller-expand-secret-namespace: default
|
||||||
|
csi.storage.k8s.io/node-stage-secret-name: csi-cephfs-secret
|
||||||
|
csi.storage.k8s.io/node-stage-secret-namespace: default
|
||||||
|
|
||||||
|
# (optional) Prefix to use for naming subvolumes.
|
||||||
|
# If omitted, defaults to "csi-vol-".
|
||||||
|
volumeNamePrefix: nfs-export-
|
||||||
|
|
||||||
|
reclaimPolicy: Delete
|
||||||
|
allowVolumeExpansion: false
|
94
go.mod
94
go.mod
@ -4,7 +4,8 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/IBM/keyprotect-go-client v0.7.0
|
github.com/IBM/keyprotect-go-client v0.7.0
|
||||||
github.com/aws/aws-sdk-go v1.43.8
|
github.com/aws/aws-sdk-go v1.43.22
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.16.0
|
||||||
github.com/ceph/ceph-csi/api v0.0.0-00010101000000-000000000000
|
github.com/ceph/ceph-csi/api v0.0.0-00010101000000-000000000000
|
||||||
github.com/ceph/go-ceph v0.14.0
|
github.com/ceph/go-ceph v0.14.0
|
||||||
github.com/container-storage-interface/spec v1.5.0
|
github.com/container-storage-interface/spec v1.5.0
|
||||||
@ -13,29 +14,29 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||||
github.com/hashicorp/vault/api v1.4.1
|
github.com/hashicorp/vault/api v1.5.0
|
||||||
github.com/kubernetes-csi/csi-lib-utils v0.10.0
|
github.com/kubernetes-csi/csi-lib-utils v0.11.0
|
||||||
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0
|
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0
|
||||||
github.com/libopenstorage/secrets v0.0.0-20210908194121-a1d19aa9713a
|
github.com/libopenstorage/secrets v0.0.0-20210908194121-a1d19aa9713a
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
github.com/onsi/gomega v1.18.1
|
github.com/onsi/gomega v1.19.0
|
||||||
github.com/pborman/uuid v1.2.1
|
github.com/pborman/uuid v1.2.1
|
||||||
github.com/prometheus/client_golang v1.12.1
|
github.com/prometheus/client_golang v1.12.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.1
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
||||||
google.golang.org/grpc v1.44.0
|
google.golang.org/grpc v1.45.0
|
||||||
google.golang.org/protobuf v1.27.1
|
google.golang.org/protobuf v1.28.0
|
||||||
k8s.io/api v0.23.4
|
k8s.io/api v0.23.5
|
||||||
k8s.io/apimachinery v0.23.4
|
k8s.io/apimachinery v0.23.5
|
||||||
k8s.io/client-go v12.0.0+incompatible
|
k8s.io/client-go v12.0.0+incompatible
|
||||||
k8s.io/cloud-provider v0.23.4
|
k8s.io/cloud-provider v0.23.5
|
||||||
k8s.io/klog/v2 v2.40.1
|
k8s.io/klog/v2 v2.60.1
|
||||||
//
|
//
|
||||||
// when updating k8s.io/kubernetes, make sure to update the replace section too
|
// when updating k8s.io/kubernetes, make sure to update the replace section too
|
||||||
//
|
//
|
||||||
k8s.io/kubernetes v1.23.4
|
k8s.io/kubernetes v1.23.5
|
||||||
k8s.io/mount-utils v0.23.4
|
k8s.io/mount-utils v0.23.5
|
||||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed
|
k8s.io/utils v0.0.0-20211116205334-6203023598ed
|
||||||
sigs.k8s.io/controller-runtime v0.11.0-beta.0.0.20211208212546-f236f0345ad2
|
sigs.k8s.io/controller-runtime v0.11.0-beta.0.0.20211208212546-f236f0345ad2
|
||||||
)
|
)
|
||||||
@ -43,6 +44,11 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/armon/go-metrics v0.3.9 // indirect
|
github.com/armon/go-metrics v0.3.9 // indirect
|
||||||
github.com/armon/go-radix v1.0.0 // indirect
|
github.com/armon/go-radix v1.0.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.15.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.0 // indirect
|
||||||
|
github.com/aws/smithy-go v1.11.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||||
@ -61,7 +67,7 @@ require (
|
|||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.5.5 // indirect
|
github.com/google/go-cmp v0.5.7 // indirect
|
||||||
github.com/google/gofuzz v1.1.0 // indirect
|
github.com/google/gofuzz v1.1.0 // indirect
|
||||||
github.com/google/uuid v1.1.2 // indirect
|
github.com/google/uuid v1.1.2 // indirect
|
||||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||||
@ -126,7 +132,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
@ -139,13 +145,13 @@ require (
|
|||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
k8s.io/apiserver v0.23.4 // indirect
|
k8s.io/apiserver v0.23.5 // indirect
|
||||||
k8s.io/component-base v0.23.4 // indirect
|
k8s.io/component-base v0.23.5 // indirect
|
||||||
k8s.io/component-helpers v0.23.4 // indirect
|
k8s.io/component-helpers v0.23.5 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||||
k8s.io/kubectl v0.0.0 // indirect
|
k8s.io/kubectl v0.0.0 // indirect
|
||||||
k8s.io/kubelet v0.0.0 // indirect
|
k8s.io/kubelet v0.0.0 // indirect
|
||||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.27 // indirect
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
@ -160,31 +166,31 @@ replace (
|
|||||||
//
|
//
|
||||||
// k8s.io/kubernetes depends on these k8s.io packages, but unversioned
|
// k8s.io/kubernetes depends on these k8s.io packages, but unversioned
|
||||||
//
|
//
|
||||||
k8s.io/api => k8s.io/api v0.23.4
|
k8s.io/api => k8s.io/api v0.23.5
|
||||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.4
|
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.5
|
||||||
k8s.io/apimachinery => k8s.io/apimachinery v0.23.4
|
k8s.io/apimachinery => k8s.io/apimachinery v0.23.5
|
||||||
k8s.io/apiserver => k8s.io/apiserver v0.23.4
|
k8s.io/apiserver => k8s.io/apiserver v0.23.5
|
||||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.4
|
k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5
|
||||||
k8s.io/client-go => k8s.io/client-go v0.23.4
|
k8s.io/client-go => k8s.io/client-go v0.23.5
|
||||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.4
|
k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.5
|
||||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.4
|
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.5
|
||||||
k8s.io/code-generator => k8s.io/code-generator v0.23.4
|
k8s.io/code-generator => k8s.io/code-generator v0.23.5
|
||||||
k8s.io/component-base => k8s.io/component-base v0.23.4
|
k8s.io/component-base => k8s.io/component-base v0.23.5
|
||||||
k8s.io/component-helpers => k8s.io/component-helpers v0.23.4
|
k8s.io/component-helpers => k8s.io/component-helpers v0.23.5
|
||||||
k8s.io/controller-manager => k8s.io/controller-manager v0.23.4
|
k8s.io/controller-manager => k8s.io/controller-manager v0.23.5
|
||||||
k8s.io/cri-api => k8s.io/cri-api v0.23.4
|
k8s.io/cri-api => k8s.io/cri-api v0.23.5
|
||||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.4
|
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.5
|
||||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.4
|
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.5
|
||||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.4
|
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.5
|
||||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.4
|
k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.5
|
||||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.4
|
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.5
|
||||||
k8s.io/kubectl => k8s.io/kubectl v0.23.4
|
k8s.io/kubectl => k8s.io/kubectl v0.23.5
|
||||||
k8s.io/kubelet => k8s.io/kubelet v0.23.4
|
k8s.io/kubelet => k8s.io/kubelet v0.23.5
|
||||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.4
|
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.5
|
||||||
k8s.io/metrics => k8s.io/metrics v0.23.4
|
k8s.io/metrics => k8s.io/metrics v0.23.5
|
||||||
k8s.io/mount-utils => k8s.io/mount-utils v0.23.4
|
k8s.io/mount-utils => k8s.io/mount-utils v0.23.5
|
||||||
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.4
|
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.5
|
||||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.4
|
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.5
|
||||||
// layeh.com seems to be misbehaving
|
// layeh.com seems to be misbehaving
|
||||||
layeh.com/radius => github.com/layeh/radius v0.0.0-20190322222518-890bc1058917
|
layeh.com/radius => github.com/layeh/radius v0.0.0-20190322222518-890bc1058917
|
||||||
)
|
)
|
||||||
|
132
go.sum
132
go.sum
@ -138,8 +138,20 @@ github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
|
|||||||
github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
|
github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
|
||||||
github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.43.8 h1:8a/M9C4l5CxFNM6IuNx4F1p+ITJEX12VxWxUQo61cbc=
|
github.com/aws/aws-sdk-go v1.43.22 h1:QY9/1TZB73UDEVQ68sUVJXf/7QUiHZl7zbbLF1wpqlc=
|
||||||
github.com/aws/aws-sdk-go v1.43.8/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
github.com/aws/aws-sdk-go v1.43.22/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.15.0 h1:f9kWLNfyCzCB43eupDAk3/XgJ2EpgktiySD6leqs0js=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.15.0/go.mod h1:lJYcuZZEHWNIb6ugJjbQY1fykdoobWbOS7kJYb4APoI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 h1:xiGjGVQsem2cxoIX61uRGy+Jux2s9C/kKbTrWLdrU54=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6/go.mod h1:SSPEdf9spsFgJyhjrXvawfpyzrXHBCUe+2eQ1CjC1Ak=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0 h1:bt3zw79tm209glISdMRCIVRCwvSDXxgAxh5KWe2qHkY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0/go.mod h1:viTrxhAuejD+LszDahzAE2x40YjYWhMqzHxv2ZiWaME=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.0 h1:YQ3fTXACo7xeAqg0NiqcCmBOXJruUfh+4+O2qxF2EjQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.0/go.mod h1:R31ot6BgESRCIoxwfKtIHzZMo/vsZn2un81g9BJ4nmo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.16.0 h1:0+X/rJ2+DTBKWbUsn7WtF0JvNk/fRf928vkFsXkbbZs=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.16.0/go.mod h1:+8k4H2ASUZZXmjx/s3DFLo9tGBb44lkz3XcgfypJY7s=
|
||||||
|
github.com/aws/smithy-go v1.11.1 h1:IQ+lPZVkSM3FRtyaDox41R8YS6iwPMYIreejOgPW49g=
|
||||||
|
github.com/aws/smithy-go v1.11.1/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
@ -437,8 +449,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||||
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-metrics-stackdriver v0.2.0/go.mod h1:KLcPyp3dWJAFD+yHisGlJSZktIsTjb50eB72U2YZ9K0=
|
github.com/google/go-metrics-stackdriver v0.2.0/go.mod h1:KLcPyp3dWJAFD+yHisGlJSZktIsTjb50eB72U2YZ9K0=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
@ -635,8 +648,8 @@ github.com/hashicorp/vault/api v1.0.5-0.20191122173911-80fcc7907c78/go.mod h1:Uf
|
|||||||
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
|
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
|
||||||
github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
|
github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o=
|
||||||
github.com/hashicorp/vault/api v1.0.5-0.20200902155336-f9d5ce5a171a/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
|
github.com/hashicorp/vault/api v1.0.5-0.20200902155336-f9d5ce5a171a/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk=
|
||||||
github.com/hashicorp/vault/api v1.4.1 h1:mWLfPT0RhxBitjKr6swieCEP2v5pp/M//t70S3kMLRo=
|
github.com/hashicorp/vault/api v1.5.0 h1:Bp6yc2bn7CWkOrVIzFT/Qurzx528bdavF3nz590eu28=
|
||||||
github.com/hashicorp/vault/api v1.4.1/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM=
|
github.com/hashicorp/vault/api v1.5.0/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM=
|
||||||
github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU=
|
github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU=
|
||||||
github.com/hashicorp/vault/sdk v0.1.14-0.20190730042320-0dc007d98cc8/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
github.com/hashicorp/vault/sdk v0.1.14-0.20190730042320-0dc007d98cc8/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
||||||
github.com/hashicorp/vault/sdk v0.1.14-0.20191108161836-82f2b5571044/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU=
|
github.com/hashicorp/vault/sdk v0.1.14-0.20191108161836-82f2b5571044/go.mod h1:PcekaFGiPJyHnFy+NZhP6ll650zEw51Ag7g/YEa+EOU=
|
||||||
@ -729,8 +742,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kubernetes-csi/csi-lib-utils v0.10.0 h1:Aqm8X81eCzzfH/bvIEqSWtcbK9HF9NbFk4d+le1snVA=
|
github.com/kubernetes-csi/csi-lib-utils v0.11.0 h1:FHWOBtAZBA/hVk7v/qaXgG9Sxv0/n06DebPFuDwumqg=
|
||||||
github.com/kubernetes-csi/csi-lib-utils v0.10.0/go.mod h1:BmGZZB16L18+9+Lgg9YWwBKfNEHIDdgGfAyuW6p2NV0=
|
github.com/kubernetes-csi/csi-lib-utils v0.11.0/go.mod h1:BmGZZB16L18+9+Lgg9YWwBKfNEHIDdgGfAyuW6p2NV0=
|
||||||
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.0.0/go.mod h1:YBCo4DoEeDndqvAn6eeu0vWM7QdXmHEeI9cFWplmBys=
|
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.0.0/go.mod h1:YBCo4DoEeDndqvAn6eeu0vWM7QdXmHEeI9cFWplmBys=
|
||||||
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 h1:nHHjmvjitIiyPlUHk/ofpgvBcNcawJLtf4PYHORLjAA=
|
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 h1:nHHjmvjitIiyPlUHk/ofpgvBcNcawJLtf4PYHORLjAA=
|
||||||
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0/go.mod h1:YBCo4DoEeDndqvAn6eeu0vWM7QdXmHEeI9cFWplmBys=
|
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0/go.mod h1:YBCo4DoEeDndqvAn6eeu0vWM7QdXmHEeI9cFWplmBys=
|
||||||
@ -852,8 +865,8 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
|
|||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
|
github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc=
|
||||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
@ -861,8 +874,8 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
@ -1045,8 +1058,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
@ -1266,8 +1280,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -1619,8 +1634,8 @@ google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
|
|||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||||
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
|
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
@ -1628,8 +1643,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -1690,28 +1706,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
k8s.io/api v0.23.4 h1:85gnfXQOWbJa1SiWGpE9EEtHs0UVvDyIsSMpEtl2D4E=
|
k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA=
|
||||||
k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI=
|
k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8=
|
||||||
k8s.io/apiextensions-apiserver v0.23.4 h1:AFDUEu/yEf0YnuZhqhIFhPLPhhcQQVuR1u3WCh0rveU=
|
k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI=
|
||||||
k8s.io/apiextensions-apiserver v0.23.4/go.mod h1:TWYAKymJx7nLMxWCgWm2RYGXHrGlVZnxIlGnvtfYu+g=
|
k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ=
|
||||||
k8s.io/apimachinery v0.23.4 h1:fhnuMd/xUL3Cjfl64j5ULKZ1/J9n8NuQEgNL+WXWfdM=
|
k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0=
|
||||||
k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
|
k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
|
||||||
k8s.io/apiserver v0.23.4 h1:zNvQlG+C/ERjuUz4p7eY/0IWHaMixRSBoxgmyIdwo9Y=
|
k8s.io/apiserver v0.23.5 h1:2Ly8oUjz5cnZRn1YwYr+aFgDZzUmEVL9RscXbnIeDSE=
|
||||||
k8s.io/apiserver v0.23.4/go.mod h1:A6l/ZcNtxGfPSqbFDoxxOjEjSKBaQmE+UTveOmMkpNc=
|
k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw=
|
||||||
k8s.io/cli-runtime v0.23.4/go.mod h1:7KywUNTUibmHPqmpDFuRO1kc9RhsufHv2lkjCm2YZyM=
|
k8s.io/cli-runtime v0.23.5/go.mod h1:oY6QDF2qo9xndSq32tqcmRp2UyXssdGrLfjAVymgbx4=
|
||||||
k8s.io/client-go v0.23.4 h1:YVWvPeerA2gpUudLelvsolzH7c2sFoXXR5wM/sWqNFU=
|
k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8=
|
||||||
k8s.io/client-go v0.23.4/go.mod h1:PKnIL4pqLuvYUK1WU7RLTMYKPiIh7MYShLshtRY9cj0=
|
k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4=
|
||||||
k8s.io/cloud-provider v0.23.4 h1:Nx42V7+Vpaad3qZE031MpTfCDl3jeQrX6wuwieES/nc=
|
k8s.io/cloud-provider v0.23.5 h1:cf5Il2oV++RtlqgNesHd+tDFtOp85dG0t9KN/pmb71s=
|
||||||
k8s.io/cloud-provider v0.23.4/go.mod h1:+RFNcj7DczZJE250/l55hh4Be4tlHkNgdtmI4PzxhJ0=
|
k8s.io/cloud-provider v0.23.5/go.mod h1:xMZFA6pIYKweqTkWCYVgRSVMAjqOvxVr3u/kmfyxvkU=
|
||||||
k8s.io/cluster-bootstrap v0.23.4/go.mod h1:H5UZ3a4ZvjyUIgTgW8VdnN1rm3DsRqhotqK9oDMHU1o=
|
k8s.io/cluster-bootstrap v0.23.5/go.mod h1:8/Gz6VTOMmEDDhn8U/nx0McnQR4YETAqiYXIlqR8hdQ=
|
||||||
k8s.io/code-generator v0.23.4/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk=
|
k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk=
|
||||||
k8s.io/component-base v0.23.4 h1:SziYh48+QKxK+ykJ3Ejqd98XdZIseVBG7sBaNLPqy6M=
|
k8s.io/component-base v0.23.5 h1:8qgP5R6jG1BBSXmRYW+dsmitIrpk8F/fPEvgDenMCCE=
|
||||||
k8s.io/component-base v0.23.4/go.mod h1:8o3Gg8i2vnUXGPOwciiYlkSaZT+p+7gA9Scoz8y4W4E=
|
k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0=
|
||||||
k8s.io/component-helpers v0.23.4 h1:zCLeBuo3Qs0BqtJu767RXJgs5S9ruFJZcbM1aD+cMmc=
|
k8s.io/component-helpers v0.23.5 h1:6uTMNP6xxJrSzYTC7BCcH2S/PbSZGxSUZG0PG+nT4tM=
|
||||||
k8s.io/component-helpers v0.23.4/go.mod h1:1Pl7L4zukZ054ElzRbvmZ1FJIU8roBXFOeRFu8zipa4=
|
k8s.io/component-helpers v0.23.5/go.mod h1:5riXJgjTIs+ZB8xnf5M2anZ8iQuq37a0B/0BgoPQuSM=
|
||||||
k8s.io/controller-manager v0.23.4/go.mod h1:+ednTkO5Z25worecG5ORa7NssZT0cpuVunVHN+24Ccs=
|
k8s.io/controller-manager v0.23.5/go.mod h1:n/KRlUzAtkFcZodZ/w0GlQdmErVKh7lS/wS0bbo7W4I=
|
||||||
k8s.io/cri-api v0.23.4/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4=
|
k8s.io/cri-api v0.23.5/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4=
|
||||||
k8s.io/csi-translation-lib v0.23.4/go.mod h1:hvAm5aoprpfE7p9Xnfe3ObmbhDcYp3U7AZJnVQUlrqw=
|
k8s.io/csi-translation-lib v0.23.5/go.mod h1:8RyFkoHAJrFU7c7MN1ZUjctm3ZhHclKm1FIHNSyGcuw=
|
||||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||||
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||||
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||||
@ -1722,28 +1738,28 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
|||||||
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||||
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
|
||||||
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||||
k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4=
|
k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc=
|
||||||
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||||
k8s.io/kube-aggregator v0.23.4/go.mod h1:hpmPi4oaLBe014CkBCqzBYWok64H2C7Ka6FBLJvHgkg=
|
k8s.io/kube-aggregator v0.23.5/go.mod h1:3ynYx07Co6dzjpKPgipM+1/Mt2Jcm7dY++cRlKLr5s8=
|
||||||
k8s.io/kube-controller-manager v0.23.4/go.mod h1:r4Cn9Y8t3GyMPrPnOGCDRpeyEKVOITuwHJ7pIWXH0IY=
|
k8s.io/kube-controller-manager v0.23.5/go.mod h1:Pkg5lIk9YG9Qjj4F7Dn0gi6/k8cEYP63oLdgrlrrtu4=
|
||||||
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
||||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4=
|
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4=
|
||||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
|
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
|
||||||
k8s.io/kube-proxy v0.23.4/go.mod h1:uZBvTCJYVBqnlyup3JpXaMmqrlkzHjcakHhf7ojYUKk=
|
k8s.io/kube-proxy v0.23.5/go.mod h1:/yCbRrOHgPCb1g1k4XmMJPmNesfdPhZTGrvwNlNgwo8=
|
||||||
k8s.io/kube-scheduler v0.23.4/go.mod h1:KNKYvMZ8dhoMLYygiEMEK+JKFQ2fhW2CLj7B5zEQ/68=
|
k8s.io/kube-scheduler v0.23.5/go.mod h1:IJGf4WngeoAHLj4ms4n3Poa29ttmaxCXxIqpgU0ky7E=
|
||||||
k8s.io/kubectl v0.23.4 h1:mAa+zEOlyZieecEy+xSrhjkpMcukYyHWzcNdX28dzMY=
|
k8s.io/kubectl v0.23.5 h1:DmDULqCaF4qstj0Im143XmncvqWtJxHzK8IrW2BzlU0=
|
||||||
k8s.io/kubectl v0.23.4/go.mod h1:Dgb0Rvx/8JKS/C2EuvsNiQc6RZnX0SbHJVG3XUzH6ok=
|
k8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE=
|
||||||
k8s.io/kubelet v0.23.4 h1:yptgklhQ3dtHHIpH/RgI0861XWoJ9/YIBnnxYS6l8VI=
|
k8s.io/kubelet v0.23.5 h1:eCGJ7olStiyF7TYHlUTjpXg2ltw7Bs9OPZcch8HP2Go=
|
||||||
k8s.io/kubelet v0.23.4/go.mod h1:RjbycP9Wnpbw33G8yFt9E23+pFYxzWy1d8qHU0KVUgg=
|
k8s.io/kubelet v0.23.5/go.mod h1:M0aj0gaX+rOaGfCfqkV6P7QbwtMwqbL6RdwviHmnehU=
|
||||||
k8s.io/kubernetes v1.23.4 h1:25dqAMS96u+9L/A7AHdEW7aMTcmHoQMbMPug6Fa61JE=
|
k8s.io/kubernetes v1.23.5 h1:bxpSv2BKc2MqYRfyqQqLVdodLZ2r+NZ/rEdZXyUAvug=
|
||||||
k8s.io/kubernetes v1.23.4/go.mod h1:C0AB/I7M4Nu6d1ELyGdC8qrrHEc6J5l8CHUashza1Io=
|
k8s.io/kubernetes v1.23.5/go.mod h1:avI3LUTUYZugxwh52KMVM7v9ZjB5gYJ6D3FIoZ1SHUo=
|
||||||
k8s.io/legacy-cloud-providers v0.23.4/go.mod h1:dl0qIfmTyeDpRe/gaudDVnLsykKW2DE7oBWbuJl2Gd8=
|
k8s.io/legacy-cloud-providers v0.23.5/go.mod h1:IENlwY686f1fbakotgNf7gAQuIyCvOUIAXkPPPE/7KU=
|
||||||
k8s.io/metrics v0.23.4/go.mod h1:cl6sY9BdVT3DubbpqnkPIKi6mn/F2ltkU4yH1tEJ3Bo=
|
k8s.io/metrics v0.23.5/go.mod h1:WNAtV2a5BYbmDS8+7jSqYYV6E3efuGTpIwJ8PTD1wgs=
|
||||||
k8s.io/mount-utils v0.23.4 h1:tWUj5A0DJ29haMiO7F3pNdP2HwyMWczzvqQmikFc9s8=
|
k8s.io/mount-utils v0.23.5 h1:MOhJKZTpfC21r5OamKYWMdVNtTMDD9wZfTkLOhI5nuE=
|
||||||
k8s.io/mount-utils v0.23.4/go.mod h1:OTN3LQPiOGMfx/SmVlsnySwsAmh4gYrDYLchlMHtf98=
|
k8s.io/mount-utils v0.23.5/go.mod h1:OTN3LQPiOGMfx/SmVlsnySwsAmh4gYrDYLchlMHtf98=
|
||||||
k8s.io/pod-security-admission v0.23.4/go.mod h1:cikO3akkUoTZ8uFhkHdlWp0m3XosiOqssTHb+TfCjLw=
|
k8s.io/pod-security-admission v0.23.5/go.mod h1:aSyWfjev8Zil5DaZBZ+ICAObZmZlRqhnAZHxA9r71UI=
|
||||||
k8s.io/sample-apiserver v0.23.4/go.mod h1:ITqvv82GqqeRue7dmsP7A/As/MHE2v1H3vriNRFv+/U=
|
k8s.io/sample-apiserver v0.23.5/go.mod h1:m4cnT3HgRY5Dt2AjMVKGnb31D6rGY0B+xpKtRJUUC8w=
|
||||||
k8s.io/system-validators v1.6.0/go.mod h1:bPldcLgkIUK22ALflnsXk8pvkTEndYdNuaHH6gRrl0Q=
|
k8s.io/system-validators v1.6.0/go.mod h1:bPldcLgkIUK22ALflnsXk8pvkTEndYdNuaHH6gRrl0Q=
|
||||||
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||||
@ -1760,8 +1776,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
|
|||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.27 h1:KQOkVzXrLNb0EP6W0FD6u3CCPAwgXFYwZitbj7K0P0Y=
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 h1:dUk62HQ3ZFhD48Qr8MIXCiKA8wInBQCtuE4QGfFW7yA=
|
||||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.27/go.mod h1:tq2nT0Kx7W+/f2JVE+zxYtUhdjuELJkVpNz+x/QN5R4=
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw=
|
||||||
sigs.k8s.io/controller-runtime v0.2.2/go.mod h1:9dyohw3ZtoXQuV1e766PHUn+cmrRCIcBh6XIMFNMZ+I=
|
sigs.k8s.io/controller-runtime v0.2.2/go.mod h1:9dyohw3ZtoXQuV1e766PHUn+cmrRCIcBh6XIMFNMZ+I=
|
||||||
sigs.k8s.io/controller-runtime v0.11.0-beta.0.0.20211208212546-f236f0345ad2 h1:+ReKrjTrd57mtAU19BJkxSAaWRIQkFlaWcO6dGFVP1g=
|
sigs.k8s.io/controller-runtime v0.11.0-beta.0.0.20211208212546-f236f0345ad2 h1:+ReKrjTrd57mtAU19BJkxSAaWRIQkFlaWcO6dGFVP1g=
|
||||||
sigs.k8s.io/controller-runtime v0.11.0-beta.0.0.20211208212546-f236f0345ad2/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA=
|
sigs.k8s.io/controller-runtime v0.11.0-beta.0.0.20211208212546-f236f0345ad2/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA=
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
fsutil "github.com/ceph/ceph-csi/internal/cephfs/util"
|
fsutil "github.com/ceph/ceph-csi/internal/cephfs/util"
|
||||||
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
||||||
"github.com/ceph/ceph-csi/internal/util"
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/k8s"
|
||||||
"github.com/ceph/ceph-csi/internal/util/log"
|
"github.com/ceph/ceph-csi/internal/util/log"
|
||||||
|
|
||||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
@ -148,6 +149,35 @@ func checkContentSource(
|
|||||||
return nil, nil, nil, status.Errorf(codes.InvalidArgument, "not a proper volume source %v", volumeSource)
|
return nil, nil, nil, status.Errorf(codes.InvalidArgument, "not a proper volume source %v", volumeSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkValidCreateVolumeRequest checks if the request is valid
|
||||||
|
// CreateVolumeRequest by inspecting the request parameters.
|
||||||
|
func checkValidCreateVolumeRequest(
|
||||||
|
vol,
|
||||||
|
parentVol *store.VolumeOptions,
|
||||||
|
pvID *store.VolumeIdentifier,
|
||||||
|
sID *store.SnapshotIdentifier) error {
|
||||||
|
switch {
|
||||||
|
case pvID != nil:
|
||||||
|
if vol.Size < parentVol.Size {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"cannot clone from volume %s: volume size %d is smaller than source volume size %d",
|
||||||
|
pvID.VolumeID,
|
||||||
|
parentVol.Size,
|
||||||
|
vol.Size)
|
||||||
|
}
|
||||||
|
case sID != nil:
|
||||||
|
if vol.Size < parentVol.Size {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"cannot restore from snapshot %s: volume size %d is smaller than source volume size %d",
|
||||||
|
sID.SnapshotID,
|
||||||
|
parentVol.Size,
|
||||||
|
vol.Size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateVolume creates a reservation and the volume in backend, if it is not already present.
|
// CreateVolume creates a reservation and the volume in backend, if it is not already present.
|
||||||
// nolint:gocognit,gocyclo,nestif,cyclop // TODO: reduce complexity
|
// nolint:gocognit,gocyclo,nestif,cyclop // TODO: reduce complexity
|
||||||
func (cs *ControllerServer) CreateVolume(
|
func (cs *ControllerServer) CreateVolume(
|
||||||
@ -199,6 +229,11 @@ func (cs *ControllerServer) CreateVolume(
|
|||||||
defer parentVol.Destroy()
|
defer parentVol.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = checkValidCreateVolumeRequest(volOptions, parentVol, pvID, sID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
vID, err := store.CheckVolExists(ctx, volOptions, parentVol, pvID, sID, cr)
|
vID, err := store.CheckVolExists(ctx, volOptions, parentVol, pvID, sID, cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cerrors.IsCloneRetryError(err) {
|
if cerrors.IsCloneRetryError(err) {
|
||||||
@ -233,7 +268,8 @@ func (cs *ControllerServer) CreateVolume(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeContext := req.GetParameters()
|
// remove kubernetes csi prefixed parameters.
|
||||||
|
volumeContext := k8s.RemoveCSIPrefixedParameters(req.GetParameters())
|
||||||
volumeContext["subvolumeName"] = vID.FsSubvolName
|
volumeContext["subvolumeName"] = vID.FsSubvolName
|
||||||
volumeContext["subvolumePath"] = volOptions.RootPath
|
volumeContext["subvolumePath"] = volOptions.RootPath
|
||||||
volume := &csi.Volume{
|
volume := &csi.Volume{
|
||||||
@ -306,7 +342,8 @@ func (cs *ControllerServer) CreateVolume(
|
|||||||
|
|
||||||
log.DebugLog(ctx, "cephfs: successfully created backing volume named %s for request name %s",
|
log.DebugLog(ctx, "cephfs: successfully created backing volume named %s for request name %s",
|
||||||
vID.FsSubvolName, requestName)
|
vID.FsSubvolName, requestName)
|
||||||
volumeContext := req.GetParameters()
|
// remove kubernetes csi prefixed parameters.
|
||||||
|
volumeContext := k8s.RemoveCSIPrefixedParameters(req.GetParameters())
|
||||||
volumeContext["subvolumeName"] = vID.FsSubvolName
|
volumeContext["subvolumeName"] = vID.FsSubvolName
|
||||||
volumeContext["subvolumePath"] = volOptions.RootPath
|
volumeContext["subvolumePath"] = volOptions.RootPath
|
||||||
volume := &csi.Volume{
|
volume := &csi.Volume{
|
||||||
@ -786,10 +823,10 @@ func (cs *ControllerServer) DeleteSnapshot(
|
|||||||
// success as deletion is complete
|
// success as deletion is complete
|
||||||
return &csi.DeleteSnapshotResponse{}, nil
|
return &csi.DeleteSnapshotResponse{}, nil
|
||||||
case errors.Is(err, cerrors.ErrSnapNotFound):
|
case errors.Is(err, cerrors.ErrSnapNotFound):
|
||||||
err = store.UndoSnapReservation(ctx, volOpt, *sid, sid.FsSnapshotName, cr)
|
err = store.UndoSnapReservation(ctx, volOpt, *sid, sid.RequestName, cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLog(ctx, "failed to remove reservation for snapname (%s) with backing snap (%s) (%s)",
|
log.ErrorLog(ctx, "failed to remove reservation for snapname (%s) with backing snap (%s) (%s)",
|
||||||
sid.FsSubvolName, sid.FsSnapshotName, err)
|
sid.RequestName, sid.FsSnapshotName, err)
|
||||||
|
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
@ -799,10 +836,10 @@ func (cs *ControllerServer) DeleteSnapshot(
|
|||||||
// if the error is ErrVolumeNotFound, the subvolume is already deleted
|
// if the error is ErrVolumeNotFound, the subvolume is already deleted
|
||||||
// from backend, Hence undo the omap entries and return success
|
// from backend, Hence undo the omap entries and return success
|
||||||
log.ErrorLog(ctx, "Volume not present")
|
log.ErrorLog(ctx, "Volume not present")
|
||||||
err = store.UndoSnapReservation(ctx, volOpt, *sid, sid.FsSnapshotName, cr)
|
err = store.UndoSnapReservation(ctx, volOpt, *sid, sid.RequestName, cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLog(ctx, "failed to remove reservation for snapname (%s) with backing snap (%s) (%s)",
|
log.ErrorLog(ctx, "failed to remove reservation for snapname (%s) with backing snap (%s) (%s)",
|
||||||
sid.FsSubvolName, sid.FsSnapshotName, err)
|
sid.RequestName, sid.FsSnapshotName, err)
|
||||||
|
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
@ -837,7 +874,7 @@ func (cs *ControllerServer) DeleteSnapshot(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
err = store.UndoSnapReservation(ctx, volOpt, *sid, sid.FsSnapshotName, cr)
|
err = store.UndoSnapReservation(ctx, volOpt, *sid, sid.RequestName, cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLog(ctx, "failed to remove reservation for snapname (%s) with backing snap (%s) (%s)",
|
log.ErrorLog(ctx, "failed to remove reservation for snapname (%s) with backing snap (%s) (%s)",
|
||||||
sid.RequestName, sid.FsSnapshotName, err)
|
sid.RequestName, sid.FsSnapshotName, err)
|
||||||
|
@ -237,7 +237,6 @@ func (s *subVolumeClient) CreateVolume(ctx context.Context) error {
|
|||||||
opts.PoolLayout = s.Pool
|
opts.PoolLayout = s.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("this is for debugging ")
|
|
||||||
// FIXME: check if the right credentials are used ("-n", cephEntityClientPrefix + cr.ID)
|
// FIXME: check if the right credentials are used ("-n", cephEntityClientPrefix + cr.ID)
|
||||||
err = ca.CreateSubVolume(s.FsName, s.SubvolumeGroup, s.VolID, &opts)
|
err = ca.CreateSubVolume(s.FsName, s.SubvolumeGroup, s.VolID, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
273
internal/cephfs/fuserecovery.go
Normal file
273
internal/cephfs/fuserecovery.go
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 cephfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/cephfs/mounter"
|
||||||
|
"github.com/ceph/ceph-csi/internal/cephfs/store"
|
||||||
|
fsutil "github.com/ceph/ceph-csi/internal/cephfs/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/log"
|
||||||
|
|
||||||
|
mountutil "k8s.io/mount-utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
mountState int
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
msUnknown mountState = iota
|
||||||
|
msNotMounted
|
||||||
|
msMounted
|
||||||
|
msCorrupted
|
||||||
|
|
||||||
|
// ceph-fuse fsType in /proc/<PID>/mountinfo.
|
||||||
|
cephFuseFsType = "fuse.ceph-fuse"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ms mountState) String() string {
|
||||||
|
return [...]string{
|
||||||
|
"UNKNOWN",
|
||||||
|
"NOT_MOUNTED",
|
||||||
|
"MOUNTED",
|
||||||
|
"CORRUPTED",
|
||||||
|
}[int(ms)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMountState(path string) (mountState, error) {
|
||||||
|
isMnt, err := util.IsMountPoint(path)
|
||||||
|
if err != nil {
|
||||||
|
if util.IsCorruptedMountError(err) {
|
||||||
|
return msCorrupted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return msUnknown, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMnt {
|
||||||
|
return msMounted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return msNotMounted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMountinfo(mountpoint string, mis []mountutil.MountInfo) int {
|
||||||
|
for i := range mis {
|
||||||
|
if mis[i].MountPoint == mountpoint {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures that given mountpoint is of specified fstype.
|
||||||
|
// Returns true if fstype matches, or if no such mountpoint exists.
|
||||||
|
func validateFsType(mountpoint, fsType string, mis []mountutil.MountInfo) bool {
|
||||||
|
if idx := findMountinfo(mountpoint, mis); idx > 0 {
|
||||||
|
mi := mis[idx]
|
||||||
|
|
||||||
|
if mi.FsType != fsType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryRestoreFuseMountsInNodePublish tries to restore staging and publish
|
||||||
|
// volume moutpoints inside the NodePublishVolume call.
|
||||||
|
//
|
||||||
|
// Restoration is performed in following steps:
|
||||||
|
// 1. Detection: staging target path must be a working mountpoint, and target
|
||||||
|
// path must not be a corrupted mountpoint (see getMountState()). If either
|
||||||
|
// of those checks fail, mount recovery is performed.
|
||||||
|
// 2. Recovery preconditions:
|
||||||
|
// * NodeStageMountinfo is present for this volume,
|
||||||
|
// * if staging target path and target path are mountpoints, they must be
|
||||||
|
// managed by ceph-fuse,
|
||||||
|
// * VolumeOptions.Mounter must evaluate to "fuse".
|
||||||
|
// 3. Recovery:
|
||||||
|
// * staging target path is unmounted and mounted again using ceph-fuse,
|
||||||
|
// * target path is only unmounted; NodePublishVolume is then expected to
|
||||||
|
// continue normally.
|
||||||
|
func (ns *NodeServer) tryRestoreFuseMountsInNodePublish(
|
||||||
|
ctx context.Context,
|
||||||
|
volID fsutil.VolumeID,
|
||||||
|
stagingTargetPath string,
|
||||||
|
targetPath string,
|
||||||
|
volContext map[string]string,
|
||||||
|
) error {
|
||||||
|
// Check if there is anything to restore.
|
||||||
|
|
||||||
|
stagingTargetMs, err := getMountState(stagingTargetPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetMs, err := getMountState(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if stagingTargetMs == msMounted && targetMs != msCorrupted {
|
||||||
|
// Mounts seem to be fine.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something is broken. Try to proceed with mount recovery.
|
||||||
|
|
||||||
|
log.WarningLog(ctx, "cephfs: mount problem detected when publishing a volume: %s is %s, %s is %s; attempting recovery",
|
||||||
|
stagingTargetPath, stagingTargetMs, targetPath, targetMs)
|
||||||
|
|
||||||
|
// NodeStageMountinfo entry must be present for this volume.
|
||||||
|
|
||||||
|
var nsMountinfo *fsutil.NodeStageMountinfo
|
||||||
|
|
||||||
|
if nsMountinfo, err = fsutil.GetNodeStageMountinfo(volID); err != nil {
|
||||||
|
return err
|
||||||
|
} else if nsMountinfo == nil {
|
||||||
|
log.WarningLog(ctx, "cephfs: cannot proceed with mount recovery because NodeStageMountinfo record is missing")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the existing stage and publish mounts for this volume are
|
||||||
|
// managed by ceph-fuse, and that the mounter is of the FuseMounter type.
|
||||||
|
// Then try to restore them.
|
||||||
|
|
||||||
|
var (
|
||||||
|
volMounter mounter.VolumeMounter
|
||||||
|
volOptions *store.VolumeOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
procMountInfo, err := util.ReadMountInfoForProc("self")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validateFsType(stagingTargetPath, cephFuseFsType, procMountInfo) ||
|
||||||
|
!validateFsType(targetPath, cephFuseFsType, procMountInfo) {
|
||||||
|
// We can't restore mounts not managed by ceph-fuse.
|
||||||
|
log.WarningLog(ctx, "cephfs: cannot proceed with mount recovery on non-FUSE mountpoints")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
volOptions, err = ns.getVolumeOptions(ctx, volID, volContext, nsMountinfo.Secrets)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
volMounter, err = mounter.New(volOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := volMounter.(*mounter.FuseMounter); !ok {
|
||||||
|
// We can't restore mounts with non-FUSE mounter.
|
||||||
|
log.WarningLog(ctx, "cephfs: cannot proceed with mount recovery with non-FUSE mounter")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to restore mount in staging target path.
|
||||||
|
// Unmount and mount the volume.
|
||||||
|
|
||||||
|
if stagingTargetMs != msMounted {
|
||||||
|
if err := mounter.UnmountVolume(ctx, stagingTargetPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ns.mount(
|
||||||
|
ctx,
|
||||||
|
volMounter,
|
||||||
|
volOptions,
|
||||||
|
volID,
|
||||||
|
stagingTargetPath,
|
||||||
|
nsMountinfo.Secrets,
|
||||||
|
nsMountinfo.VolumeCapability,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to restore mount in target path.
|
||||||
|
// Only unmount the bind mount. NodePublishVolume should then
|
||||||
|
// create the bind mount by itself.
|
||||||
|
|
||||||
|
if err := mounter.UnmountVolume(ctx, targetPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to restore FUSE mount of the staging target path in NodeStageVolume.
|
||||||
|
// If corruption is detected, try to only unmount the volume. NodeStageVolume
|
||||||
|
// should be able to continue with mounting the volume normally afterwards.
|
||||||
|
func (ns *NodeServer) tryRestoreFuseMountInNodeStage(
|
||||||
|
ctx context.Context,
|
||||||
|
mnt mounter.VolumeMounter,
|
||||||
|
stagingTargetPath string,
|
||||||
|
) error {
|
||||||
|
// Check if there is anything to restore.
|
||||||
|
|
||||||
|
stagingTargetMs, err := getMountState(stagingTargetPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if stagingTargetMs != msCorrupted {
|
||||||
|
// Mounts seem to be fine.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something is broken. Try to proceed with mount recovery.
|
||||||
|
|
||||||
|
log.WarningLog(ctx, "cephfs: mountpoint problem detected when staging a volume: %s is %s; attempting recovery",
|
||||||
|
stagingTargetPath, stagingTargetMs)
|
||||||
|
|
||||||
|
// Check that the existing stage mount for this volume is managed by
|
||||||
|
// ceph-fuse, and that the mounter is FuseMounter. Then try to restore them.
|
||||||
|
|
||||||
|
procMountInfo, err := util.ReadMountInfoForProc("self")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validateFsType(stagingTargetPath, cephFuseFsType, procMountInfo) {
|
||||||
|
// We can't restore mounts not managed by ceph-fuse.
|
||||||
|
log.WarningLog(ctx, "cephfs: cannot proceed with mount recovery on non-FUSE mountpoints")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := mnt.(*mounter.FuseMounter); !ok {
|
||||||
|
// We can't restore mounts with non-FUSE mounter.
|
||||||
|
log.WarningLog(ctx, "cephfs: cannot proceed with mount recovery with non-FUSE mounter")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restoration here means only unmounting the volume.
|
||||||
|
// NodeStageVolume should take care of the rest.
|
||||||
|
return mounter.UnmountVolume(ctx, stagingTargetPath)
|
||||||
|
}
|
@ -47,11 +47,10 @@ type NodeServer struct {
|
|||||||
|
|
||||||
func getCredentialsForVolume(
|
func getCredentialsForVolume(
|
||||||
volOptions *store.VolumeOptions,
|
volOptions *store.VolumeOptions,
|
||||||
req *csi.NodeStageVolumeRequest) (*util.Credentials, error) {
|
secrets map[string]string) (*util.Credentials, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
cr *util.Credentials
|
cr *util.Credentials
|
||||||
secrets = req.GetSecrets()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if volOptions.ProvisionVolume {
|
if volOptions.ProvisionVolume {
|
||||||
@ -64,7 +63,7 @@ func getCredentialsForVolume(
|
|||||||
} else {
|
} else {
|
||||||
// The volume is pre-made, credentials are in node stage secrets
|
// The volume is pre-made, credentials are in node stage secrets
|
||||||
|
|
||||||
cr, err = util.NewUserCredentials(req.GetSecrets())
|
cr, err = util.NewUserCredentials(secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get user credentials from node stage secrets: %w", err)
|
return nil, fmt.Errorf("failed to get user credentials from node stage secrets: %w", err)
|
||||||
}
|
}
|
||||||
@ -73,11 +72,38 @@ func getCredentialsForVolume(
|
|||||||
return cr, nil
|
return cr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ns *NodeServer) getVolumeOptions(
|
||||||
|
ctx context.Context,
|
||||||
|
volID fsutil.VolumeID,
|
||||||
|
volContext,
|
||||||
|
volSecrets map[string]string,
|
||||||
|
) (*store.VolumeOptions, error) {
|
||||||
|
volOptions, _, err := store.NewVolumeOptionsFromVolID(ctx, string(volID), volContext, volSecrets)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, cerrors.ErrInvalidVolID) {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
volOptions, _, err = store.NewVolumeOptionsFromStaticVolume(string(volID), volContext)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, cerrors.ErrNonStaticVolume) {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
volOptions, _, err = store.NewVolumeOptionsFromMonitorList(string(volID), volContext, volSecrets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return volOptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NodeStageVolume mounts the volume to a staging path on the node.
|
// NodeStageVolume mounts the volume to a staging path on the node.
|
||||||
func (ns *NodeServer) NodeStageVolume(
|
func (ns *NodeServer) NodeStageVolume(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
|
req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
|
||||||
var volOptions *store.VolumeOptions
|
|
||||||
if err := util.ValidateNodeStageVolumeRequest(req); err != nil {
|
if err := util.ValidateNodeStageVolumeRequest(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -94,31 +120,25 @@ func (ns *NodeServer) NodeStageVolume(
|
|||||||
}
|
}
|
||||||
defer ns.VolumeLocks.Release(req.GetVolumeId())
|
defer ns.VolumeLocks.Release(req.GetVolumeId())
|
||||||
|
|
||||||
volOptions, _, err := store.NewVolumeOptionsFromVolID(ctx, string(volID), req.GetVolumeContext(), req.GetSecrets())
|
volOptions, err := ns.getVolumeOptions(ctx, volID, req.GetVolumeContext(), req.GetSecrets())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, cerrors.ErrInvalidVolID) {
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// gets mon IPs from the supplied cluster info
|
|
||||||
volOptions, _, err = store.NewVolumeOptionsFromStaticVolume(string(volID), req.GetVolumeContext())
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, cerrors.ErrNonStaticVolume) {
|
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// get mon IPs from the volume context
|
|
||||||
volOptions, _, err = store.NewVolumeOptionsFromMonitorList(string(volID), req.GetVolumeContext(),
|
|
||||||
req.GetSecrets())
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defer volOptions.Destroy()
|
defer volOptions.Destroy()
|
||||||
|
|
||||||
|
mnt, err := mounter.New(volOptions)
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorLog(ctx, "failed to create mounter for volume %s: %v", volID, err)
|
||||||
|
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the volume is already mounted
|
// Check if the volume is already mounted
|
||||||
|
|
||||||
|
if err = ns.tryRestoreFuseMountInNodeStage(ctx, mnt, stagingTargetPath); err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to try to restore FUSE mounts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
isMnt, err := util.IsMountPoint(stagingTargetPath)
|
isMnt, err := util.IsMountPoint(stagingTargetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLog(ctx, "stat failed: %v", err)
|
log.ErrorLog(ctx, "stat failed: %v", err)
|
||||||
@ -133,20 +153,53 @@ func (ns *NodeServer) NodeStageVolume(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// It's not, mount now
|
// It's not, mount now
|
||||||
if err = ns.mount(ctx, volOptions, req); err != nil {
|
|
||||||
|
if err = ns.mount(
|
||||||
|
ctx,
|
||||||
|
mnt,
|
||||||
|
volOptions,
|
||||||
|
fsutil.VolumeID(req.GetVolumeId()),
|
||||||
|
req.GetStagingTargetPath(),
|
||||||
|
req.GetSecrets(),
|
||||||
|
req.GetVolumeCapability(),
|
||||||
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.DebugLog(ctx, "cephfs: successfully mounted volume %s to %s", volID, stagingTargetPath)
|
log.DebugLog(ctx, "cephfs: successfully mounted volume %s to %s", volID, stagingTargetPath)
|
||||||
|
|
||||||
|
if _, isFuse := mnt.(*mounter.FuseMounter); isFuse {
|
||||||
|
// FUSE mount recovery needs NodeStageMountinfo records.
|
||||||
|
|
||||||
|
if err = fsutil.WriteNodeStageMountinfo(volID, &fsutil.NodeStageMountinfo{
|
||||||
|
VolumeCapability: req.GetVolumeCapability(),
|
||||||
|
Secrets: req.GetSecrets(),
|
||||||
|
}); err != nil {
|
||||||
|
log.ErrorLog(ctx, "cephfs: failed to write NodeStageMountinfo for volume %s: %v", volID, err)
|
||||||
|
|
||||||
|
// Try to clean node stage mount.
|
||||||
|
if unmountErr := mounter.UnmountVolume(ctx, stagingTargetPath); unmountErr != nil {
|
||||||
|
log.ErrorLog(ctx, "cephfs: failed to unmount %s in WriteNodeStageMountinfo clean up: %v",
|
||||||
|
stagingTargetPath, unmountErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &csi.NodeStageVolumeResponse{}, nil
|
return &csi.NodeStageVolumeResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*NodeServer) mount(ctx context.Context, volOptions *store.VolumeOptions, req *csi.NodeStageVolumeRequest) error {
|
func (*NodeServer) mount(
|
||||||
stagingTargetPath := req.GetStagingTargetPath()
|
ctx context.Context,
|
||||||
volID := fsutil.VolumeID(req.GetVolumeId())
|
mnt mounter.VolumeMounter,
|
||||||
|
volOptions *store.VolumeOptions,
|
||||||
cr, err := getCredentialsForVolume(volOptions, req)
|
volID fsutil.VolumeID,
|
||||||
|
stagingTargetPath string,
|
||||||
|
secrets map[string]string,
|
||||||
|
volCap *csi.VolumeCapability,
|
||||||
|
) error {
|
||||||
|
cr, err := getCredentialsForVolume(volOptions, secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLog(ctx, "failed to get ceph credentials for volume %s: %v", volID, err)
|
log.ErrorLog(ctx, "failed to get ceph credentials for volume %s: %v", volID, err)
|
||||||
|
|
||||||
@ -154,20 +207,13 @@ func (*NodeServer) mount(ctx context.Context, volOptions *store.VolumeOptions, r
|
|||||||
}
|
}
|
||||||
defer cr.DeleteCredentials()
|
defer cr.DeleteCredentials()
|
||||||
|
|
||||||
m, err := mounter.New(volOptions)
|
log.DebugLog(ctx, "cephfs: mounting volume %s with %s", volID, mnt.Name())
|
||||||
if err != nil {
|
|
||||||
log.ErrorLog(ctx, "failed to create mounter for volume %s: %v", volID, err)
|
|
||||||
|
|
||||||
return status.Error(codes.Internal, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.DebugLog(ctx, "cephfs: mounting volume %s with %s", volID, m.Name())
|
|
||||||
|
|
||||||
readOnly := "ro"
|
readOnly := "ro"
|
||||||
|
|
||||||
if req.VolumeCapability.AccessMode.Mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
|
if volCap.AccessMode.Mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
|
||||||
req.VolumeCapability.AccessMode.Mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY {
|
volCap.AccessMode.Mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY {
|
||||||
switch m.(type) {
|
switch mnt.(type) {
|
||||||
case *mounter.FuseMounter:
|
case *mounter.FuseMounter:
|
||||||
if !csicommon.MountOptionContains(strings.Split(volOptions.FuseMountOptions, ","), readOnly) {
|
if !csicommon.MountOptionContains(strings.Split(volOptions.FuseMountOptions, ","), readOnly) {
|
||||||
volOptions.FuseMountOptions = util.MountOptionsAdd(volOptions.FuseMountOptions, readOnly)
|
volOptions.FuseMountOptions = util.MountOptionsAdd(volOptions.FuseMountOptions, readOnly)
|
||||||
@ -179,7 +225,7 @@ func (*NodeServer) mount(ctx context.Context, volOptions *store.VolumeOptions, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = m.Mount(ctx, stagingTargetPath, cr, volOptions); err != nil {
|
if err = mnt.Mount(ctx, stagingTargetPath, cr, volOptions); err != nil {
|
||||||
log.ErrorLog(ctx,
|
log.ErrorLog(ctx,
|
||||||
"failed to mount volume %s: %v Check dmesg logs if required.",
|
"failed to mount volume %s: %v Check dmesg logs if required.",
|
||||||
volID,
|
volID,
|
||||||
@ -201,8 +247,9 @@ func (ns *NodeServer) NodePublishVolume(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stagingTargetPath := req.GetStagingTargetPath()
|
||||||
targetPath := req.GetTargetPath()
|
targetPath := req.GetTargetPath()
|
||||||
volID := req.GetVolumeId()
|
volID := fsutil.VolumeID(req.GetVolumeId())
|
||||||
|
|
||||||
// Considering kubelet make sure the stage and publish operations
|
// Considering kubelet make sure the stage and publish operations
|
||||||
// are serialized, we dont need any extra locking in nodePublish
|
// are serialized, we dont need any extra locking in nodePublish
|
||||||
@ -213,12 +260,34 @@ func (ns *NodeServer) NodePublishVolume(
|
|||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := ns.tryRestoreFuseMountsInNodePublish(
|
||||||
|
ctx,
|
||||||
|
volID,
|
||||||
|
stagingTargetPath,
|
||||||
|
targetPath,
|
||||||
|
req.GetVolumeContext(),
|
||||||
|
); err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to try to restore FUSE mounts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if req.GetReadonly() {
|
if req.GetReadonly() {
|
||||||
mountOptions = append(mountOptions, "ro")
|
mountOptions = append(mountOptions, "ro")
|
||||||
}
|
}
|
||||||
|
|
||||||
mountOptions = csicommon.ConstructMountOptions(mountOptions, req.GetVolumeCapability())
|
mountOptions = csicommon.ConstructMountOptions(mountOptions, req.GetVolumeCapability())
|
||||||
|
|
||||||
|
// Ensure staging target path is a mountpoint.
|
||||||
|
|
||||||
|
if isMnt, err := util.IsMountPoint(stagingTargetPath); err != nil {
|
||||||
|
log.ErrorLog(ctx, "stat failed: %v", err)
|
||||||
|
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
} else if !isMnt {
|
||||||
|
return nil, status.Errorf(
|
||||||
|
codes.Internal, "staging path %s for volume %s is not a mountpoint", stagingTargetPath, volID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the volume is already mounted
|
// Check if the volume is already mounted
|
||||||
|
|
||||||
isMnt, err := util.IsMountPoint(targetPath)
|
isMnt, err := util.IsMountPoint(targetPath)
|
||||||
@ -238,8 +307,8 @@ func (ns *NodeServer) NodePublishVolume(
|
|||||||
|
|
||||||
if err = mounter.BindMount(
|
if err = mounter.BindMount(
|
||||||
ctx,
|
ctx,
|
||||||
req.GetStagingTargetPath(),
|
stagingTargetPath,
|
||||||
req.GetTargetPath(),
|
targetPath,
|
||||||
req.GetReadonly(),
|
req.GetReadonly(),
|
||||||
mountOptions); err != nil {
|
mountOptions); err != nil {
|
||||||
log.ErrorLog(ctx, "failed to bind-mount volume %s: %v", volID, err)
|
log.ErrorLog(ctx, "failed to bind-mount volume %s: %v", volID, err)
|
||||||
@ -260,11 +329,14 @@ func (ns *NodeServer) NodeUnpublishVolume(
|
|||||||
if err = util.ValidateNodeUnpublishVolumeRequest(req); err != nil {
|
if err = util.ValidateNodeUnpublishVolumeRequest(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// considering kubelet make sure node operations like unpublish/unstage...etc can not be called
|
// considering kubelet make sure node operations like unpublish/unstage...etc can not be called
|
||||||
// at same time, an explicit locking at time of nodeunpublish is not required.
|
// at same time, an explicit locking at time of nodeunpublish is not required.
|
||||||
targetPath := req.GetTargetPath()
|
targetPath := req.GetTargetPath()
|
||||||
isMnt, err := util.IsMountPoint(targetPath)
|
isMnt, err := util.IsMountPoint(targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.ErrorLog(ctx, "stat failed: %v", err)
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// targetPath has already been deleted
|
// targetPath has already been deleted
|
||||||
log.DebugLog(ctx, "targetPath: %s has already been deleted", targetPath)
|
log.DebugLog(ctx, "targetPath: %s has already been deleted", targetPath)
|
||||||
@ -272,7 +344,14 @@ func (ns *NodeServer) NodeUnpublishVolume(
|
|||||||
return &csi.NodeUnpublishVolumeResponse{}, nil
|
return &csi.NodeUnpublishVolumeResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
if !util.IsCorruptedMountError(err) {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Corrupted mounts need to be unmounted properly too,
|
||||||
|
// regardless of the mounter used. Continue as normal.
|
||||||
|
log.DebugLog(ctx, "cephfs: detected corrupted mount in publish target path %s, trying to unmount anyway", targetPath)
|
||||||
|
isMnt = true
|
||||||
}
|
}
|
||||||
if !isMnt {
|
if !isMnt {
|
||||||
if err = os.RemoveAll(targetPath); err != nil {
|
if err = os.RemoveAll(targetPath); err != nil {
|
||||||
@ -316,8 +395,16 @@ func (ns *NodeServer) NodeUnstageVolume(
|
|||||||
|
|
||||||
stagingTargetPath := req.GetStagingTargetPath()
|
stagingTargetPath := req.GetStagingTargetPath()
|
||||||
|
|
||||||
|
if err = fsutil.RemoveNodeStageMountinfo(fsutil.VolumeID(volID)); err != nil {
|
||||||
|
log.ErrorLog(ctx, "cephfs: failed to remove NodeStageMountinfo for volume %s: %v", volID, err)
|
||||||
|
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
isMnt, err := util.IsMountPoint(stagingTargetPath)
|
isMnt, err := util.IsMountPoint(stagingTargetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.ErrorLog(ctx, "stat failed: %v", err)
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// targetPath has already been deleted
|
// targetPath has already been deleted
|
||||||
log.DebugLog(ctx, "targetPath: %s has already been deleted", stagingTargetPath)
|
log.DebugLog(ctx, "targetPath: %s has already been deleted", stagingTargetPath)
|
||||||
@ -325,7 +412,16 @@ func (ns *NodeServer) NodeUnstageVolume(
|
|||||||
return &csi.NodeUnstageVolumeResponse{}, nil
|
return &csi.NodeUnstageVolumeResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
if !util.IsCorruptedMountError(err) {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Corrupted mounts need to be unmounted properly too,
|
||||||
|
// regardless of the mounter used. Continue as normal.
|
||||||
|
log.DebugLog(ctx,
|
||||||
|
"cephfs: detected corrupted mount in staging target path %s, trying to unmount anyway",
|
||||||
|
stagingTargetPath)
|
||||||
|
isMnt = true
|
||||||
}
|
}
|
||||||
if !isMnt {
|
if !isMnt {
|
||||||
return &csi.NodeUnstageVolumeResponse{}, nil
|
return &csi.NodeUnstageVolumeResponse{}, nil
|
||||||
|
149
internal/cephfs/util/mountinfo.go
Normal file
149
internal/cephfs/util/mountinfo.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
|
// google.golang.org/protobuf/encoding doesn't offer MessageV2().
|
||||||
|
"github.com/golang/protobuf/proto" // nolint:staticcheck // See comment above.
|
||||||
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file provides functionality to store various mount information
|
||||||
|
// in a file. It's currently used to restore ceph-fuse mounts.
|
||||||
|
// Mount info is stored in `/csi/mountinfo`.
|
||||||
|
|
||||||
|
const (
|
||||||
|
mountinfoDir = "/csi/mountinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nodeStageMountinfoRecord describes a single
|
||||||
|
// record of mountinfo of a staged volume.
|
||||||
|
// encoding/json-friendly format.
|
||||||
|
// Only for internal use for marshaling and unmarshaling.
|
||||||
|
type nodeStageMountinfoRecord struct {
|
||||||
|
VolumeCapabilityProtoJSON string `json:",omitempty"`
|
||||||
|
MountOptions []string `json:",omitempty"`
|
||||||
|
Secrets map[string]string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeStageMountinfo describes mountinfo of a volume.
|
||||||
|
type NodeStageMountinfo struct {
|
||||||
|
VolumeCapability *csi.VolumeCapability
|
||||||
|
Secrets map[string]string
|
||||||
|
MountOptions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func fmtNodeStageMountinfoFilename(volID VolumeID) string {
|
||||||
|
return path.Join(mountinfoDir, fmt.Sprintf("nodestage-%s.json", volID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *NodeStageMountinfo) toNodeStageMountinfoRecord() (*nodeStageMountinfoRecord, error) {
|
||||||
|
bs, err := protojson.Marshal(proto.MessageV2(mi.VolumeCapability))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &nodeStageMountinfoRecord{
|
||||||
|
VolumeCapabilityProtoJSON: string(bs),
|
||||||
|
MountOptions: mi.MountOptions,
|
||||||
|
Secrets: mi.Secrets,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *nodeStageMountinfoRecord) toNodeStageMountinfo() (*NodeStageMountinfo, error) {
|
||||||
|
volCapability := &csi.VolumeCapability{}
|
||||||
|
if err := protojson.Unmarshal([]byte(r.VolumeCapabilityProtoJSON), proto.MessageV2(volCapability)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NodeStageMountinfo{
|
||||||
|
VolumeCapability: volCapability,
|
||||||
|
MountOptions: r.MountOptions,
|
||||||
|
Secrets: r.Secrets,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteNodeStageMountinfo writes mount info to a file.
|
||||||
|
func WriteNodeStageMountinfo(volID VolumeID, mi *NodeStageMountinfo) error {
|
||||||
|
// Write NodeStageMountinfo into JSON-formatted byte slice.
|
||||||
|
|
||||||
|
r, err := mi.toNodeStageMountinfoRecord()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := json.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the byte slice into file.
|
||||||
|
|
||||||
|
err = os.WriteFile(fmtNodeStageMountinfoFilename(volID), bs, 0o600)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeStageMountinfo tries to retrieve NodeStageMountinfoRecord for `volID`.
|
||||||
|
// If it doesn't exist, `(nil, nil)` is returned.
|
||||||
|
func GetNodeStageMountinfo(volID VolumeID) (*NodeStageMountinfo, error) {
|
||||||
|
// Read the file.
|
||||||
|
|
||||||
|
bs, err := os.ReadFile(fmtNodeStageMountinfoFilename(volID))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshall JSON-formatted byte slice into NodeStageMountinfo struct.
|
||||||
|
|
||||||
|
r := &nodeStageMountinfoRecord{}
|
||||||
|
if err = json.Unmarshal(bs, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mi, err := r.toNodeStageMountinfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mi, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveNodeStageMountinfo tries to remove NodeStageMountinfo for `volID`.
|
||||||
|
// If no such record exists for `volID`, it's considered success too.
|
||||||
|
func RemoveNodeStageMountinfo(volID VolumeID) error {
|
||||||
|
if err := os.Remove(fmtNodeStageMountinfoFilename(volID)); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -139,6 +139,12 @@ func (r ReconcilePersistentVolume) reconcilePV(ctx context.Context, obj runtime.
|
|||||||
if pv.Spec.CSI == nil || pv.Spec.CSI.Driver != r.config.DriverName {
|
if pv.Spec.CSI == nil || pv.Spec.CSI.Driver != r.config.DriverName {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// PV is not attached to any PVC
|
||||||
|
if pv.Spec.ClaimRef == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pvcNamespace := pv.Spec.ClaimRef.Namespace
|
||||||
requestName := pv.Name
|
requestName := pv.Name
|
||||||
volumeHandler := pv.Spec.CSI.VolumeHandle
|
volumeHandler := pv.Spec.CSI.VolumeHandle
|
||||||
secretName := ""
|
secretName := ""
|
||||||
@ -171,7 +177,7 @@ func (r ReconcilePersistentVolume) reconcilePV(ctx context.Context, obj runtime.
|
|||||||
}
|
}
|
||||||
defer cr.DeleteCredentials()
|
defer cr.DeleteCredentials()
|
||||||
|
|
||||||
rbdVolID, err := rbd.RegenerateJournal(pv.Spec.CSI.VolumeAttributes, volumeHandler, requestName, cr)
|
rbdVolID, err := rbd.RegenerateJournal(pv.Spec.CSI.VolumeAttributes, volumeHandler, requestName, pvcNamespace, cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorLogMsg("failed to regenerate journal %s", err)
|
log.ErrorLogMsg("failed to regenerate journal %s", err)
|
||||||
|
|
||||||
|
@ -45,13 +45,6 @@ func (cs *DefaultControllerServer) ControllerUnpublishVolume(
|
|||||||
return nil, status.Error(codes.Unimplemented, "")
|
return nil, status.Error(codes.Unimplemented, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControllerExpandVolume expand volume.
|
|
||||||
func (cs *DefaultControllerServer) ControllerExpandVolume(
|
|
||||||
ctx context.Context,
|
|
||||||
req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListVolumes lists volumes.
|
// ListVolumes lists volumes.
|
||||||
func (cs *DefaultControllerServer) ListVolumes(
|
func (cs *DefaultControllerServer) ListVolumes(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@ -81,20 +74,6 @@ func (cs *DefaultControllerServer) ControllerGetCapabilities(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSnapshot creates snapshot.
|
|
||||||
func (cs *DefaultControllerServer) CreateSnapshot(
|
|
||||||
ctx context.Context,
|
|
||||||
req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSnapshot deletes snapshot.
|
|
||||||
func (cs *DefaultControllerServer) DeleteSnapshot(
|
|
||||||
ctx context.Context,
|
|
||||||
req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListSnapshots lists snapshots.
|
// ListSnapshots lists snapshots.
|
||||||
func (cs *DefaultControllerServer) ListSnapshots(
|
func (cs *DefaultControllerServer) ListSnapshots(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
@ -32,20 +32,6 @@ type DefaultNodeServer struct {
|
|||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeStageVolume returns unimplemented response.
|
|
||||||
func (ns *DefaultNodeServer) NodeStageVolume(
|
|
||||||
ctx context.Context,
|
|
||||||
req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeUnstageVolume returns unimplemented response.
|
|
||||||
func (ns *DefaultNodeServer) NodeUnstageVolume(
|
|
||||||
ctx context.Context,
|
|
||||||
req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeExpandVolume returns unimplemented response.
|
// NodeExpandVolume returns unimplemented response.
|
||||||
func (ns *DefaultNodeServer) NodeExpandVolume(
|
func (ns *DefaultNodeServer) NodeExpandVolume(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@ -88,13 +74,6 @@ func (ns *DefaultNodeServer) NodeGetCapabilities(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeGetVolumeStats returns volume stats.
|
|
||||||
func (ns *DefaultNodeServer) NodeGetVolumeStats(
|
|
||||||
ctx context.Context,
|
|
||||||
req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConstructMountOptions returns only unique mount options in slice.
|
// ConstructMountOptions returns only unique mount options in slice.
|
||||||
func ConstructMountOptions(mountOptions []string, volCap *csi.VolumeCapability) []string {
|
func ConstructMountOptions(mountOptions []string, volCap *csi.VolumeCapability) []string {
|
||||||
if m := volCap.GetMount(); m != nil {
|
if m := volCap.GetMount(); m != nil {
|
||||||
|
@ -742,6 +742,36 @@ func (conn *Connection) StoreImageID(ctx context.Context, pool, reservedUUID, im
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StoreAttribute stores an attribute (key/value) in omap.
|
||||||
|
func (conn *Connection) StoreAttribute(ctx context.Context, pool, reservedUUID, attribute, value string) error {
|
||||||
|
key := conn.config.commonPrefix + attribute
|
||||||
|
err := setOMapKeys(ctx, conn, pool, conn.config.namespace, conn.config.cephUUIDDirectoryPrefix+reservedUUID,
|
||||||
|
map[string]string{key: value})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set key %q to %q: %w", key, value, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAttribute fetches an attribute (key) in omap.
|
||||||
|
func (conn *Connection) FetchAttribute(ctx context.Context, pool, reservedUUID, attribute string) (string, error) {
|
||||||
|
key := conn.config.commonPrefix + attribute
|
||||||
|
values, err := getOMapValues(
|
||||||
|
ctx, conn, pool, conn.config.namespace, conn.config.cephUUIDDirectoryPrefix+reservedUUID,
|
||||||
|
conn.config.commonPrefix, []string{key})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get values for key %q from OMAP: %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, ok := values[key]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("failed to find key %q in returned map: %v", key, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Destroy frees any resources and invalidates the journal connection.
|
// Destroy frees any resources and invalidates the journal connection.
|
||||||
func (conn *Connection) Destroy() {
|
func (conn *Connection) Destroy() {
|
||||||
// invalidate cluster connection metadata
|
// invalidate cluster connection metadata
|
||||||
|
236
internal/kms/aws_sts_metadata.go
Normal file
236
internal/kms/aws_sts_metadata.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 kms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/k8s"
|
||||||
|
|
||||||
|
awsSTS "github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
awsCreds "github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
awsSession "github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
awsKMS "github.com/aws/aws-sdk-go/service/kms"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kmsTypeAWSSTSMetadata = "aws-sts-metadata"
|
||||||
|
|
||||||
|
// awsRoleSessionName is the name of the role session to connect with aws STS.
|
||||||
|
awsRoleSessionName = "ceph-csi-aws-sts-metadata"
|
||||||
|
|
||||||
|
// awsMetadataDefaultSecretsName is the default name of the Kubernetes Secret
|
||||||
|
// that contains the credentials to access the Amazon KMS. The name of
|
||||||
|
// the Secret can be configured by setting the `kmsSecretName`
|
||||||
|
// option.
|
||||||
|
//
|
||||||
|
// #nosec:G101, value not credential, just references token.
|
||||||
|
awsSTSMetadataDefaultSecretsName = "ceph-csi-aws-credentials"
|
||||||
|
|
||||||
|
// awsSTSSecretNameKey is the key for the secret name in the config map.
|
||||||
|
awsSTSSecretNameKey = "secretName"
|
||||||
|
|
||||||
|
// The following options are part of the Kubernetes Secrets.
|
||||||
|
//
|
||||||
|
// #nosec:G101, value not credential, just configuration keys.
|
||||||
|
awsSTSRoleARNKey = "awsRoleARN"
|
||||||
|
awsSTSCMKARNKey = "awsCMKARN"
|
||||||
|
awsSTSRegionKey = "awsRegion"
|
||||||
|
|
||||||
|
// tokenFilePath is the path to the file containing the OIDC token.
|
||||||
|
//
|
||||||
|
// #nosec:G101, value not credential, just path to the token.
|
||||||
|
tokenFilePath = "/run/secrets/tokens/oidc-token"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = RegisterProvider(Provider{
|
||||||
|
UniqueID: kmsTypeAWSSTSMetadata,
|
||||||
|
Initializer: initAWSSTSMetadataKMS,
|
||||||
|
})
|
||||||
|
|
||||||
|
type awsSTSMetadataKMS struct {
|
||||||
|
awsMetadataKMS
|
||||||
|
|
||||||
|
// AWS STS configuration options
|
||||||
|
role string
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAWSSTSMetadataKMS(args ProviderInitArgs) (EncryptionKMS, error) {
|
||||||
|
kms := &awsSTSMetadataKMS{
|
||||||
|
awsMetadataKMS: awsMetadataKMS{
|
||||||
|
namespace: args.Tenant,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// get secret name if set, else use default.
|
||||||
|
err := setConfigString(&kms.secretName, args.Config, awsSTSSecretNameKey)
|
||||||
|
if errors.Is(err, errConfigOptionInvalid) {
|
||||||
|
return nil, err
|
||||||
|
} else if errors.Is(err, errConfigOptionMissing) {
|
||||||
|
kms.secretName = awsSTSMetadataDefaultSecretsName
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the Kubernetes Secret with aws region, role & cmk ARN.
|
||||||
|
secrets, err := kms.getSecrets()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get secrets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
kms.role, found = secrets[awsSTSRoleARNKey]
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errConfigOptionMissing, awsSTSRoleARNKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
kms.cmk, found = secrets[awsSTSCMKARNKey]
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errConfigOptionMissing, awsSTSCMKARNKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
kms.region, found = secrets[awsSTSRegionKey]
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errConfigOptionMissing, awsSTSRegionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return kms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSecrets returns required STS configuration options from the Kubernetes Secret.
|
||||||
|
func (as *awsSTSMetadataKMS) getSecrets() (map[string]string, error) {
|
||||||
|
c, err := k8s.NewK8sClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to Kubernetes to "+
|
||||||
|
"get Secret %s/%s: %w", as.namespace, as.secretName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := c.CoreV1().Secrets(as.namespace).Get(context.TODO(),
|
||||||
|
as.secretName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get Secret %s/%s: %w",
|
||||||
|
as.namespace, as.secretName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := make(map[string]string)
|
||||||
|
for k, v := range secret.Data {
|
||||||
|
switch k {
|
||||||
|
case awsSTSRoleARNKey, awsSTSRegionKey, awsSTSCMKARNKey:
|
||||||
|
config[k] = string(v)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported option for KMS "+
|
||||||
|
"provider %q: %s", kmsTypeAWSMetadata, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWebIdentityToken returns the web identity token from the file.
|
||||||
|
func (as *awsSTSMetadataKMS) getWebIdentityToken() (string, error) {
|
||||||
|
buf, err := os.ReadFile(tokenFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read oidc token file %q: %w",
|
||||||
|
tokenFilePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getServiceWithSTS returns a new awsSession established with the STS.
|
||||||
|
func (as *awsSTSMetadataKMS) getServiceWithSTS() (*awsKMS.KMS, error) {
|
||||||
|
webIdentityToken, err := as.getWebIdentityToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get web identity token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := awsSTS.New(awsSTS.Options{
|
||||||
|
Region: as.region,
|
||||||
|
})
|
||||||
|
output, err := client.AssumeRoleWithWebIdentity(context.TODO(),
|
||||||
|
&awsSTS.AssumeRoleWithWebIdentityInput{
|
||||||
|
RoleArn: aws.String(as.role),
|
||||||
|
RoleSessionName: aws.String(awsRoleSessionName),
|
||||||
|
WebIdentityToken: aws.String(webIdentityToken),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to assume role with web identity token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := awsCreds.NewStaticCredentials(*output.Credentials.AccessKeyId,
|
||||||
|
*output.Credentials.SecretAccessKey, *output.Credentials.SessionToken)
|
||||||
|
|
||||||
|
sess, err := awsSession.NewSessionWithOptions(awsSession.Options{
|
||||||
|
SharedConfigState: awsSession.SharedConfigDisable,
|
||||||
|
Config: aws.Config{
|
||||||
|
Credentials: creds,
|
||||||
|
Region: &as.region,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create AWS session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return awsKMS.New(sess), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptDEK uses the Amazon KMS and the configured CMK to encrypt the DEK.
|
||||||
|
func (as *awsSTSMetadataKMS) EncryptDEK(_, plainDEK string) (string, error) {
|
||||||
|
svc, err := as.getServiceWithSTS()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get KMS service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := svc.Encrypt(&awsKMS.EncryptInput{
|
||||||
|
KeyId: aws.String(as.cmk),
|
||||||
|
Plaintext: []byte(plainDEK),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to encrypt DEK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64 encode the encrypted DEK, so that storing it should not have
|
||||||
|
// issues
|
||||||
|
return base64.StdEncoding.EncodeToString(result.CiphertextBlob), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptDEK uses the Amazon KMS and the configured CMK to decrypt the DEK.
|
||||||
|
func (as *awsSTSMetadataKMS) DecryptDEK(_, encryptedDEK string) (string, error) {
|
||||||
|
svc, err := as.getServiceWithSTS()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get KMS service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertextBlob, err := base64.StdEncoding.DecodeString(encryptedDEK)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode base64 cipher: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := svc.Decrypt(&awsKMS.DecryptInput{
|
||||||
|
CiphertextBlob: ciphertextBlob,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decrypt DEK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result.Plaintext), nil
|
||||||
|
}
|
29
internal/kms/aws_sts_metadata_test.go
Normal file
29
internal/kms/aws_sts_metadata_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 kms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAWSSTSMetadataKMSRegistered(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_, ok := kmsManager.providers[kmsTypeAWSSTSMetadata]
|
||||||
|
assert.True(t, ok)
|
||||||
|
}
|
152
internal/nfs/controller/controllerserver.go
Normal file
152
internal/nfs/controller/controllerserver.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/cephfs"
|
||||||
|
"github.com/ceph/ceph-csi/internal/cephfs/store"
|
||||||
|
fsutil "github.com/ceph/ceph-csi/internal/cephfs/util"
|
||||||
|
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
||||||
|
"github.com/ceph/ceph-csi/internal/journal"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/log"
|
||||||
|
|
||||||
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server struct of CEPH CSI driver with supported methods of CSI controller
|
||||||
|
// server spec.
|
||||||
|
type Server struct {
|
||||||
|
csi.UnimplementedControllerServer
|
||||||
|
|
||||||
|
// backendServer handles the CephFS requests
|
||||||
|
backendServer *cephfs.ControllerServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewControllerServer initialize a controller server for ceph CSI driver.
|
||||||
|
func NewControllerServer(d *csicommon.CSIDriver) *Server {
|
||||||
|
// global instance of the volume journal, yuck
|
||||||
|
store.VolJournal = journal.NewCSIVolumeJournalWithNamespace(cephfs.CSIInstanceID, fsutil.RadosNamespace)
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
backendServer: cephfs.NewControllerServer(d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControllerGetCapabilities uses the CephFS backendServer to return the
|
||||||
|
// capabilities that were set in the Driver.Run() function.
|
||||||
|
func (cs *Server) ControllerGetCapabilities(
|
||||||
|
ctx context.Context,
|
||||||
|
req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
|
||||||
|
return cs.backendServer.ControllerGetCapabilities(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateVolumeCapabilities checks whether the volume capabilities requested
|
||||||
|
// are supported.
|
||||||
|
func (cs *Server) ValidateVolumeCapabilities(
|
||||||
|
ctx context.Context,
|
||||||
|
req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
|
||||||
|
return cs.backendServer.ValidateVolumeCapabilities(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVolume creates the backing subvolume and on any error cleans up any
|
||||||
|
// created entities.
|
||||||
|
func (cs *Server) CreateVolume(
|
||||||
|
ctx context.Context,
|
||||||
|
req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
|
||||||
|
res, err := cs.backendServer.CreateVolume(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to create CephFS volume: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
backend := res.Volume
|
||||||
|
|
||||||
|
log.DebugLog(ctx, "CephFS volume created: %s", backend.VolumeId)
|
||||||
|
|
||||||
|
secret := req.GetSecrets()
|
||||||
|
cr, err := util.NewAdminCredentials(secret)
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorLog(ctx, "failed to retrieve admin credentials: %v", err)
|
||||||
|
|
||||||
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
defer cr.DeleteCredentials()
|
||||||
|
|
||||||
|
nfsVolume, err := NewNFSVolume(ctx, backend.VolumeId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nfsVolume.Connect(cr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to connect: %v", err)
|
||||||
|
}
|
||||||
|
defer nfsVolume.Destroy()
|
||||||
|
|
||||||
|
err = nfsVolume.CreateExport(backend)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to create export: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.DebugLog(ctx, "published NFS-export: %s", nfsVolume)
|
||||||
|
|
||||||
|
// volume has been exported over NFS, set the "share" parameter to
|
||||||
|
// allow mounting
|
||||||
|
backend.VolumeContext["share"] = nfsVolume.GetExportPath()
|
||||||
|
|
||||||
|
return &csi.CreateVolumeResponse{Volume: backend}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteVolume deletes the volume in backend and its reservation.
|
||||||
|
func (cs *Server) DeleteVolume(
|
||||||
|
ctx context.Context,
|
||||||
|
req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
|
||||||
|
secret := req.GetSecrets()
|
||||||
|
cr, err := util.NewAdminCredentials(secret)
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorLog(ctx, "failed to retrieve admin credentials: %v", err)
|
||||||
|
|
||||||
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
defer cr.DeleteCredentials()
|
||||||
|
|
||||||
|
nfsVolume, err := NewNFSVolume(ctx, req.GetVolumeId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nfsVolume.Connect(cr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to connect: %v", err)
|
||||||
|
}
|
||||||
|
defer nfsVolume.Destroy()
|
||||||
|
|
||||||
|
err = nfsVolume.DeleteExport()
|
||||||
|
// TODO: if the export does not exist, but the backend does, delete the backend
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to delete export: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.DebugLog(ctx, "deleted NFS-export: %s", nfsVolume)
|
||||||
|
|
||||||
|
return cs.backendServer.DeleteVolume(ctx, req)
|
||||||
|
}
|
317
internal/nfs/controller/volume.go
Normal file
317
internal/nfs/controller/volume.go
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
fscore "github.com/ceph/ceph-csi/internal/cephfs/core"
|
||||||
|
"github.com/ceph/ceph-csi/internal/cephfs/store"
|
||||||
|
fsutil "github.com/ceph/ceph-csi/internal/cephfs/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
|
|
||||||
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// clusterNameKey is the key in OMAP that contains the name of the
|
||||||
|
// NFS-cluster. It will be prefixed with the journal configuration.
|
||||||
|
clusterNameKey = "nfs.cluster"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NFSVolume presents the API for consumption by the CSI-controller to create,
|
||||||
|
// modify and delete the NFS-exported CephFS volume. Instances of this struct
|
||||||
|
// are short lived, they only exist as long as a CSI-procedure is active.
|
||||||
|
type NFSVolume struct {
|
||||||
|
// ctx is the context for this short living volume object
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
volumeID string
|
||||||
|
clusterID string
|
||||||
|
mons string
|
||||||
|
fscID int64
|
||||||
|
objectUUID string
|
||||||
|
|
||||||
|
// TODO: drop in favor of a go-ceph connection
|
||||||
|
cr *util.Credentials
|
||||||
|
connected bool
|
||||||
|
conn *util.ClusterConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNFSVolume create a new NFSVolume instance for the currently executing
|
||||||
|
// CSI-procedure.
|
||||||
|
func NewNFSVolume(ctx context.Context, volumeID string) (*NFSVolume, error) {
|
||||||
|
vi := util.CSIIdentifier{}
|
||||||
|
|
||||||
|
err := vi.DecomposeCSIID(volumeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding volume ID (%s): %w", volumeID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NFSVolume{
|
||||||
|
ctx: ctx,
|
||||||
|
volumeID: volumeID,
|
||||||
|
clusterID: vi.ClusterID,
|
||||||
|
fscID: vi.LocationID,
|
||||||
|
objectUUID: vi.ObjectUUID,
|
||||||
|
conn: &util.ClusterConnection{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a simple/short representation of the NFSVolume.
|
||||||
|
func (nv *NFSVolume) String() string {
|
||||||
|
return nv.volumeID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect fetches cluster connection details (like MONs) and connects to the
|
||||||
|
// Ceph cluster. This uses go-ceph, so after Connect(), Destroy() should be
|
||||||
|
// called to cleanup resources.
|
||||||
|
func (nv *NFSVolume) Connect(cr *util.Credentials) error {
|
||||||
|
if nv.connected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
nv.mons, err = util.Mons(util.CsiConfigFile, nv.clusterID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get MONs for cluster (%s): %w", nv.clusterID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nv.conn.Connect(nv.mons, cr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nv.cr = cr
|
||||||
|
nv.connected = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy cleans up resources once the NFSVolume instance is not needed
|
||||||
|
// anymore.
|
||||||
|
func (nv *NFSVolume) Destroy() {
|
||||||
|
if nv.connected {
|
||||||
|
nv.conn.Destroy()
|
||||||
|
nv.connected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExportPath returns the path on the NFS-server that can be used for
|
||||||
|
// mounting.
|
||||||
|
func (nv *NFSVolume) GetExportPath() string {
|
||||||
|
return "/" + nv.volumeID
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateExport takes the (CephFS) CSI-volume and instructs Ceph Mgr to create
|
||||||
|
// a new NFS-export for the volume on the Ceph managed NFS-server.
|
||||||
|
func (nv *NFSVolume) CreateExport(backend *csi.Volume) error {
|
||||||
|
if !nv.connected {
|
||||||
|
return fmt.Errorf("can not created export for %q: not connected", nv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := backend.VolumeContext["fsName"]
|
||||||
|
nfsCluster := backend.VolumeContext["nfsCluster"]
|
||||||
|
path := backend.VolumeContext["subvolumePath"]
|
||||||
|
|
||||||
|
err := nv.setNFSCluster(nfsCluster)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set NFS-cluster: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use new go-ceph API, see ceph/ceph-csi#2977
|
||||||
|
// new versions of Ceph use a different command, and the go-ceph API
|
||||||
|
// also seems to be different :-/
|
||||||
|
//
|
||||||
|
// run the new command, but fall back to the previous one in case of an
|
||||||
|
// error
|
||||||
|
cmds := [][]string{
|
||||||
|
// ceph nfs export create cephfs --cluster-id <cluster_id>
|
||||||
|
// --pseudo-path <pseudo_path> --fsname <fsname>
|
||||||
|
// [--readonly] [--path=/path/in/cephfs]
|
||||||
|
nv.createExportCommand("--cluster-id="+nfsCluster,
|
||||||
|
"--fsname="+fs, "--pseudo-path="+nv.GetExportPath(),
|
||||||
|
"--path="+path),
|
||||||
|
// ceph nfs export create cephfs ${FS} ${NFS} /${EXPORT} ${SUBVOL_PATH}
|
||||||
|
nv.createExportCommand(nfsCluster, fs, nv.GetExportPath(), path),
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr, err := nv.retryIfInvalid(cmds)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create export %q in NFS-cluster %q"+
|
||||||
|
"(%v): %s", nv, nfsCluster, err, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryIfInvalid executes the "ceph" command, and falls back to the next cmd
|
||||||
|
// in case the error is EINVAL.
|
||||||
|
func (nv *NFSVolume) retryIfInvalid(cmds [][]string) (string, error) {
|
||||||
|
var (
|
||||||
|
stderr string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
_, stderr, err = util.ExecCommand(nv.ctx, "ceph", cmd...)
|
||||||
|
// in case of an invalid command, fallback to the next one
|
||||||
|
if strings.Contains(stderr, "Error EINVAL: invalid command") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, either no error, or an unexpected error
|
||||||
|
// happened. There is no need to retry an other command.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return stderr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// createExportCommand returns the "ceph nfs export create ..." command
|
||||||
|
// arguments (without "ceph"). The order of the parameters matches old Ceph
|
||||||
|
// releases, new Ceph releases added --option formats, which can be added when
|
||||||
|
// passing the parameters to this function.
|
||||||
|
func (nv *NFSVolume) createExportCommand(nfsCluster, fs, export, path string) []string {
|
||||||
|
return []string{
|
||||||
|
"--id", nv.cr.ID,
|
||||||
|
"--keyfile=" + nv.cr.KeyFile,
|
||||||
|
"-m", nv.mons,
|
||||||
|
"nfs",
|
||||||
|
"export",
|
||||||
|
"create",
|
||||||
|
"cephfs",
|
||||||
|
fs,
|
||||||
|
nfsCluster,
|
||||||
|
export,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteExport removes the NFS-export from the Ceph managed NFS-server.
|
||||||
|
func (nv *NFSVolume) DeleteExport() error {
|
||||||
|
if !nv.connected {
|
||||||
|
return fmt.Errorf("can not delete export for %q: not connected", nv)
|
||||||
|
}
|
||||||
|
|
||||||
|
nfsCluster, err := nv.getNFSCluster()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to identify NFS cluster: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use new go-ceph API, see ceph/ceph-csi#2977
|
||||||
|
// new versions of Ceph use a different command, and the go-ceph API
|
||||||
|
// also seems to be different :-/
|
||||||
|
//
|
||||||
|
// run the new command, but fall back to the previous one in case of an
|
||||||
|
// error
|
||||||
|
cmds := [][]string{
|
||||||
|
// ceph nfs export rm <cluster_id> <pseudo_path>
|
||||||
|
nv.deleteExportCommand("rm", nfsCluster),
|
||||||
|
// ceph nfs export delete <cluster_id> <pseudo_path>
|
||||||
|
nv.deleteExportCommand("delete", nfsCluster),
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr, err := nv.retryIfInvalid(cmds)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete export %q from NFS-cluster"+
|
||||||
|
"%q (%v): %s", nv, nfsCluster, err, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteExportCommand returns the "ceph nfs export delete ..." command
|
||||||
|
// arguments (without "ceph"). Old releases of Ceph expect "delete" as cmd,
|
||||||
|
// newer releases use "rm".
|
||||||
|
func (nv *NFSVolume) deleteExportCommand(cmd, nfsCluster string) []string {
|
||||||
|
return []string{
|
||||||
|
"--id", nv.cr.ID,
|
||||||
|
"--keyfile=" + nv.cr.KeyFile,
|
||||||
|
"-m", nv.mons,
|
||||||
|
"nfs",
|
||||||
|
"export",
|
||||||
|
cmd,
|
||||||
|
nfsCluster,
|
||||||
|
nv.GetExportPath(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNFSCluster fetches the NFS-cluster name from the CephFS journal.
|
||||||
|
func (nv *NFSVolume) getNFSCluster() (string, error) {
|
||||||
|
if !nv.connected {
|
||||||
|
return "", fmt.Errorf("can not get NFS-cluster for %q: not connected", nv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := fscore.NewFileSystem(nv.conn)
|
||||||
|
fsName, err := fs.GetFsName(nv.ctx, nv.fscID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get filesystem name for ID %x: %w", nv.fscID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mdPool, err := fs.GetMetadataPool(nv.ctx, fsName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get metadata pool for %q: %w", fsName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to cephfs' default radosNamespace (csi)
|
||||||
|
j, err := store.VolJournal.Connect(nv.mons, fsutil.RadosNamespace, nv.cr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to connect to journal: %w", err)
|
||||||
|
}
|
||||||
|
defer j.Destroy()
|
||||||
|
|
||||||
|
clusterName, err := j.FetchAttribute(nv.ctx, mdPool, nv.objectUUID, clusterNameKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get cluster name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusterName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setNFSCluster stores the NFS-cluster name in the CephFS journal.
|
||||||
|
func (nv *NFSVolume) setNFSCluster(clusterName string) error {
|
||||||
|
if !nv.connected {
|
||||||
|
return fmt.Errorf("can not set NFS-cluster for %q: not connected", nv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := fscore.NewFileSystem(nv.conn)
|
||||||
|
fsName, err := fs.GetFsName(nv.ctx, nv.fscID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get filesystem name for ID %x: %w", nv.fscID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mdPool, err := fs.GetMetadataPool(nv.ctx, fsName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get metadata pool for %q: %w", fsName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to cephfs' default radosNamespace (csi)
|
||||||
|
j, err := store.VolJournal.Connect(nv.mons, fsutil.RadosNamespace, nv.cr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to journal: %w", err)
|
||||||
|
}
|
||||||
|
defer j.Destroy()
|
||||||
|
|
||||||
|
err = j.StoreAttribute(nv.ctx, mdPool, nv.objectUUID, clusterNameKey, clusterName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to store cluster name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
77
internal/nfs/driver/driver.go
Normal file
77
internal/nfs/driver/driver.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
||||||
|
"github.com/ceph/ceph-csi/internal/nfs/controller"
|
||||||
|
"github.com/ceph/ceph-csi/internal/nfs/identity"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/log"
|
||||||
|
|
||||||
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Driver contains the default identity and controller struct.
|
||||||
|
type Driver struct{}
|
||||||
|
|
||||||
|
// NewDriver returns new ceph driver.
|
||||||
|
func NewDriver() *Driver {
|
||||||
|
return &Driver{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run start a non-blocking grpc controller,node and identityserver for
|
||||||
|
// ceph CSI driver which can serve multiple parallel requests.
|
||||||
|
func (fs *Driver) Run(conf *util.Config) {
|
||||||
|
// Initialize default library driver
|
||||||
|
cd := csicommon.NewCSIDriver(conf.DriverName, util.DriverVersion, conf.NodeID)
|
||||||
|
if cd == nil {
|
||||||
|
log.FatalLogMsg("failed to initialize CSI driver")
|
||||||
|
}
|
||||||
|
|
||||||
|
cd.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
|
||||||
|
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
|
||||||
|
csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
|
||||||
|
})
|
||||||
|
// VolumeCapabilities are validated by the CephFS Controller
|
||||||
|
cd.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
|
||||||
|
csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
|
||||||
|
csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
|
||||||
|
csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
|
||||||
|
csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create gRPC servers
|
||||||
|
server := csicommon.NewNonBlockingGRPCServer()
|
||||||
|
srv := csicommon.Servers{
|
||||||
|
IS: identity.NewIdentityServer(cd),
|
||||||
|
CS: controller.NewControllerServer(cd),
|
||||||
|
}
|
||||||
|
server.Start(conf.Endpoint, conf.HistogramOption, srv, conf.EnableGRPCMetrics)
|
||||||
|
if conf.EnableGRPCMetrics {
|
||||||
|
log.WarningLogMsg("EnableGRPCMetrics is deprecated")
|
||||||
|
go util.StartMetricsServer(conf)
|
||||||
|
}
|
||||||
|
if conf.EnableProfiling {
|
||||||
|
if !conf.EnableGRPCMetrics {
|
||||||
|
go util.StartMetricsServer(conf)
|
||||||
|
}
|
||||||
|
log.DebugLogMsg("Registering profiling handler")
|
||||||
|
go util.EnableProfiling()
|
||||||
|
}
|
||||||
|
server.Wait()
|
||||||
|
}
|
55
internal/nfs/identity/identityserver.go
Normal file
55
internal/nfs/identity/identityserver.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 identity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
||||||
|
|
||||||
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server struct of ceph CSI driver with supported methods of CSI identity
|
||||||
|
// server spec.
|
||||||
|
type Server struct {
|
||||||
|
*csicommon.DefaultIdentityServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentityServer initialize a identity server for ceph CSI driver.
|
||||||
|
func NewIdentityServer(d *csicommon.CSIDriver) *Server {
|
||||||
|
return &Server{
|
||||||
|
DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPluginCapabilities returns available capabilities of the ceph driver.
|
||||||
|
func (is *Server) GetPluginCapabilities(
|
||||||
|
ctx context.Context,
|
||||||
|
req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
|
||||||
|
return &csi.GetPluginCapabilitiesResponse{
|
||||||
|
Capabilities: []*csi.PluginCapability{
|
||||||
|
{
|
||||||
|
Type: &csi.PluginCapability_Service_{
|
||||||
|
Service: &csi.PluginCapability_Service{
|
||||||
|
Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
@ -56,23 +56,12 @@ func (rv *rbdVolume) checkCloneImage(ctx context.Context, parentVol *rbdVolume)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, ErrSnapNotFound):
|
case errors.Is(err, ErrSnapNotFound):
|
||||||
// check temporary image needs flatten, if yes add task to flatten the
|
|
||||||
// temporary clone
|
|
||||||
err = tempClone.flattenRbdImage(ctx, false, rbdHardMaxCloneDepth, rbdSoftMaxCloneDepth)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
// as the snapshot is not present, create new snapshot,clone and
|
// as the snapshot is not present, create new snapshot,clone and
|
||||||
// delete the temporary snapshot
|
// delete the temporary snapshot
|
||||||
err = createRBDClone(ctx, tempClone, rv, snap)
|
err = createRBDClone(ctx, tempClone, rv, snap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
// check image needs flatten, if yes add task to flatten the clone
|
|
||||||
err = rv.flattenRbdImage(ctx, false, rbdHardMaxCloneDepth, rbdSoftMaxCloneDepth)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
||||||
@ -114,11 +103,6 @@ func (rv *rbdVolume) checkCloneImage(ctx context.Context, parentVol *rbdVolume)
|
|||||||
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
// check image needs flatten, if yes add task to flatten the clone
|
|
||||||
err = rv.flattenRbdImage(ctx, false, rbdHardMaxCloneDepth, rbdSoftMaxCloneDepth)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@ -186,10 +170,7 @@ func (rv *rbdVolume) createCloneFromImage(ctx context.Context, parentVol *rbdVol
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rbdVolume) doSnapClone(ctx context.Context, parentVol *rbdVolume) error {
|
func (rv *rbdVolume) doSnapClone(ctx context.Context, parentVol *rbdVolume) error {
|
||||||
var (
|
var errClone error
|
||||||
errClone error
|
|
||||||
errFlatten error
|
|
||||||
)
|
|
||||||
|
|
||||||
// generate temp cloned volume
|
// generate temp cloned volume
|
||||||
tempClone := rv.generateTempClone()
|
tempClone := rv.generateTempClone()
|
||||||
@ -218,62 +199,22 @@ func (rv *rbdVolume) doSnapClone(ctx context.Context, parentVol *rbdVolume) erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil || errFlatten != nil {
|
if err != nil {
|
||||||
if !errors.Is(errFlatten, ErrFlattenInProgress) {
|
// cleanup snapshot
|
||||||
// cleanup snapshot
|
cErr := cleanUpSnapshot(ctx, parentVol, tempSnap, tempClone)
|
||||||
cErr := cleanUpSnapshot(ctx, parentVol, tempSnap, tempClone)
|
if cErr != nil {
|
||||||
if cErr != nil {
|
log.ErrorLog(ctx, "failed to cleanup image %s or snapshot %s: %v", tempClone, tempSnap, cErr)
|
||||||
log.ErrorLog(ctx, "failed to cleanup image %s or snapshot %s: %v", tempSnap, tempClone, cErr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// flatten clone
|
|
||||||
errFlatten = tempClone.flattenRbdImage(ctx, false, rbdHardMaxCloneDepth, rbdSoftMaxCloneDepth)
|
|
||||||
if errFlatten != nil {
|
|
||||||
return errFlatten
|
|
||||||
}
|
|
||||||
|
|
||||||
// create snap of temp clone from temporary cloned image
|
// create snap of temp clone from temporary cloned image
|
||||||
// create final clone
|
// create final clone
|
||||||
// delete snap of temp clone
|
// delete snap of temp clone
|
||||||
errClone = createRBDClone(ctx, tempClone, rv, cloneSnap)
|
errClone = createRBDClone(ctx, tempClone, rv, cloneSnap)
|
||||||
if errClone != nil {
|
if errClone != nil {
|
||||||
// set errFlatten error to cleanup temporary snapshot and temporary clone
|
|
||||||
errFlatten = errors.New("failed to create user requested cloned image")
|
|
||||||
|
|
||||||
return errClone
|
return errClone
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rv *rbdVolume) flattenCloneImage(ctx context.Context) error {
|
|
||||||
tempClone := rv.generateTempClone()
|
|
||||||
// reducing the limit for cloned images to make sure the limit is in range,
|
|
||||||
// If the intermediate clone reaches the depth we may need to return ABORT
|
|
||||||
// error message as it need to be flatten before continuing, this may leak
|
|
||||||
// omap entries and stale temporary snapshots in corner cases, if we reduce
|
|
||||||
// the limit and check for the depth of the parent image clain itself we
|
|
||||||
// can flatten the parent images before used to avoid the stale omap entries.
|
|
||||||
hardLimit := rbdHardMaxCloneDepth
|
|
||||||
softLimit := rbdSoftMaxCloneDepth
|
|
||||||
// choosing 2 so that we don't need to flatten the image in the request.
|
|
||||||
const depthToAvoidFlatten = 2
|
|
||||||
if rbdHardMaxCloneDepth > depthToAvoidFlatten {
|
|
||||||
hardLimit = rbdHardMaxCloneDepth - depthToAvoidFlatten
|
|
||||||
}
|
|
||||||
if rbdSoftMaxCloneDepth > depthToAvoidFlatten {
|
|
||||||
softLimit = rbdSoftMaxCloneDepth - depthToAvoidFlatten
|
|
||||||
}
|
|
||||||
err := tempClone.getImageInfo()
|
|
||||||
if err == nil {
|
|
||||||
return tempClone.flattenRbdImage(ctx, false, hardLimit, softLimit)
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrImageNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv.flattenRbdImage(ctx, false, hardLimit, softLimit)
|
|
||||||
}
|
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
csicommon "github.com/ceph/ceph-csi/internal/csi-common"
|
||||||
"github.com/ceph/ceph-csi/internal/util"
|
"github.com/ceph/ceph-csi/internal/util"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/k8s"
|
||||||
"github.com/ceph/ceph-csi/internal/util/log"
|
"github.com/ceph/ceph-csi/internal/util/log"
|
||||||
|
|
||||||
librbd "github.com/ceph/go-ceph/rbd"
|
librbd "github.com/ceph/go-ceph/rbd"
|
||||||
@ -123,13 +124,27 @@ func (cs *ControllerServer) parseVolCreateRequest(
|
|||||||
rbdVol, err := genVolFromVolumeOptions(
|
rbdVol, err := genVolFromVolumeOptions(
|
||||||
ctx,
|
ctx,
|
||||||
req.GetParameters(),
|
req.GetParameters(),
|
||||||
req.GetSecrets(),
|
|
||||||
isMultiWriter && isBlock,
|
isMultiWriter && isBlock,
|
||||||
false)
|
false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the KMS is of type VaultToken, additional metadata is needed
|
||||||
|
// depending on the tenant, the KMS can be configured with other
|
||||||
|
// options
|
||||||
|
// FIXME: this works only on Kubernetes, how do other CO supply metadata?
|
||||||
|
// namespace is derived from the `csi.storage.k8s.io/pvc/namespace`
|
||||||
|
// parameter.
|
||||||
|
|
||||||
|
// get the owner of the PVC which is required for few encryption related operations
|
||||||
|
rbdVol.Owner = k8s.GetOwner(req.GetParameters())
|
||||||
|
|
||||||
|
err = rbdVol.initKMS(ctx, req.GetParameters(), req.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
rbdVol.RequestName = req.GetName()
|
rbdVol.RequestName = req.GetName()
|
||||||
|
|
||||||
// Volume Size - Default is 1 GiB
|
// Volume Size - Default is 1 GiB
|
||||||
@ -159,7 +174,8 @@ func (cs *ControllerServer) parseVolCreateRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildCreateVolumeResponse(req *csi.CreateVolumeRequest, rbdVol *rbdVolume) *csi.CreateVolumeResponse {
|
func buildCreateVolumeResponse(req *csi.CreateVolumeRequest, rbdVol *rbdVolume) *csi.CreateVolumeResponse {
|
||||||
volumeContext := req.GetParameters()
|
// remove kubernetes csi prefixed parameters.
|
||||||
|
volumeContext := k8s.RemoveCSIPrefixedParameters(req.GetParameters())
|
||||||
volumeContext["pool"] = rbdVol.Pool
|
volumeContext["pool"] = rbdVol.Pool
|
||||||
volumeContext["journalPool"] = rbdVol.JournalPool
|
volumeContext["journalPool"] = rbdVol.JournalPool
|
||||||
volumeContext["imageName"] = rbdVol.RbdImageName
|
volumeContext["imageName"] = rbdVol.RbdImageName
|
||||||
@ -286,7 +302,7 @@ func (cs *ControllerServer) CreateVolume(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = flattenParentImage(ctx, parentVol, cr)
|
err = flattenParentImage(ctx, parentVol, rbdSnap, cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -297,11 +313,9 @@ func (cs *ControllerServer) CreateVolume(
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, ErrFlattenInProgress) {
|
errDefer := undoVolReservation(ctx, rbdVol, cr)
|
||||||
errDefer := undoVolReservation(ctx, rbdVol, cr)
|
if errDefer != nil {
|
||||||
if errDefer != nil {
|
log.WarningLog(ctx, "failed undoing reservation of volume: %s (%s)", req.GetName(), errDefer)
|
||||||
log.WarningLog(ctx, "failed undoing reservation of volume: %s (%s)", req.GetName(), errDefer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -318,12 +332,38 @@ func (cs *ControllerServer) CreateVolume(
|
|||||||
return buildCreateVolumeResponse(req, rbdVol), nil
|
return buildCreateVolumeResponse(req, rbdVol), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenParentImage(ctx context.Context, rbdVol *rbdVolume, cr *util.Credentials) error {
|
// flattenParentImage is to be called before proceeding with creating volume,
|
||||||
|
// with datasource. This function flattens the parent image accordingly to
|
||||||
|
// make sure no flattening is required during or after the new volume creation.
|
||||||
|
// For parent volume, it's parent(temp clone or snapshot) is flattened.
|
||||||
|
// For parent snapshot, the snapshot itself is flattened.
|
||||||
|
func flattenParentImage(
|
||||||
|
ctx context.Context,
|
||||||
|
rbdVol *rbdVolume,
|
||||||
|
rbdSnap *rbdSnapshot,
|
||||||
|
cr *util.Credentials) error {
|
||||||
|
// flatten the image's parent before the reservation to avoid
|
||||||
|
// stale entries in post creation if we return ABORT error and the
|
||||||
|
// DeleteVolume RPC is not called.
|
||||||
|
// reducing the limit for cloned images to make sure the limit is in range,
|
||||||
|
// If the intermediate clone reaches the depth we may need to return ABORT
|
||||||
|
// error message as it need to be flatten before continuing, this may leak
|
||||||
|
// omap entries and stale temporary snapshots in corner cases, if we reduce
|
||||||
|
// the limit and check for the depth of the parent image clain itself we
|
||||||
|
// can flatten the parent images before used to avoid the stale omap entries.
|
||||||
|
hardLimit := rbdHardMaxCloneDepth
|
||||||
|
softLimit := rbdSoftMaxCloneDepth
|
||||||
if rbdVol != nil {
|
if rbdVol != nil {
|
||||||
// flatten the image or its parent before the reservation to avoid
|
// choosing 2, since cloning image creates a temp clone and a final clone which
|
||||||
// stale entries in post creation if we return ABORT error and the
|
// will add a total depth of 2.
|
||||||
// delete volume is not called
|
const depthToAvoidFlatten = 2
|
||||||
err := rbdVol.flattenCloneImage(ctx)
|
if rbdHardMaxCloneDepth > depthToAvoidFlatten {
|
||||||
|
hardLimit = rbdHardMaxCloneDepth - depthToAvoidFlatten
|
||||||
|
}
|
||||||
|
if rbdSoftMaxCloneDepth > depthToAvoidFlatten {
|
||||||
|
softLimit = rbdSoftMaxCloneDepth - depthToAvoidFlatten
|
||||||
|
}
|
||||||
|
err := rbdVol.flattenParent(ctx, hardLimit, softLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return getGRPCErrorForCreateVolume(err)
|
return getGRPCErrorForCreateVolume(err)
|
||||||
}
|
}
|
||||||
@ -335,6 +375,32 @@ func flattenParentImage(ctx context.Context, rbdVol *rbdVolume, cr *util.Credent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rbdSnap != nil {
|
||||||
|
err := rbdSnap.Connect(cr)
|
||||||
|
if err != nil {
|
||||||
|
return getGRPCErrorForCreateVolume(err)
|
||||||
|
}
|
||||||
|
// in case of any error call Destroy for cleanup.
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
rbdSnap.Destroy()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// choosing 1, since restore from snapshot adds one depth.
|
||||||
|
const depthToAvoidFlatten = 1
|
||||||
|
if rbdHardMaxCloneDepth > depthToAvoidFlatten {
|
||||||
|
hardLimit = rbdHardMaxCloneDepth - depthToAvoidFlatten
|
||||||
|
}
|
||||||
|
if rbdSoftMaxCloneDepth > depthToAvoidFlatten {
|
||||||
|
softLimit = rbdSoftMaxCloneDepth - depthToAvoidFlatten
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rbdSnap.flattenRbdImage(ctx, false, hardLimit, softLimit)
|
||||||
|
if err != nil {
|
||||||
|
return getGRPCErrorForCreateVolume(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -574,10 +640,8 @@ func (cs *ControllerServer) createBackingImage(
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, ErrFlattenInProgress) {
|
if deleteErr := rbdVol.deleteImage(ctx); deleteErr != nil {
|
||||||
if deleteErr := rbdVol.deleteImage(ctx); deleteErr != nil {
|
log.ErrorLog(ctx, "failed to delete rbd image: %s with error: %v", rbdVol, deleteErr)
|
||||||
log.ErrorLog(ctx, "failed to delete rbd image: %s with error: %v", rbdVol, deleteErr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -586,15 +650,6 @@ func (cs *ControllerServer) createBackingImage(
|
|||||||
return status.Error(codes.Internal, err.Error())
|
return status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if rbdSnap != nil {
|
|
||||||
err = rbdVol.flattenRbdImage(ctx, false, rbdHardMaxCloneDepth, rbdSoftMaxCloneDepth)
|
|
||||||
if err != nil {
|
|
||||||
log.ErrorLog(ctx, "failed to flatten image %s: %v", rbdVol, err)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,22 +265,14 @@ func (ri *rbdImage) initKMS(ctx context.Context, volOptions, credentials map[str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseEncryptionOpts returns kmsID and sets Owner attribute.
|
// ParseEncryptionOpts returns kmsID and sets Owner attribute.
|
||||||
func (ri *rbdImage) ParseEncryptionOpts(ctx context.Context, volOptions map[string]string) (string, error) {
|
func (ri *rbdImage) ParseEncryptionOpts(
|
||||||
|
ctx context.Context,
|
||||||
|
volOptions map[string]string) (string, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
ok bool
|
ok bool
|
||||||
encrypted, kmsID string
|
encrypted, kmsID string
|
||||||
)
|
)
|
||||||
|
|
||||||
// if the KMS is of type VaultToken, additional metadata is needed
|
|
||||||
// depending on the tenant, the KMS can be configured with other
|
|
||||||
// options
|
|
||||||
// FIXME: this works only on Kubernetes, how do other CO supply metadata?
|
|
||||||
ri.Owner, ok = volOptions["csi.storage.k8s.io/pvc/namespace"]
|
|
||||||
if !ok {
|
|
||||||
log.DebugLog(ctx, "could not detect owner for %s", ri)
|
|
||||||
}
|
|
||||||
|
|
||||||
encrypted, ok = volOptions["encrypted"]
|
encrypted, ok = volOptions["encrypted"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -88,8 +88,8 @@ func (ri *rbdImage) promoteImage(force bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// forcePromoteImage promotes image to primary with force option with 1 minute
|
// forcePromoteImage promotes image to primary with force option with 2 minutes
|
||||||
// timeout. If there is no response within 1 minute,the rbd CLI process will be
|
// timeout. If there is no response within 2 minutes,the rbd CLI process will be
|
||||||
// killed and an error is returned.
|
// killed and an error is returned.
|
||||||
func (rv *rbdVolume) forcePromoteImage(cr *util.Credentials) error {
|
func (rv *rbdVolume) forcePromoteImage(cr *util.Credentials) error {
|
||||||
promoteArgs := []string{
|
promoteArgs := []string{
|
||||||
@ -102,7 +102,8 @@ func (rv *rbdVolume) forcePromoteImage(cr *util.Credentials) error {
|
|||||||
}
|
}
|
||||||
_, stderr, err := util.ExecCommandWithTimeout(
|
_, stderr, err := util.ExecCommandWithTimeout(
|
||||||
context.TODO(),
|
context.TODO(),
|
||||||
time.Minute,
|
// 2 minutes timeout as the Replication RPC timeout is 2.5 minutes.
|
||||||
|
2*time.Minute,
|
||||||
"rbd",
|
"rbd",
|
||||||
promoteArgs...,
|
promoteArgs...,
|
||||||
)
|
)
|
||||||
|
@ -147,8 +147,7 @@ func healerStageTransaction(ctx context.Context, cr *util.Credentials, volOps *r
|
|||||||
func populateRbdVol(
|
func populateRbdVol(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *csi.NodeStageVolumeRequest,
|
req *csi.NodeStageVolumeRequest,
|
||||||
cr *util.Credentials,
|
cr *util.Credentials) (*rbdVolume, error) {
|
||||||
secrets map[string]string) (*rbdVolume, error) {
|
|
||||||
var err error
|
var err error
|
||||||
var j *journal.Connection
|
var j *journal.Connection
|
||||||
volID := req.GetVolumeId()
|
volID := req.GetVolumeId()
|
||||||
@ -173,7 +172,7 @@ func populateRbdVol(
|
|||||||
disableInUseChecks = true
|
disableInUseChecks = true
|
||||||
}
|
}
|
||||||
|
|
||||||
rv, err := genVolFromVolumeOptions(ctx, req.GetVolumeContext(), secrets, disableInUseChecks, true)
|
rv, err := genVolFromVolumeOptions(ctx, req.GetVolumeContext(), disableInUseChecks, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
@ -213,6 +212,8 @@ func populateRbdVol(
|
|||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
rv.RbdImageName = imageAttributes.ImageName
|
rv.RbdImageName = imageAttributes.ImageName
|
||||||
|
// set owner after extracting the owner name from the journal
|
||||||
|
rv.Owner = imageAttributes.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rv.Connect(cr)
|
err = rv.Connect(cr)
|
||||||
@ -235,6 +236,11 @@ func populateRbdVol(
|
|||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = rv.initKMS(ctx, req.GetVolumeContext(), req.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if req.GetVolumeContext()["mounter"] == rbdDefaultMounter &&
|
if req.GetVolumeContext()["mounter"] == rbdDefaultMounter &&
|
||||||
!isKrbdFeatureSupported(ctx, strings.Join(rv.ImageFeatureSet.Names(), ",")) {
|
!isKrbdFeatureSupported(ctx, strings.Join(rv.ImageFeatureSet.Names(), ",")) {
|
||||||
if !parseBoolOption(ctx, req.GetVolumeContext(), tryOtherMounters, false) {
|
if !parseBoolOption(ctx, req.GetVolumeContext(), tryOtherMounters, false) {
|
||||||
@ -320,7 +326,7 @@ func (ns *NodeServer) NodeStageVolume(
|
|||||||
}
|
}
|
||||||
|
|
||||||
isStaticVol := parseBoolOption(ctx, req.GetVolumeContext(), staticVol, false)
|
isStaticVol := parseBoolOption(ctx, req.GetVolumeContext(), staticVol, false)
|
||||||
rv, err := populateRbdVol(ctx, req, cr, req.GetSecrets())
|
rv, err := populateRbdVol(ctx, req, cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -228,21 +228,19 @@ func setRbdNbdToolFeatures() {
|
|||||||
// returns mounter specific options.
|
// returns mounter specific options.
|
||||||
func parseMapOptions(mapOptions string) (string, string, error) {
|
func parseMapOptions(mapOptions string) (string, string, error) {
|
||||||
var krbdMapOptions, nbdMapOptions string
|
var krbdMapOptions, nbdMapOptions string
|
||||||
const (
|
|
||||||
noKeyLength = 1
|
|
||||||
validLength = 2
|
|
||||||
)
|
|
||||||
for _, item := range strings.Split(mapOptions, ";") {
|
for _, item := range strings.Split(mapOptions, ";") {
|
||||||
var mounter, options string
|
var mounter, options string
|
||||||
if item == "" {
|
if item == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s := strings.Split(item, ":")
|
s := strings.SplitN(item, ":", 2)
|
||||||
switch len(s) {
|
if len(s) == 1 {
|
||||||
case noKeyLength:
|
|
||||||
options = strings.TrimSpace(s[0])
|
options = strings.TrimSpace(s[0])
|
||||||
krbdMapOptions = options
|
krbdMapOptions = options
|
||||||
case validLength:
|
} else {
|
||||||
|
// options might also contain values delimited with ":", in this
|
||||||
|
// case mounter type MUST be specified.
|
||||||
|
// ex: krbd:read_from_replica=localize,crush_location=zone:zone1;
|
||||||
mounter = strings.TrimSpace(s[0])
|
mounter = strings.TrimSpace(s[0])
|
||||||
options = strings.TrimSpace(s[1])
|
options = strings.TrimSpace(s[1])
|
||||||
switch strings.ToLower(mounter) {
|
switch strings.ToLower(mounter) {
|
||||||
@ -251,10 +249,8 @@ func parseMapOptions(mapOptions string) (string, string, error) {
|
|||||||
case accessTypeNbd:
|
case accessTypeNbd:
|
||||||
nbdMapOptions = options
|
nbdMapOptions = options
|
||||||
default:
|
default:
|
||||||
return "", "", fmt.Errorf("unknown mounter type: %q", mounter)
|
return "", "", fmt.Errorf("unknown mounter type: %q, please specify mounter type", mounter)
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return "", "", fmt.Errorf("badly formatted map/unmap options: %q", mapOptions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,18 +60,25 @@ func TestParseMapOptions(t *testing.T) {
|
|||||||
expectErr: "",
|
expectErr: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unknown mounter used",
|
name: "with `:` delimiter used with in the options",
|
||||||
mapOption: "xyz:xOp1,xOp2",
|
mapOption: "krbd:kOp1,kOp2=kOp21:kOp22;nbd:nOp1,nOp2=nOp21:nOp22",
|
||||||
|
expectKrbdOptions: "kOp1,kOp2=kOp21:kOp22",
|
||||||
|
expectNbdOptions: "nOp1,nOp2=nOp21:nOp22",
|
||||||
|
expectErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with `:` delimiter used with in the options, without mounter label",
|
||||||
|
mapOption: "kOp1,kOp2=kOp21:kOp22;nbd:nOp1,nOp2",
|
||||||
expectKrbdOptions: "",
|
expectKrbdOptions: "",
|
||||||
expectNbdOptions: "",
|
expectNbdOptions: "",
|
||||||
expectErr: "unknown mounter type",
|
expectErr: "unknown mounter type",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad formatted options",
|
name: "unknown mounter used",
|
||||||
mapOption: "nbd:nOp1:nOp2;",
|
mapOption: "xyz:xOp1,xOp2",
|
||||||
expectKrbdOptions: "",
|
expectKrbdOptions: "",
|
||||||
expectNbdOptions: "",
|
expectNbdOptions: "",
|
||||||
expectErr: "badly formatted map/unmap options",
|
expectErr: "unknown mounter type",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -267,7 +267,7 @@ func (rv *rbdVolume) Exists(ctx context.Context, parentVol *rbdVolume) (bool, er
|
|||||||
rv.RbdImageName = imageData.ImageAttributes.ImageName
|
rv.RbdImageName = imageData.ImageAttributes.ImageName
|
||||||
rv.ImageID = imageData.ImageAttributes.ImageID
|
rv.ImageID = imageData.ImageAttributes.ImageID
|
||||||
// check if topology constraints match what is found
|
// check if topology constraints match what is found
|
||||||
rv.Topology, err = util.MatchTopologyForPool(rv.TopologyPools, rv.TopologyRequirement,
|
_, _, rv.Topology, err = util.MatchPoolAndTopology(rv.TopologyPools, rv.TopologyRequirement,
|
||||||
imageData.ImagePool)
|
imageData.ImagePool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO check if need any undo operation here, or ErrVolNameConflict
|
// TODO check if need any undo operation here, or ErrVolNameConflict
|
||||||
@ -303,7 +303,6 @@ func (rv *rbdVolume) Exists(ctx context.Context, parentVol *rbdVolume) (bool, er
|
|||||||
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
// TODO: check image needs flattening and completed?
|
|
||||||
|
|
||||||
err = rv.repairImageID(ctx, j, false)
|
err = rv.repairImageID(ctx, j, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -413,7 +412,10 @@ func updateTopologyConstraints(rbdVol *rbdVolume, rbdSnap *rbdSnapshot) error {
|
|||||||
var err error
|
var err error
|
||||||
if rbdSnap != nil {
|
if rbdSnap != nil {
|
||||||
// check if topology constraints matches snapshot pool
|
// check if topology constraints matches snapshot pool
|
||||||
rbdVol.Topology, err = util.MatchTopologyForPool(rbdVol.TopologyPools,
|
var poolName string
|
||||||
|
var dataPoolName string
|
||||||
|
|
||||||
|
poolName, dataPoolName, rbdVol.Topology, err = util.MatchPoolAndTopology(rbdVol.TopologyPools,
|
||||||
rbdVol.TopologyRequirement, rbdSnap.Pool)
|
rbdVol.TopologyRequirement, rbdSnap.Pool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -421,7 +423,8 @@ func updateTopologyConstraints(rbdVol *rbdVolume, rbdSnap *rbdSnapshot) error {
|
|||||||
|
|
||||||
// update Pool, if it was topology constrained
|
// update Pool, if it was topology constrained
|
||||||
if rbdVol.Topology != nil {
|
if rbdVol.Topology != nil {
|
||||||
rbdVol.Pool = rbdSnap.Pool
|
rbdVol.Pool = poolName
|
||||||
|
rbdVol.DataPool = dataPoolName
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -532,7 +535,7 @@ func undoVolReservation(ctx context.Context, rbdVol *rbdVolume, cr *util.Credent
|
|||||||
// which are not same across clusters.
|
// which are not same across clusters.
|
||||||
func RegenerateJournal(
|
func RegenerateJournal(
|
||||||
volumeAttributes map[string]string,
|
volumeAttributes map[string]string,
|
||||||
volumeID, requestName string,
|
volumeID, requestName, owner string,
|
||||||
cr *util.Credentials) (string, error) {
|
cr *util.Credentials) (string, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
var (
|
var (
|
||||||
@ -552,6 +555,8 @@ func RegenerateJournal(
|
|||||||
ErrInvalidVolID, err, rbdVol.VolID)
|
ErrInvalidVolID, err, rbdVol.VolID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rbdVol.Owner = owner
|
||||||
|
|
||||||
kmsID, err = rbdVol.ParseEncryptionOpts(ctx, volumeAttributes)
|
kmsID, err = rbdVol.ParseEncryptionOpts(ctx, volumeAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -1142,7 +1142,7 @@ func generateVolumeFromMapping(
|
|||||||
|
|
||||||
func genVolFromVolumeOptions(
|
func genVolFromVolumeOptions(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
volOptions, credentials map[string]string,
|
volOptions map[string]string,
|
||||||
disableInUseChecks, checkClusterIDMapping bool) (*rbdVolume, error) {
|
disableInUseChecks, checkClusterIDMapping bool) (*rbdVolume, error) {
|
||||||
var (
|
var (
|
||||||
ok bool
|
ok bool
|
||||||
@ -1195,11 +1195,6 @@ func genVolFromVolumeOptions(
|
|||||||
rbdVol.Mounter)
|
rbdVol.Mounter)
|
||||||
rbdVol.DisableInUseChecks = disableInUseChecks
|
rbdVol.DisableInUseChecks = disableInUseChecks
|
||||||
|
|
||||||
err = rbdVol.initKMS(ctx, volOptions, credentials)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rbdVol, nil
|
return rbdVol, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1440,6 +1435,47 @@ func (ri *rbdImage) getImageInfo() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getParent returns parent image if it exists.
|
||||||
|
func (ri *rbdImage) getParent() (*rbdImage, error) {
|
||||||
|
err := ri.getImageInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ri.ParentName == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parentImage := rbdImage{}
|
||||||
|
parentImage.conn = ri.conn.Copy()
|
||||||
|
parentImage.ClusterID = ri.ClusterID
|
||||||
|
parentImage.Monitors = ri.Monitors
|
||||||
|
parentImage.Pool = ri.ParentPool
|
||||||
|
parentImage.RadosNamespace = ri.RadosNamespace
|
||||||
|
parentImage.RbdImageName = ri.ParentName
|
||||||
|
|
||||||
|
err = parentImage.getImageInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &parentImage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// flattenParent flatten the given image's parent if it exists according to hard and soft
|
||||||
|
// limits.
|
||||||
|
func (ri *rbdImage) flattenParent(ctx context.Context, hardLimit, softLimit uint) error {
|
||||||
|
parentImage, err := ri.getParent()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentImage == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentImage.flattenRbdImage(ctx, false, hardLimit, softLimit)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
checkSnapExists queries rbd about the snapshots of the given image and returns
|
checkSnapExists queries rbd about the snapshots of the given image and returns
|
||||||
ErrImageNotFound if provided image is not found, and ErrSnapNotFound if
|
ErrImageNotFound if provided image is not found, and ErrSnapNotFound if
|
||||||
|
45
internal/util/k8s/parameters.go
Normal file
45
internal/util/k8s/parameters.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CSI Parameters prefixed with csiParameterPrefix are passed through
|
||||||
|
// to the driver on CreateVolumeRequest/CreateSnapshotRequest calls.
|
||||||
|
const (
|
||||||
|
csiParameterPrefix = "csi.storage.k8s.io/"
|
||||||
|
pvcNamespaceKey = "csi.storage.k8s.io/pvc/namespace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemoveCSIPrefixedParameters removes parameters prefixed with csiParameterPrefix.
|
||||||
|
func RemoveCSIPrefixedParameters(param map[string]string) map[string]string {
|
||||||
|
newParam := map[string]string{}
|
||||||
|
for k, v := range param {
|
||||||
|
if !strings.HasPrefix(k, csiParameterPrefix) {
|
||||||
|
// add the parameter to the new map if its not having the prefix
|
||||||
|
newParam[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newParam
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwner returns the pvc namespace name from the parameter.
|
||||||
|
func GetOwner(param map[string]string) string {
|
||||||
|
return param[pvcNamespaceKey]
|
||||||
|
}
|
95
internal/util/k8s/parameters_test.go
Normal file
95
internal/util/k8s/parameters_test.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveCSIPrefixedParameters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
param map[string]string
|
||||||
|
want map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "without csi.storage.k8s.io prefix",
|
||||||
|
param: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
want: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with csi.storage.k8s.io prefix",
|
||||||
|
param: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"csi.storage.k8s.io/pvc/name": "foo",
|
||||||
|
"csi.storage.k8s.io/pvc/namespace": "bar",
|
||||||
|
"csi.storage.k8s.io/pv/name": "baz",
|
||||||
|
},
|
||||||
|
want: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
ts := tt
|
||||||
|
t.Run(ts.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
got := RemoveCSIPrefixedParameters(ts.param)
|
||||||
|
if !reflect.DeepEqual(got, ts.want) {
|
||||||
|
t.Errorf("RemoveCSIPrefixedParameters() = %v, want %v", got, ts.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOwner(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args map[string]string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "namespace is not present in the parameters",
|
||||||
|
args: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace is present in the parameters",
|
||||||
|
args: map[string]string{
|
||||||
|
"csi.storage.k8s.io/pvc/namespace": "bar",
|
||||||
|
},
|
||||||
|
want: "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
ts := tt
|
||||||
|
t.Run(ts.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := GetOwner(ts.args); got != ts.want {
|
||||||
|
t.Errorf("GetOwner() = %v, want %v", got, ts.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
85
internal/util/reftracker/errors/errors.go
Normal file
85
internal/util/reftracker/errors/errors.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
goerrors "errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ceph/go-ceph/rados"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrObjectOutOfDate is an error returned by RADOS read/write ops whose
|
||||||
|
// rados_*_op_assert_version failed.
|
||||||
|
var ErrObjectOutOfDate = goerrors.New("object is out of date since the last time it was read, try again later")
|
||||||
|
|
||||||
|
// UnexpectedReadSize formats an error message for a failure due to bad read
|
||||||
|
// size.
|
||||||
|
func UnexpectedReadSize(expectedBytes, actualBytes int) error {
|
||||||
|
return fmt.Errorf("unexpected size read: expected %d bytes, got %d",
|
||||||
|
expectedBytes, actualBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnknownObjectVersion formats an error message for a failure due to unknown
|
||||||
|
// reftracker object version.
|
||||||
|
func UnknownObjectVersion(unknownVersion uint32) error {
|
||||||
|
return fmt.Errorf("unknown reftracker version %d", unknownVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailedObjectRead formats an error message for a failed RADOS read op.
|
||||||
|
func FailedObjectRead(cause error) error {
|
||||||
|
if cause != nil {
|
||||||
|
return fmt.Errorf("failed to read object: %w", TryRADOSAborted(cause))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailedObjectRead formats an error message for a failed RADOS read op.
|
||||||
|
func FailedObjectWrite(cause error) error {
|
||||||
|
if cause != nil {
|
||||||
|
return fmt.Errorf("failed to write object: %w", TryRADOSAborted(cause))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TryRADOSAborted tries to extract rados_*_op_assert_version from opErr.
|
||||||
|
func TryRADOSAborted(opErr error) error {
|
||||||
|
if opErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var radosOpErr rados.OperationError
|
||||||
|
if !goerrors.As(opErr, &radosOpErr) {
|
||||||
|
return opErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:errorlint // Can't use errors.As() because rados.radosError is private.
|
||||||
|
errnoErr, ok := radosOpErr.OpError.(interface{ ErrorCode() int })
|
||||||
|
if !ok {
|
||||||
|
return opErr
|
||||||
|
}
|
||||||
|
|
||||||
|
errno := errnoErr.ErrorCode()
|
||||||
|
if errno == -int(unix.EOVERFLOW) || errno == -int(unix.ERANGE) {
|
||||||
|
return ErrObjectOutOfDate
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
551
internal/util/reftracker/radoswrapper/fakerados.go
Normal file
551
internal/util/reftracker/radoswrapper/fakerados.go
Normal file
@ -0,0 +1,551 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 radoswrapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ceph/go-ceph/rados"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
FakeObj struct {
|
||||||
|
Oid string
|
||||||
|
Ver uint64
|
||||||
|
Xattrs map[string][]byte
|
||||||
|
Omap map[string][]byte
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
FakeRados struct {
|
||||||
|
Objs map[string]*FakeObj
|
||||||
|
}
|
||||||
|
|
||||||
|
FakeIOContext struct {
|
||||||
|
LastObjVersion uint64
|
||||||
|
Rados *FakeRados
|
||||||
|
}
|
||||||
|
|
||||||
|
FakeWriteOp struct {
|
||||||
|
IoCtx *FakeIOContext
|
||||||
|
|
||||||
|
steps map[fakeWriteOpStepExecutorIdx]fakeWriteOpStepExecutor
|
||||||
|
oid string
|
||||||
|
}
|
||||||
|
|
||||||
|
FakeReadOp struct {
|
||||||
|
IoCtx *FakeIOContext
|
||||||
|
|
||||||
|
steps map[fakeReadOpStepExecutorIdx]fakeReadOpStepExecutor
|
||||||
|
oid string
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeWriteOpStepExecutorIdx int
|
||||||
|
fakeReadOpStepExecutorIdx int
|
||||||
|
|
||||||
|
fakeWriteOpStepExecutor interface {
|
||||||
|
operate(w *FakeWriteOp) error
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeReadOpStepExecutor interface {
|
||||||
|
operate(r *FakeReadOp) error
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeRadosError int
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fakeWriteOpAssertVersionExecutorIdx fakeWriteOpStepExecutorIdx = iota
|
||||||
|
fakeWriteOpRemoveExecutorIdx
|
||||||
|
fakeWriteOpCreateExecutorIdx
|
||||||
|
fakeWriteOpSetXattrExecutorIdx
|
||||||
|
fakeWriteOpWriteFullExecutorIdx
|
||||||
|
fakeWriteOpRmOmapKeysExecutorIdx
|
||||||
|
fakeWriteOpSetOmapExecutorIdx
|
||||||
|
|
||||||
|
fakeReadOpAssertVersionExecutorIdx fakeReadOpStepExecutorIdx = iota
|
||||||
|
fakeReadOpReadExecutorIdx
|
||||||
|
fakeReadOpGetOmapValuesByKeysExecutorIdx
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ IOContextW = &FakeIOContext{}
|
||||||
|
|
||||||
|
// fakeWriteOpStepExecutorOrder defines fixed order in which the write ops are performed.
|
||||||
|
fakeWriteOpStepExecutorOrder = []fakeWriteOpStepExecutorIdx{
|
||||||
|
fakeWriteOpAssertVersionExecutorIdx,
|
||||||
|
fakeWriteOpRemoveExecutorIdx,
|
||||||
|
fakeWriteOpCreateExecutorIdx,
|
||||||
|
fakeWriteOpSetXattrExecutorIdx,
|
||||||
|
fakeWriteOpWriteFullExecutorIdx,
|
||||||
|
fakeWriteOpRmOmapKeysExecutorIdx,
|
||||||
|
fakeWriteOpSetOmapExecutorIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeReadOpStepExecutorOrder defines fixed order in which the read ops are performed.
|
||||||
|
fakeReadOpStepExecutorOrder = []fakeReadOpStepExecutorIdx{
|
||||||
|
fakeReadOpAssertVersionExecutorIdx,
|
||||||
|
fakeReadOpReadExecutorIdx,
|
||||||
|
fakeReadOpGetOmapValuesByKeysExecutorIdx,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFakeRados() *FakeRados {
|
||||||
|
return &FakeRados{
|
||||||
|
Objs: make(map[string]*FakeObj),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeIOContext(fakeRados *FakeRados) *FakeIOContext {
|
||||||
|
return &FakeIOContext{
|
||||||
|
Rados: fakeRados,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e fakeRadosError) Error() string {
|
||||||
|
return fmt.Sprintf("FakeRados errno=%d", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e fakeRadosError) ErrorCode() int {
|
||||||
|
return int(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *FakeObj) String() string {
|
||||||
|
return fmt.Sprintf("%s{Ver=%d, Xattrs(%d)=%+v, OMap(%d)=%+v, Data(%d)=%+v}",
|
||||||
|
o.Oid, o.Ver, len(o.Xattrs), o.Xattrs, len(o.Omap), o.Omap, len(o.Data), o.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeIOContext) GetLastVersion() (uint64, error) {
|
||||||
|
return c.LastObjVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeIOContext) getObj(oid string) (*FakeObj, error) {
|
||||||
|
obj, ok := c.Rados.Objs[oid]
|
||||||
|
if !ok {
|
||||||
|
return nil, rados.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeIOContext) GetXattr(oid, key string, data []byte) (int, error) {
|
||||||
|
obj, ok := c.Rados.Objs[oid]
|
||||||
|
if !ok {
|
||||||
|
return 0, rados.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
xattr, ok := obj.Xattrs[key]
|
||||||
|
if !ok {
|
||||||
|
return 0, fakeRadosError(-int(unix.ENODATA))
|
||||||
|
}
|
||||||
|
copy(data, xattr)
|
||||||
|
|
||||||
|
return len(xattr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeIOContext) CreateWriteOp() WriteOpW {
|
||||||
|
return &FakeWriteOp{
|
||||||
|
IoCtx: c,
|
||||||
|
steps: make(map[fakeWriteOpStepExecutorIdx]fakeWriteOpStepExecutor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) Operate(oid string) error {
|
||||||
|
if len(w.steps) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.oid = oid
|
||||||
|
|
||||||
|
for _, writeOpExecutorIdx := range fakeWriteOpStepExecutorOrder {
|
||||||
|
e, ok := w.steps[writeOpExecutorIdx]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.operate(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj, err := w.IoCtx.getObj(oid); err == nil {
|
||||||
|
obj.Ver++
|
||||||
|
w.IoCtx.LastObjVersion = obj.Ver
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) Release() {}
|
||||||
|
|
||||||
|
func (c *FakeIOContext) CreateReadOp() ReadOpW {
|
||||||
|
return &FakeReadOp{
|
||||||
|
IoCtx: c,
|
||||||
|
steps: make(map[fakeReadOpStepExecutorIdx]fakeReadOpStepExecutor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeReadOp) Operate(oid string) error {
|
||||||
|
r.oid = oid
|
||||||
|
|
||||||
|
for _, readOpExecutorIdx := range fakeReadOpStepExecutorOrder {
|
||||||
|
e, ok := r.steps[readOpExecutorIdx]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.operate(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj, err := r.IoCtx.getObj(oid); err == nil {
|
||||||
|
r.IoCtx.LastObjVersion = obj.Ver
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeReadOp) Release() {}
|
||||||
|
|
||||||
|
// WriteOp Create
|
||||||
|
|
||||||
|
type fakeWriteOpCreateExecutor struct {
|
||||||
|
exclusive rados.CreateOption
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeWriteOpCreateExecutor) operate(w *FakeWriteOp) error {
|
||||||
|
if e.exclusive == rados.CreateExclusive {
|
||||||
|
if _, exists := w.IoCtx.Rados.Objs[w.oid]; exists {
|
||||||
|
return rados.ErrObjectExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.IoCtx.Rados.Objs[w.oid] = &FakeObj{
|
||||||
|
Oid: w.oid,
|
||||||
|
Omap: make(map[string][]byte),
|
||||||
|
Xattrs: make(map[string][]byte),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) Create(exclusive rados.CreateOption) {
|
||||||
|
w.steps[fakeWriteOpCreateExecutorIdx] = &fakeWriteOpCreateExecutor{
|
||||||
|
exclusive: exclusive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOp Remove
|
||||||
|
|
||||||
|
type fakeWriteOpRemoveExecutor struct{}
|
||||||
|
|
||||||
|
func (e *fakeWriteOpRemoveExecutor) operate(w *FakeWriteOp) error {
|
||||||
|
if _, err := w.IoCtx.getObj(w.oid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(w.IoCtx.Rados.Objs, w.oid)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) Remove() {
|
||||||
|
w.steps[fakeWriteOpRemoveExecutorIdx] = &fakeWriteOpRemoveExecutor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOp SetXattr
|
||||||
|
|
||||||
|
type fakeWriteOpSetXattrExecutor struct {
|
||||||
|
name string
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeWriteOpSetXattrExecutor) operate(w *FakeWriteOp) error {
|
||||||
|
obj, err := w.IoCtx.getObj(w.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Xattrs[e.name] = e.value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) SetXattr(name string, value []byte) {
|
||||||
|
valueCopy := append([]byte(nil), value...)
|
||||||
|
|
||||||
|
w.steps[fakeWriteOpSetXattrExecutorIdx] = &fakeWriteOpSetXattrExecutor{
|
||||||
|
name: name,
|
||||||
|
value: valueCopy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOp WriteFull
|
||||||
|
|
||||||
|
type fakeWriteOpWriteFullExecutor struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeWriteOpWriteFullExecutor) operate(w *FakeWriteOp) error {
|
||||||
|
obj, err := w.IoCtx.getObj(w.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Data = e.data
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) WriteFull(b []byte) {
|
||||||
|
bCopy := append([]byte(nil), b...)
|
||||||
|
|
||||||
|
w.steps[fakeWriteOpWriteFullExecutorIdx] = &fakeWriteOpWriteFullExecutor{
|
||||||
|
data: bCopy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOp SetOmap
|
||||||
|
|
||||||
|
type fakeWriteOpSetOmapExecutor struct {
|
||||||
|
pairs map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeWriteOpSetOmapExecutor) operate(w *FakeWriteOp) error {
|
||||||
|
obj, err := w.IoCtx.getObj(w.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range e.pairs {
|
||||||
|
obj.Omap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) SetOmap(pairs map[string][]byte) {
|
||||||
|
pairsCopy := make(map[string][]byte, len(pairs))
|
||||||
|
for k, v := range pairs {
|
||||||
|
vCopy := append([]byte(nil), v...)
|
||||||
|
pairsCopy[k] = vCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
w.steps[fakeWriteOpSetOmapExecutorIdx] = &fakeWriteOpSetOmapExecutor{
|
||||||
|
pairs: pairsCopy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOp RmOmapKeys
|
||||||
|
|
||||||
|
type fakeWriteOpRmOmapKeysExecutor struct {
|
||||||
|
keys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeWriteOpRmOmapKeysExecutor) operate(w *FakeWriteOp) error {
|
||||||
|
obj, err := w.IoCtx.getObj(w.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range e.keys {
|
||||||
|
delete(obj.Omap, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) RmOmapKeys(keys []string) {
|
||||||
|
keysCopy := append([]string(nil), keys...)
|
||||||
|
|
||||||
|
w.steps[fakeWriteOpRmOmapKeysExecutorIdx] = &fakeWriteOpRmOmapKeysExecutor{
|
||||||
|
keys: keysCopy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOp AssertVersion
|
||||||
|
|
||||||
|
type fakeWriteOpAssertVersionExecutor struct {
|
||||||
|
version uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeWriteOpAssertVersionExecutor) operate(w *FakeWriteOp) error {
|
||||||
|
obj, err := w.IoCtx.getObj(w.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateObjVersion(obj.Ver, e.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *FakeWriteOp) AssertVersion(v uint64) {
|
||||||
|
w.steps[fakeWriteOpAssertVersionExecutorIdx] = &fakeWriteOpAssertVersionExecutor{
|
||||||
|
version: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOp Read
|
||||||
|
|
||||||
|
type fakeReadOpReadExecutor struct {
|
||||||
|
offset int
|
||||||
|
buffer []byte
|
||||||
|
step *rados.ReadOpReadStep
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeReadOpReadExecutor) operate(r *FakeReadOp) error {
|
||||||
|
obj, err := r.IoCtx.getObj(r.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.offset > len(obj.Data) {
|
||||||
|
// RADOS just returns zero bytes read.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
end := e.offset + len(e.buffer)
|
||||||
|
if end > len(obj.Data) {
|
||||||
|
end = len(obj.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
nbytes := end - e.offset
|
||||||
|
e.step.BytesRead = int64(nbytes)
|
||||||
|
copy(e.buffer, obj.Data[e.offset:])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeReadOp) Read(offset uint64, buffer []byte) *rados.ReadOpReadStep {
|
||||||
|
s := &rados.ReadOpReadStep{}
|
||||||
|
r.steps[fakeReadOpReadExecutorIdx] = &fakeReadOpReadExecutor{
|
||||||
|
offset: int(offset),
|
||||||
|
buffer: buffer,
|
||||||
|
step: s,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOp GetOmapValuesByKeys
|
||||||
|
|
||||||
|
type (
|
||||||
|
fakeReadOpGetOmapValuesByKeysExecutor struct {
|
||||||
|
keys []string
|
||||||
|
step *FakeReadOpOmapGetValsByKeysStep
|
||||||
|
}
|
||||||
|
|
||||||
|
FakeReadOpOmapGetValsByKeysStep struct {
|
||||||
|
pairs []rados.OmapKeyValue
|
||||||
|
idx int
|
||||||
|
canIterate bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *fakeReadOpGetOmapValuesByKeysExecutor) operate(r *FakeReadOp) error {
|
||||||
|
obj, err := r.IoCtx.getObj(r.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pairs []rados.OmapKeyValue
|
||||||
|
for _, key := range e.keys {
|
||||||
|
val, ok := obj.Omap[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs = append(pairs, rados.OmapKeyValue{
|
||||||
|
Key: key,
|
||||||
|
Value: val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
e.step.pairs = pairs
|
||||||
|
e.step.canIterate = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FakeReadOpOmapGetValsByKeysStep) Next() (*rados.OmapKeyValue, error) {
|
||||||
|
if !s.canIterate {
|
||||||
|
return nil, rados.ErrOperationIncomplete
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.idx >= len(s.pairs) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
omapKeyValue := &s.pairs[s.idx]
|
||||||
|
s.idx++
|
||||||
|
|
||||||
|
return omapKeyValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeReadOp) GetOmapValuesByKeys(keys []string) ReadOpOmapGetValsByKeysStepW {
|
||||||
|
keysCopy := append([]string(nil), keys...)
|
||||||
|
|
||||||
|
s := &FakeReadOpOmapGetValsByKeysStep{}
|
||||||
|
r.steps[fakeReadOpGetOmapValuesByKeysExecutorIdx] = &fakeReadOpGetOmapValuesByKeysExecutor{
|
||||||
|
keys: keysCopy,
|
||||||
|
step: s,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOp AssertVersion
|
||||||
|
|
||||||
|
type fakeReadOpAssertVersionExecutor struct {
|
||||||
|
version uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fakeReadOpAssertVersionExecutor) operate(r *FakeReadOp) error {
|
||||||
|
obj, err := r.IoCtx.getObj(r.oid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateObjVersion(obj.Ver, e.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FakeReadOp) AssertVersion(v uint64) {
|
||||||
|
r.steps[fakeReadOpAssertVersionExecutorIdx] = &fakeReadOpAssertVersionExecutor{
|
||||||
|
version: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateObjVersion(expected, actual uint64) error {
|
||||||
|
// See librados docs for returning error codes in rados_*_op_assert_version:
|
||||||
|
// https://docs.ceph.com/en/latest/rados/api/librados/?#c.rados_write_op_assert_version
|
||||||
|
// https://docs.ceph.com/en/latest/rados/api/librados/?#c.rados_read_op_assert_version
|
||||||
|
|
||||||
|
if expected > actual {
|
||||||
|
return rados.OperationError{
|
||||||
|
OpError: fakeRadosError(-int(unix.ERANGE)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected < actual {
|
||||||
|
return rados.OperationError{
|
||||||
|
OpError: fakeRadosError(-int(unix.EOVERFLOW)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
106
internal/util/reftracker/radoswrapper/interface.go
Normal file
106
internal/util/reftracker/radoswrapper/interface.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 radoswrapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ceph/go-ceph/rados"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These interfaces are just wrappers around some of go-ceph's rados pkg
|
||||||
|
// structures and functions. They have two implementations: the "real" one
|
||||||
|
// (that simply uses go-ceph), and a fake one, used in unit tests.
|
||||||
|
|
||||||
|
// IOContextW is a wrapper around rados.IOContext.
|
||||||
|
type IOContextW interface {
|
||||||
|
// GetLastVersion will return the version number of the last object read or
|
||||||
|
// written to.
|
||||||
|
GetLastVersion() (uint64, error)
|
||||||
|
|
||||||
|
// GetXattr gets an xattr with key `name`, it returns the length of
|
||||||
|
// the key read or an error if not successful
|
||||||
|
GetXattr(oid string, key string, data []byte) (int, error)
|
||||||
|
|
||||||
|
// CreateWriteOp returns a newly constructed write operation.
|
||||||
|
CreateWriteOp() WriteOpW
|
||||||
|
|
||||||
|
// CreateReadOp returns a newly constructed read operation.
|
||||||
|
CreateReadOp() ReadOpW
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteOpW is a wrapper around rados.WriteOp interface.
|
||||||
|
type WriteOpW interface {
|
||||||
|
// Create a rados object.
|
||||||
|
Create(exclusive rados.CreateOption)
|
||||||
|
|
||||||
|
// Remove object.
|
||||||
|
Remove()
|
||||||
|
|
||||||
|
// SetXattr sets an xattr.
|
||||||
|
SetXattr(name string, value []byte)
|
||||||
|
|
||||||
|
// WriteFull writes a given byte slice as the whole object,
|
||||||
|
// atomically replacing it.
|
||||||
|
WriteFull(b []byte)
|
||||||
|
|
||||||
|
// SetOmap appends the map `pairs` to the omap `oid`.
|
||||||
|
SetOmap(pairs map[string][]byte)
|
||||||
|
|
||||||
|
// RmOmapKeys removes the specified `keys` from the omap `oid`.
|
||||||
|
RmOmapKeys(keys []string)
|
||||||
|
|
||||||
|
// AssertVersion ensures that the object exists and that its internal version
|
||||||
|
// number is equal to "ver" before writing. "ver" should be a version number
|
||||||
|
// previously obtained with IOContext.GetLastVersion().
|
||||||
|
AssertVersion(ver uint64)
|
||||||
|
|
||||||
|
// Operate will perform the operation(s).
|
||||||
|
Operate(oid string) error
|
||||||
|
|
||||||
|
// Release the resources associated with this write operation.
|
||||||
|
Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOpW is a wrapper around rados.ReadOp.
|
||||||
|
type ReadOpW interface {
|
||||||
|
// Read bytes from offset into buffer.
|
||||||
|
// len(buffer) is the maximum number of bytes read from the object.
|
||||||
|
// buffer[:ReadOpReadStep.BytesRead] then contains object data.
|
||||||
|
Read(offset uint64, buffer []byte) *rados.ReadOpReadStep
|
||||||
|
|
||||||
|
// GetOmapValuesByKeys starts iterating over specific key/value pairs.
|
||||||
|
GetOmapValuesByKeys(keys []string) ReadOpOmapGetValsByKeysStepW
|
||||||
|
|
||||||
|
// AssertVersion ensures that the object exists and that its internal version
|
||||||
|
// number is equal to "ver" before reading. "ver" should be a version number
|
||||||
|
// previously obtained with IOContext.GetLastVersion().
|
||||||
|
AssertVersion(ver uint64)
|
||||||
|
|
||||||
|
// Operate will perform the operation(s).
|
||||||
|
Operate(oid string) error
|
||||||
|
|
||||||
|
// Release the resources associated with this read operation.
|
||||||
|
Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOpOmapGetValsByKeysStepW is a wrapper around rados.ReadOpOmapGetValsByKeysStep.
|
||||||
|
type ReadOpOmapGetValsByKeysStepW interface {
|
||||||
|
// Next gets the next omap key/value pair referenced by
|
||||||
|
// ReadOpOmapGetValsByKeysStep's internal iterator.
|
||||||
|
// If there are no more elements to retrieve, (nil, nil) is returned.
|
||||||
|
// May be called only after Operate() finished.
|
||||||
|
Next() (*rados.OmapKeyValue, error)
|
||||||
|
}
|
133
internal/util/reftracker/radoswrapper/radoswrapper.go
Normal file
133
internal/util/reftracker/radoswrapper/radoswrapper.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 radoswrapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ceph/go-ceph/rados"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
IOContext struct {
|
||||||
|
*rados.IOContext
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteOp struct {
|
||||||
|
IoCtx *rados.IOContext
|
||||||
|
*rados.WriteOp
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOp struct {
|
||||||
|
IoCtx *rados.IOContext
|
||||||
|
*rados.ReadOp
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOpOmapGetValsByKeysStep struct {
|
||||||
|
*rados.ReadOpOmapGetValsByKeysStep
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ IOContextW = &IOContext{}
|
||||||
|
|
||||||
|
func NewIOContext(ioctx *rados.IOContext) IOContextW {
|
||||||
|
return &IOContext{
|
||||||
|
IOContext: ioctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IOContext) GetLastVersion() (uint64, error) {
|
||||||
|
return c.IOContext.GetLastVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IOContext) GetXattr(oid, key string, data []byte) (int, error) {
|
||||||
|
return c.IOContext.GetXattr(oid, key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IOContext) CreateWriteOp() WriteOpW {
|
||||||
|
return &WriteOp{
|
||||||
|
IoCtx: c.IOContext,
|
||||||
|
WriteOp: rados.CreateWriteOp(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *IOContext) CreateReadOp() ReadOpW {
|
||||||
|
return &ReadOp{
|
||||||
|
IoCtx: c.IOContext,
|
||||||
|
ReadOp: rados.CreateReadOp(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) Create(exclusive rados.CreateOption) {
|
||||||
|
w.WriteOp.Create(exclusive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) Remove() {
|
||||||
|
w.WriteOp.Remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) SetXattr(name string, value []byte) {
|
||||||
|
w.WriteOp.SetXattr(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) WriteFull(b []byte) {
|
||||||
|
w.WriteOp.WriteFull(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) SetOmap(pairs map[string][]byte) {
|
||||||
|
w.WriteOp.SetOmap(pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) RmOmapKeys(keys []string) {
|
||||||
|
w.WriteOp.RmOmapKeys(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) AssertVersion(v uint64) {
|
||||||
|
w.WriteOp.AssertVersion(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) Operate(oid string) error {
|
||||||
|
return w.WriteOp.Operate(w.IoCtx, oid, rados.OperationNoFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteOp) Release() {
|
||||||
|
w.WriteOp.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOp) Read(offset uint64, buffer []byte) *rados.ReadOpReadStep {
|
||||||
|
return r.ReadOp.Read(offset, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOp) GetOmapValuesByKeys(keys []string) ReadOpOmapGetValsByKeysStepW {
|
||||||
|
return &ReadOpOmapGetValsByKeysStep{
|
||||||
|
ReadOpOmapGetValsByKeysStep: r.ReadOp.GetOmapValuesByKeys(keys),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOp) AssertVersion(v uint64) {
|
||||||
|
r.ReadOp.AssertVersion(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOp) Operate(oid string) error {
|
||||||
|
return r.ReadOp.Operate(r.IoCtx, oid, rados.OperationNoFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ReadOp) Release() {
|
||||||
|
r.ReadOp.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReadOpOmapGetValsByKeysStep) Next() (*rados.OmapKeyValue, error) {
|
||||||
|
return s.ReadOpOmapGetValsByKeysStep.Next()
|
||||||
|
}
|
248
internal/util/reftracker/reftracker.go
Normal file
248
internal/util/reftracker/reftracker.go
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 reftracker
|
||||||
|
|
||||||
|
import (
|
||||||
|
goerrors "errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/errors"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/radoswrapper"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/reftype"
|
||||||
|
v1 "github.com/ceph/ceph-csi/internal/util/reftracker/v1"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/version"
|
||||||
|
|
||||||
|
"github.com/ceph/go-ceph/rados"
|
||||||
|
)
|
||||||
|
|
||||||
|
// reftracker is key-based implementation of a reference counter.
|
||||||
|
//
|
||||||
|
// Unlike integer-based counter, reftracker counts references by tracking
|
||||||
|
// unique keys. This allows accounting in situations where idempotency must be
|
||||||
|
// preserved. It guarantees there will be no duplicit increments or decrements
|
||||||
|
// of the counter.
|
||||||
|
//
|
||||||
|
// It is stored persistently as a RADOS object, and is safe to use with
|
||||||
|
// multiple concurrent writers, and across different nodes of a cluster.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// created, err := Add(
|
||||||
|
// ioctx,
|
||||||
|
// "my-reftracker",
|
||||||
|
// map[string]struct{}{
|
||||||
|
// "ref-key-1": {},
|
||||||
|
// "ref-key-2": {},
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// Since this is a new reftracker object, `created` is `true`.
|
||||||
|
//
|
||||||
|
// "my-reftracker" now holds:
|
||||||
|
// ["ref-key-1":reftype.Normal, "ref-key-2":reftype.Normal]
|
||||||
|
// The reference count is 2.
|
||||||
|
//
|
||||||
|
// created, err := Add(
|
||||||
|
// ioctx,
|
||||||
|
// "my-reftracker",
|
||||||
|
// map[string]struct{}{
|
||||||
|
// "ref-key-1": {},
|
||||||
|
// "ref-key-2": {},
|
||||||
|
// "ref-key-3": {},
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// Reftracker named "my-reftracker" already exists, so `created` is now
|
||||||
|
// `false`. Since "ref-key-1" and "ref-key-2" keys are already tracked,
|
||||||
|
// only "ref-key-3" is added.
|
||||||
|
//
|
||||||
|
// "my-reftracker" now holds:
|
||||||
|
// ["ref-key-1":reftype.Normal, "ref-key-2":reftype.Normal,
|
||||||
|
// "ref-key-3":reftype.Normal]
|
||||||
|
// The reference count is 3.
|
||||||
|
//
|
||||||
|
// deleted, err := Remove(
|
||||||
|
// ioctx,
|
||||||
|
// "my-reftracker",
|
||||||
|
// map[string]reftype.RefType{
|
||||||
|
// "ref-key-1": reftype.Normal,
|
||||||
|
// "ref-key-2": reftype.Mask,
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// "my-reftracker" now holds:
|
||||||
|
// ["ref-key-2":reftype.Mask, "ref-key-3":reftype.Normal]
|
||||||
|
// The reference count is 1.
|
||||||
|
//
|
||||||
|
// Since the reference count is greater than zero, `deleted` is `false`.
|
||||||
|
// "ref-key-1" was removed, and so is not listed among tracked references.
|
||||||
|
// "ref-key-2" was only masked, so it's been kept. However, masked references
|
||||||
|
// don't contribute to overall reference count, so the resulting refcount
|
||||||
|
// after this Remove() call is 1.
|
||||||
|
//
|
||||||
|
// created, err := Add(
|
||||||
|
// ioctx,
|
||||||
|
// "my-reftracker",
|
||||||
|
// map[string]struct{}{
|
||||||
|
// "ref-key-2": {},
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// "my-reftracker" now holds:
|
||||||
|
// ["ref-key-2":reftype.Mask, "ref-key-3":reftype.Normal]
|
||||||
|
// The reference count is 1.
|
||||||
|
//
|
||||||
|
// "ref-key-2" is already tracked, so it will not be added again. Since it
|
||||||
|
// remains masked, it won't contribute to the reference count.
|
||||||
|
//
|
||||||
|
// deleted, err := Remove(
|
||||||
|
// ioctx,
|
||||||
|
// "my-reftracker",
|
||||||
|
// map[string]reftype.RefType{
|
||||||
|
// "ref-key-3": reftype.Normal,
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// "ref-key-3" was the only tracked key that contributed to reference count.
|
||||||
|
// After this Remove() call it's now removed. As a result, the reference count
|
||||||
|
// dropped down to zero, and the whole object has been deleted too.
|
||||||
|
// `deleted` is `true`.
|
||||||
|
|
||||||
|
// Add atomically adds references to `rtName` reference tracker.
|
||||||
|
// If the reftracker object doesn't exist yet, it is created and `true` is
|
||||||
|
// returned. If some keys in `refs` map are already tracked by this reftracker
|
||||||
|
// object, they will not be added again.
|
||||||
|
func Add(
|
||||||
|
ioctx radoswrapper.IOContextW,
|
||||||
|
rtName string,
|
||||||
|
refs map[string]struct{},
|
||||||
|
) (bool, error) {
|
||||||
|
if err := validateAddInput(rtName, refs); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reftracker version.
|
||||||
|
|
||||||
|
rtVer, err := version.Read(ioctx, rtName)
|
||||||
|
if err != nil {
|
||||||
|
if goerrors.Is(err, rados.ErrNotFound) {
|
||||||
|
// This is a new reftracker. Initialize it with `refs`.
|
||||||
|
if err = v1.Init(ioctx, rtName, refs); err != nil {
|
||||||
|
return false, fmt.Errorf("failed to initialize reftracker: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("failed to read reftracker version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add references to reftracker object.
|
||||||
|
|
||||||
|
gen, err := ioctx.GetLastVersion()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get RADOS object version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rtVer {
|
||||||
|
case v1.Version:
|
||||||
|
err = v1.Add(ioctx, rtName, gen, refs)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to add refs: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = errors.UnknownObjectVersion(rtVer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove atomically removes references from `rtName` reference tracker.
|
||||||
|
// If the reftracker object holds no references after this removal, the whole
|
||||||
|
// object is deleted too, and `true` is returned. If the reftracker object
|
||||||
|
// doesn't exist, (true, nil) is returned.
|
||||||
|
func Remove(
|
||||||
|
ioctx radoswrapper.IOContextW,
|
||||||
|
rtName string,
|
||||||
|
refs map[string]reftype.RefType,
|
||||||
|
) (bool, error) {
|
||||||
|
if err := validateRemoveInput(rtName, refs); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reftracker version.
|
||||||
|
|
||||||
|
rtVer, err := version.Read(ioctx, rtName)
|
||||||
|
if err != nil {
|
||||||
|
if goerrors.Is(err, rados.ErrNotFound) {
|
||||||
|
// This reftracker doesn't exist. Assume it was already deleted.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("failed to read reftracker version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove references from reftracker.
|
||||||
|
|
||||||
|
gen, err := ioctx.GetLastVersion()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get RADOS object version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleted bool
|
||||||
|
|
||||||
|
switch rtVer {
|
||||||
|
case v1.Version:
|
||||||
|
deleted, err = v1.Remove(ioctx, rtName, gen, refs)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to remove refs: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = errors.UnknownObjectVersion(rtVer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleted, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoRTName = goerrors.New("missing reftracker name")
|
||||||
|
errNoRefs = goerrors.New("missing refs")
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateAddInput(rtName string, refs map[string]struct{}) error {
|
||||||
|
if rtName == "" {
|
||||||
|
return errNoRTName
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(refs) == 0 {
|
||||||
|
return errNoRefs
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRemoveInput(rtName string, refs map[string]reftype.RefType) error {
|
||||||
|
if rtName == "" {
|
||||||
|
return errNoRTName
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(refs) == 0 {
|
||||||
|
return errNoRefs
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
491
internal/util/reftracker/reftracker_test.go
Normal file
491
internal/util/reftracker/reftracker_test.go
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 reftracker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/radoswrapper"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/reftype"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rtName = "hello-rt"
|
||||||
|
|
||||||
|
func TestRTAdd(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Verify input validation for reftracker name.
|
||||||
|
t.Run("AddNoName", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
created, err := Add(ioctx, "", nil)
|
||||||
|
assert.Error(ts, err)
|
||||||
|
assert.False(ts, created)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify input validation for nil and empty refs.
|
||||||
|
t.Run("AddNoRefs", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
refs := []map[string]struct{}{
|
||||||
|
nil,
|
||||||
|
make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
for _, ref := range refs {
|
||||||
|
created, err := Add(ioctx, rtName, ref)
|
||||||
|
assert.Error(ts, err)
|
||||||
|
assert.False(ts, created)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add multiple refs in a single Add().
|
||||||
|
t.Run("AddBulk", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add refs where each Add() has some of the refs overlapping
|
||||||
|
// with the previous call.
|
||||||
|
t.Run("AddOverlapping", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
refsTable := []map[string]struct{}{
|
||||||
|
{"ref2": {}, "ref3": {}},
|
||||||
|
{"ref3": {}, "ref4": {}},
|
||||||
|
{"ref4": {}, "ref5": {}},
|
||||||
|
}
|
||||||
|
for _, refs := range refsTable {
|
||||||
|
created, err = Add(ioctx, rtName, refs)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, created)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRTRemove(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Verify input validation for nil and empty refs.
|
||||||
|
t.Run("RemoveNoRefs", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
refs := []map[string]reftype.RefType{
|
||||||
|
nil,
|
||||||
|
make(map[string]reftype.RefType),
|
||||||
|
}
|
||||||
|
for _, ref := range refs {
|
||||||
|
created, err := Remove(ioctx, rtName, ref)
|
||||||
|
assert.Error(ts, err)
|
||||||
|
assert.False(ts, created)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Attempt to remove refs in a non-existent reftracker object should result
|
||||||
|
// in success, with deleted=true,err=nil.
|
||||||
|
t.Run("RemoveNotExists", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
deleted, err := Remove(ioctx, "xxx", map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Removing only non-existent refs should not result in reftracker object
|
||||||
|
// deletion.
|
||||||
|
t.Run("RemoveNonExistentRefs", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"refX": reftype.Normal,
|
||||||
|
"refY": reftype.Normal,
|
||||||
|
"refZ": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Removing all refs plus some surplus should result in reftracker object
|
||||||
|
// deletion.
|
||||||
|
t.Run("RemoveNonExistentRefs", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"refX": reftype.Normal,
|
||||||
|
"refY": reftype.Normal,
|
||||||
|
"ref": reftype.Normal,
|
||||||
|
"refZ": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bulk removal of all refs should result in reftracker object deletion.
|
||||||
|
t.Run("RemoveBulk", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
keys := []string{"ref1", "ref2", "ref3"}
|
||||||
|
refsToAdd := make(map[string]struct{})
|
||||||
|
refsToRemove := make(map[string]reftype.RefType)
|
||||||
|
for _, k := range keys {
|
||||||
|
refsToAdd[k] = struct{}{}
|
||||||
|
refsToRemove[k] = reftype.Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, refsToAdd)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, refsToRemove)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Removal of all refs one-by-one should result in reftracker object deletion
|
||||||
|
// in the last Remove() call.
|
||||||
|
t.Run("RemoveSingle", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
for _, k := range []string{"ref3", "ref2"} {
|
||||||
|
deleted, errRemove := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
k: reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, errRemove)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the last reference. It should remove the whole reftracker object too.
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Cycle through reftracker object twice.
|
||||||
|
t.Run("AddRemoveAddRemove", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
refsToAdd := map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
}
|
||||||
|
refsToRemove := map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
"ref2": reftype.Normal,
|
||||||
|
"ref3": reftype.Normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
created, err := Add(ioctx, rtName, refsToAdd)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, refsToRemove)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check for respecting idempotency by making multiple additions with overlapping keys
|
||||||
|
// and removing only ref keys that were distinct.
|
||||||
|
t.Run("AddOverlappingRemoveBulk", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
})
|
||||||
|
assert.True(ts, created)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
refsTable := []map[string]struct{}{
|
||||||
|
{"ref2": {}, "ref3": {}},
|
||||||
|
{"ref3": {}, "ref4": {}},
|
||||||
|
{"ref4": {}, "ref5": {}},
|
||||||
|
}
|
||||||
|
for _, refs := range refsTable {
|
||||||
|
created, err = Add(ioctx, rtName, refs)
|
||||||
|
assert.False(ts, created)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
"ref2": reftype.Normal,
|
||||||
|
"ref3": reftype.Normal,
|
||||||
|
"ref4": reftype.Normal,
|
||||||
|
"ref5": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRTMask(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Bulk masking all refs should result in reftracker object deletion.
|
||||||
|
t.Run("MaskAllBulk", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
keys := []string{"ref1", "ref2", "ref3"}
|
||||||
|
refsToAdd := make(map[string]struct{})
|
||||||
|
refsToRemove := make(map[string]reftype.RefType)
|
||||||
|
for _, k := range keys {
|
||||||
|
refsToAdd[k] = struct{}{}
|
||||||
|
refsToRemove[k] = reftype.Mask
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, refsToAdd)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, refsToRemove)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Masking all refs one-by-one should result in reftracker object deletion in
|
||||||
|
// the last Remove() call.
|
||||||
|
t.Run("RemoveSingle", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
for _, k := range []string{"ref3", "ref2"} {
|
||||||
|
deleted, errRemove := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
k: reftype.Mask,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, errRemove)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the last reference. It should delete the whole reftracker object
|
||||||
|
// too.
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Mask,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bulk removing two (out of 3) refs and then masking the ref that's left
|
||||||
|
// should result in reftracker object deletion in the last Remove() call.
|
||||||
|
t.Run("RemoveBulkMaskSingle", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
"ref2": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
|
||||||
|
deleted, err = Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref3": reftype.Mask,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Bulk masking two (out of 3) refs and then removing the ref that's left
|
||||||
|
// should result in reftracker object deletion in the last Remove() call.
|
||||||
|
t.Run("MaskSingleRemoveBulk", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Mask,
|
||||||
|
"ref2": reftype.Mask,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
|
||||||
|
deleted, err = Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref3": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify that masking refs hides them from future Add()s.
|
||||||
|
t.Run("MaskAndAdd", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Mask,
|
||||||
|
"ref2": reftype.Mask,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
|
||||||
|
created, err = Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, created)
|
||||||
|
|
||||||
|
deleted, err = Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref3": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify that masked refs may be removed with reftype.Normal and re-added.
|
||||||
|
t.Run("MaskRemoveAdd", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
|
||||||
|
created, err := Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
"ref3": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, created)
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Mask,
|
||||||
|
"ref2": reftype.Mask,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
|
||||||
|
deleted, err = Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
"ref2": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
|
||||||
|
created, err = Add(ioctx, rtName, map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
"ref2": {},
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, created)
|
||||||
|
|
||||||
|
deleted, err = Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref3": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.False(ts, deleted)
|
||||||
|
|
||||||
|
deleted, err = Remove(ioctx, rtName, map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
"ref2": reftype.Normal,
|
||||||
|
})
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.True(ts, deleted)
|
||||||
|
})
|
||||||
|
}
|
63
internal/util/reftracker/reftype/reftype.go
Normal file
63
internal/util/reftracker/reftype/reftype.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 reftype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RefType describes type of the reftracker reference.
|
||||||
|
type RefType int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
refTypeSize = 1
|
||||||
|
|
||||||
|
// Unknown reftype used to signal error state.
|
||||||
|
Unknown RefType = 0
|
||||||
|
|
||||||
|
// Normal type tags the reference to have normal effect on the reference
|
||||||
|
// count. Adding Normal reference increments the reference count. Removing
|
||||||
|
// Normal reference decrements the reference count.
|
||||||
|
//
|
||||||
|
// It may be converted to a Mask if it is removed with Mask reftype.
|
||||||
|
Normal RefType = 1
|
||||||
|
|
||||||
|
// Mask type tags the reference to be masked, making it not contribute to the
|
||||||
|
// overall reference count. The reference will be ignored by all future Add()
|
||||||
|
// calls until it is removed with Normal reftype.
|
||||||
|
Mask RefType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToBytes(t RefType) []byte {
|
||||||
|
return []byte{byte(t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromBytes(bs []byte) (RefType, error) {
|
||||||
|
if len(bs) != refTypeSize {
|
||||||
|
return Unknown, errors.UnexpectedReadSize(refTypeSize, len(bs))
|
||||||
|
}
|
||||||
|
|
||||||
|
num := RefType(bs[0])
|
||||||
|
switch num { // nolint:exhaustive // reftype.Unknown is handled in default case.
|
||||||
|
case Normal, Mask:
|
||||||
|
return num, nil
|
||||||
|
default:
|
||||||
|
return Unknown, fmt.Errorf("unknown reftype %d", num)
|
||||||
|
}
|
||||||
|
}
|
63
internal/util/reftracker/reftype/reftype_test.go
Normal file
63
internal/util/reftracker/reftype/reftype_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 reftype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRefTypeBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
refTypeNormalBytes = []byte{1}
|
||||||
|
refTypeMaskBytes = []byte{2}
|
||||||
|
|
||||||
|
expectedBytes = [][]byte{refTypeNormalBytes, refTypeMaskBytes}
|
||||||
|
refTypes = []RefType{Normal, Mask}
|
||||||
|
|
||||||
|
refTypeInvalidBytes = []byte{0xFF}
|
||||||
|
refTypeWrongSizeBytes = []byte{0, 0, 0, 0, 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("ToBytes", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
for i := range expectedBytes {
|
||||||
|
bs := ToBytes(refTypes[i])
|
||||||
|
assert.Equal(ts, expectedBytes[i], bs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FromBytes", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
for i := range refTypes {
|
||||||
|
refType, err := FromBytes(expectedBytes[i])
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.Equal(ts, refTypes[i], refType)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := FromBytes(refTypeInvalidBytes)
|
||||||
|
assert.Error(ts, err)
|
||||||
|
|
||||||
|
_, err = FromBytes(refTypeWrongSizeBytes)
|
||||||
|
assert.Error(ts, err)
|
||||||
|
})
|
||||||
|
}
|
47
internal/util/reftracker/v1/refcount.go
Normal file
47
internal/util/reftracker/v1/refcount.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents the number of references a reftracker object holds.
|
||||||
|
type refCount uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = 1
|
||||||
|
|
||||||
|
refCountSize = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rc refCount) toBytes() []byte {
|
||||||
|
bs := make([]byte, refCountSize)
|
||||||
|
binary.BigEndian.PutUint32(bs, uint32(rc))
|
||||||
|
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func refCountFromBytes(bs []byte) (refCount, error) {
|
||||||
|
if len(bs) != refCountSize {
|
||||||
|
return 0, errors.UnexpectedReadSize(refCountSize, len(bs))
|
||||||
|
}
|
||||||
|
|
||||||
|
return refCount(binary.BigEndian.Uint32(bs)), nil
|
||||||
|
}
|
51
internal/util/reftracker/v1/refcount_test.go
Normal file
51
internal/util/reftracker/v1/refcount_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestV1RefCountBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
refCountBytes = []byte{0x0, 0x0, 0x0, 0x7B}
|
||||||
|
refCountValue = refCount(123)
|
||||||
|
wrongSizeRefCountBytes = []byte{0, 0, 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("ToBytes", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
bs := refCountValue.toBytes()
|
||||||
|
assert.Equal(ts, refCountBytes, bs)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FromBytes", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
rc, err := refCountFromBytes(refCountBytes)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.Equal(ts, refCountValue, rc)
|
||||||
|
|
||||||
|
_, err = refCountFromBytes(wrongSizeRefCountBytes)
|
||||||
|
assert.Error(ts, err)
|
||||||
|
})
|
||||||
|
}
|
314
internal/util/reftracker/v1/v1.go
Normal file
314
internal/util/reftracker/v1/v1.go
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
goerrors "errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/errors"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/radoswrapper"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/reftype"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/version"
|
||||||
|
|
||||||
|
"github.com/ceph/go-ceph/rados"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Version 1 layout:
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If not specified otherwise, all values are stored in big-endian order.
|
||||||
|
|
||||||
|
byte idx type name
|
||||||
|
-------- ------ ------
|
||||||
|
0 .. 3 uint32 refcount
|
||||||
|
|
||||||
|
`refcount`: Number of references held by the reftracker object. The actual
|
||||||
|
reference keys are stored in an OMap of the RADOS object.
|
||||||
|
|
||||||
|
OMap entry layout:
|
||||||
|
|
||||||
|
Key:
|
||||||
|
|
||||||
|
reftracker key.
|
||||||
|
|
||||||
|
Value:
|
||||||
|
|
||||||
|
byte idx type name
|
||||||
|
-------- ------ ------
|
||||||
|
0 .. 3 uint32 type
|
||||||
|
|
||||||
|
`type`: reference type defined in reftracker/reftype.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
type readResult struct {
|
||||||
|
// Total number of references held by the reftracker object.
|
||||||
|
total refCount
|
||||||
|
// Refs whose keys matched the request.
|
||||||
|
foundRefs map[string]reftype.RefType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically initializes a new reftracker object.
|
||||||
|
func Init(
|
||||||
|
ioctx radoswrapper.IOContextW,
|
||||||
|
rtName string,
|
||||||
|
refs map[string]struct{},
|
||||||
|
) error {
|
||||||
|
// Prepare refcount and OMap key-value pairs.
|
||||||
|
|
||||||
|
refsToAddBytes := make(map[string][]byte, len(refs))
|
||||||
|
|
||||||
|
for ref := range refs {
|
||||||
|
refsToAddBytes[ref] = reftype.ToBytes(reftype.Normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the write.
|
||||||
|
|
||||||
|
w := ioctx.CreateWriteOp()
|
||||||
|
defer w.Release()
|
||||||
|
|
||||||
|
w.Create(rados.CreateExclusive)
|
||||||
|
w.SetXattr(version.XattrName, version.ToBytes(Version))
|
||||||
|
w.SetOmap(refsToAddBytes)
|
||||||
|
w.WriteFull(refCount(len(refsToAddBytes)).toBytes())
|
||||||
|
|
||||||
|
return errors.FailedObjectWrite(w.Operate(rtName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically adds refs to an existing reftracker object.
|
||||||
|
func Add(
|
||||||
|
ioctx radoswrapper.IOContextW,
|
||||||
|
rtName string,
|
||||||
|
gen uint64,
|
||||||
|
refs map[string]struct{},
|
||||||
|
) error {
|
||||||
|
// Read the reftracker object to figure out which refs to add.
|
||||||
|
|
||||||
|
readRes, err := readObjectByKeys(ioctx, rtName, gen, refsMapToKeysSlice(refs))
|
||||||
|
if err != nil {
|
||||||
|
return errors.FailedObjectRead(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build list of refs to add.
|
||||||
|
// Add only refs that are missing in the reftracker object.
|
||||||
|
|
||||||
|
refsToAdd := make(map[string][]byte)
|
||||||
|
|
||||||
|
for ref := range refs {
|
||||||
|
if _, found := readRes.foundRefs[ref]; !found {
|
||||||
|
refsToAdd[ref] = reftype.ToBytes(reftype.Normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(refsToAdd) == 0 {
|
||||||
|
// Nothing to do.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new refcount.
|
||||||
|
|
||||||
|
rcToAdd := refCount(len(refsToAdd))
|
||||||
|
newRC := readRes.total + rcToAdd
|
||||||
|
|
||||||
|
if newRC < readRes.total {
|
||||||
|
return goerrors.New("addition would overflow uint32 refcount")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the data.
|
||||||
|
|
||||||
|
w := ioctx.CreateWriteOp()
|
||||||
|
defer w.Release()
|
||||||
|
|
||||||
|
w.AssertVersion(gen)
|
||||||
|
w.WriteFull(newRC.toBytes())
|
||||||
|
w.SetOmap(refsToAdd)
|
||||||
|
|
||||||
|
return errors.FailedObjectWrite(w.Operate(rtName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically removes refs from reftracker object. If the object wouldn't hold
|
||||||
|
// any references after the removal, the whole object is deleted instead.
|
||||||
|
func Remove(
|
||||||
|
ioctx radoswrapper.IOContextW,
|
||||||
|
rtName string,
|
||||||
|
gen uint64,
|
||||||
|
refs map[string]reftype.RefType,
|
||||||
|
) (bool, error) {
|
||||||
|
// Read the reftracker object to figure out which refs to remove.
|
||||||
|
|
||||||
|
readRes, err := readObjectByKeys(ioctx, rtName, gen, typedRefsMapToKeysSlice(refs))
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.FailedObjectRead(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build lists of refs to remove, replace, and add.
|
||||||
|
// There are three cases that need to be handled:
|
||||||
|
// (1) removing reftype.Normal refs,
|
||||||
|
// (2) converting refs that were reftype.Normal into reftype.Mask,
|
||||||
|
// (3) adding a new reftype.Mask key.
|
||||||
|
|
||||||
|
var (
|
||||||
|
refsToRemove []string
|
||||||
|
refsToSet = make(map[string][]byte)
|
||||||
|
rcToSubtract refCount
|
||||||
|
)
|
||||||
|
|
||||||
|
for ref, refType := range refs {
|
||||||
|
if matchedRefType, found := readRes.foundRefs[ref]; found {
|
||||||
|
if refType == reftype.Normal {
|
||||||
|
// Case (1): regular removal of Normal ref.
|
||||||
|
refsToRemove = append(refsToRemove, ref)
|
||||||
|
if matchedRefType == reftype.Normal {
|
||||||
|
// If matchedRef was reftype.Mask, it would have already been
|
||||||
|
// subtracted from the refcount.
|
||||||
|
rcToSubtract++
|
||||||
|
}
|
||||||
|
} else if refType == reftype.Mask && matchedRefType == reftype.Normal {
|
||||||
|
// Case (2): convert Normal ref to Mask.
|
||||||
|
// Since this ref is now reftype.Mask, rcToSubtract needs to be adjusted
|
||||||
|
// too -- so that this ref is not counted in.
|
||||||
|
refsToSet[ref] = reftype.ToBytes(reftype.Mask)
|
||||||
|
rcToSubtract++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if refType == reftype.Mask {
|
||||||
|
// Case (3): add a new Mask ref.
|
||||||
|
// reftype.Mask doesn't contribute refcount so no change to rcToSubtract.
|
||||||
|
refsToSet[ref] = reftype.ToBytes(reftype.Mask)
|
||||||
|
} // else: No such ref was found, so there's nothing to remove.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(refsToRemove) == 0 && len(refsToSet) == 0 {
|
||||||
|
// Nothing to do.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new refcount.
|
||||||
|
|
||||||
|
if rcToSubtract > readRes.total {
|
||||||
|
// BUG: this should never happen!
|
||||||
|
return false, fmt.Errorf("refcount underflow, reftracker object corrupted")
|
||||||
|
}
|
||||||
|
|
||||||
|
newRC := readRes.total - rcToSubtract
|
||||||
|
// If newRC is zero, it means all refs that the reftracker object held will be
|
||||||
|
// now gone, and the object must be deleted.
|
||||||
|
deleted := newRC == 0
|
||||||
|
|
||||||
|
// Write the data.
|
||||||
|
|
||||||
|
w := ioctx.CreateWriteOp()
|
||||||
|
defer w.Release()
|
||||||
|
|
||||||
|
w.AssertVersion(gen)
|
||||||
|
|
||||||
|
if deleted {
|
||||||
|
w.Remove()
|
||||||
|
} else {
|
||||||
|
w.WriteFull(newRC.toBytes())
|
||||||
|
w.RmOmapKeys(refsToRemove)
|
||||||
|
w.SetOmap(refsToSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.Operate(rtName); err != nil {
|
||||||
|
return false, errors.FailedObjectWrite(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries to find `keys` in reftracker object and returns the result. Failing to
|
||||||
|
// find any particular key does not result in an error.
|
||||||
|
func readObjectByKeys(
|
||||||
|
ioctx radoswrapper.IOContextW,
|
||||||
|
rtName string,
|
||||||
|
gen uint64,
|
||||||
|
keys []string,
|
||||||
|
) (*readResult, error) {
|
||||||
|
// Read data from object.
|
||||||
|
|
||||||
|
rcBytes := make([]byte, refCountSize)
|
||||||
|
|
||||||
|
r := ioctx.CreateReadOp()
|
||||||
|
defer r.Release()
|
||||||
|
|
||||||
|
r.AssertVersion(gen)
|
||||||
|
r.Read(0, rcBytes)
|
||||||
|
s := r.GetOmapValuesByKeys(keys)
|
||||||
|
|
||||||
|
if err := r.Operate(rtName); err != nil {
|
||||||
|
return nil, errors.TryRADOSAborted(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it from byte slices to type-safe values.
|
||||||
|
|
||||||
|
var (
|
||||||
|
rc refCount
|
||||||
|
refs = make(map[string]reftype.RefType)
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
rc, err = refCountFromBytes(rcBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse refcount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
kvPair, err := s.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to iterate over OMap: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if kvPair == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
refType, err := reftype.FromBytes(kvPair.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse reftype: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refs[kvPair.Key] = refType
|
||||||
|
}
|
||||||
|
|
||||||
|
return &readResult{
|
||||||
|
total: rc,
|
||||||
|
foundRefs: refs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func refsMapToKeysSlice(m map[string]struct{}) []string {
|
||||||
|
s := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
s = append(s, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func typedRefsMapToKeysSlice(m map[string]reftype.RefType) []string {
|
||||||
|
s := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
s = append(s, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
423
internal/util/reftracker/v1/v1_test.go
Normal file
423
internal/util/reftracker/v1/v1_test.go
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
goerrors "errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/errors"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/radoswrapper"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/reftype"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestV1Read(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const rtName = "hello-rt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
gen = uint64(0)
|
||||||
|
|
||||||
|
validObj = radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Oid: rtName,
|
||||||
|
Data: []byte{0, 0, 0, 0},
|
||||||
|
Omap: make(map[string][]byte),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
invalidObjs = []*radoswrapper.FakeIOContext{
|
||||||
|
// Missing object.
|
||||||
|
radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados()),
|
||||||
|
// Bad generation number.
|
||||||
|
radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Ver: 123,
|
||||||
|
Oid: rtName,
|
||||||
|
Data: []byte{0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// Refcount overflow.
|
||||||
|
radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Oid: rtName,
|
||||||
|
Data: []byte{0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
refsToAdd = map[string]struct{}{"ref1": {}}
|
||||||
|
)
|
||||||
|
|
||||||
|
err := Add(validObj, rtName, gen, refsToAdd)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for i := range invalidObjs {
|
||||||
|
err = Add(invalidObjs[i], rtName, gen, refsToAdd)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for correct error type for wrong gen num.
|
||||||
|
err = Add(invalidObjs[1], rtName, gen, refsToAdd)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, goerrors.Is(err, errors.ErrObjectOutOfDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1Init(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const rtName = "hello-rt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyRados = radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{},
|
||||||
|
})
|
||||||
|
|
||||||
|
alreadyExists = radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
refsToInit = map[string]struct{}{"ref1": {}}
|
||||||
|
)
|
||||||
|
|
||||||
|
err := Init(emptyRados, rtName, refsToInit)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = Init(alreadyExists, rtName, refsToInit)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1Add(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const rtName = "hello-rt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
shouldSucceed = []struct {
|
||||||
|
before *radoswrapper.FakeObj
|
||||||
|
refsToAdd map[string]struct{}
|
||||||
|
after *radoswrapper.FakeObj
|
||||||
|
}{
|
||||||
|
// Add a new ref.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToAdd: map[string]struct{}{
|
||||||
|
"ref2": {},
|
||||||
|
},
|
||||||
|
after: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 1,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
"ref2": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(2).toBytes(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Try to add a ref that's already tracked.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToAdd: map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
},
|
||||||
|
after: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Try to add a ref that's masked.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
"ref2": reftype.ToBytes(reftype.Mask),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToAdd: map[string]struct{}{
|
||||||
|
"ref1": {},
|
||||||
|
},
|
||||||
|
after: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
"ref2": reftype.ToBytes(reftype.Mask),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldFail = []*radoswrapper.FakeIOContext{
|
||||||
|
// Missing object.
|
||||||
|
radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados()),
|
||||||
|
// Bad generation number.
|
||||||
|
radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Ver: 123,
|
||||||
|
Oid: rtName,
|
||||||
|
Data: []byte{0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// Refcount overflow.
|
||||||
|
radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Oid: rtName,
|
||||||
|
Data: []byte{0xFF, 0xFF, 0xFF, 0xFF},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := range shouldSucceed {
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
ioctx.Rados.Objs[rtName] = shouldSucceed[i].before
|
||||||
|
|
||||||
|
err := Add(ioctx, rtName, 0, shouldSucceed[i].refsToAdd)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, shouldSucceed[i].after, ioctx.Rados.Objs[rtName])
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range shouldFail {
|
||||||
|
err := Add(shouldFail[i], rtName, 0, map[string]struct{}{"ref1": {}})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for correct error type for wrong gen num.
|
||||||
|
err := Add(shouldFail[1], rtName, 0, map[string]struct{}{"ref1": {}})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, goerrors.Is(err, errors.ErrObjectOutOfDate))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1Remove(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const rtName = "hello-rt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
shouldSucceed = []struct {
|
||||||
|
before *radoswrapper.FakeObj
|
||||||
|
refsToRemove map[string]reftype.RefType
|
||||||
|
after *radoswrapper.FakeObj
|
||||||
|
deleted bool
|
||||||
|
}{
|
||||||
|
// Remove without deleting the reftracker object.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
"ref2": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(2).toBytes(),
|
||||||
|
},
|
||||||
|
refsToRemove: map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
},
|
||||||
|
after: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 1,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref2": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
},
|
||||||
|
// Remove and delete the reftracker object.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToRemove: map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
},
|
||||||
|
after: nil,
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
// Remove and delete the reftracker object.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToRemove: map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Normal,
|
||||||
|
},
|
||||||
|
after: nil,
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
// Mask a ref without deleting reftracker object.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
"ref2": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(2).toBytes(),
|
||||||
|
},
|
||||||
|
refsToRemove: map[string]reftype.RefType{
|
||||||
|
"ref2": reftype.Mask,
|
||||||
|
},
|
||||||
|
after: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 1,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
"ref2": reftype.ToBytes(reftype.Mask),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
},
|
||||||
|
// Mask a ref and delete reftracker object.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToRemove: map[string]reftype.RefType{
|
||||||
|
"ref1": reftype.Mask,
|
||||||
|
},
|
||||||
|
after: nil,
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
// Add a masking ref.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToRemove: map[string]reftype.RefType{
|
||||||
|
"ref2": reftype.Mask,
|
||||||
|
},
|
||||||
|
after: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 1,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
"ref2": reftype.ToBytes(reftype.Mask),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
},
|
||||||
|
// Try to remove non-existent ref.
|
||||||
|
{
|
||||||
|
before: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
refsToRemove: map[string]reftype.RefType{
|
||||||
|
"ref2": reftype.Normal,
|
||||||
|
},
|
||||||
|
after: &radoswrapper.FakeObj{
|
||||||
|
Oid: rtName,
|
||||||
|
Ver: 0,
|
||||||
|
Omap: map[string][]byte{
|
||||||
|
"ref1": reftype.ToBytes(reftype.Normal),
|
||||||
|
},
|
||||||
|
Data: refCount(1).toBytes(),
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad generation number.
|
||||||
|
badGen = radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Ver: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := range shouldSucceed {
|
||||||
|
ioctx := radoswrapper.NewFakeIOContext(radoswrapper.NewFakeRados())
|
||||||
|
ioctx.Rados.Objs[rtName] = shouldSucceed[i].before
|
||||||
|
|
||||||
|
deleted, err := Remove(ioctx, rtName, 0, shouldSucceed[i].refsToRemove)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, shouldSucceed[i].deleted, deleted)
|
||||||
|
assert.Equal(t, shouldSucceed[i].after, ioctx.Rados.Objs[rtName])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := Remove(badGen, rtName, 0, map[string]reftype.RefType{"ref": reftype.Normal})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, goerrors.Is(err, errors.ErrObjectOutOfDate))
|
||||||
|
}
|
64
internal/util/reftracker/version/version.go
Normal file
64
internal/util/reftracker/version/version.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/errors"
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/radoswrapper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// reftracker objects are versioned, should the object layout need to change.
|
||||||
|
// Version is stored in its underlying RADOS object xattr as uint32.
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Name of the xattr entry in the RADOS object.
|
||||||
|
XattrName = "csi.ceph.com/rt-version"
|
||||||
|
|
||||||
|
// SizeBytes is the size of version in bytes.
|
||||||
|
SizeBytes = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToBytes(v uint32) []byte {
|
||||||
|
bs := make([]byte, SizeBytes)
|
||||||
|
binary.BigEndian.PutUint32(bs, v)
|
||||||
|
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromBytes(bs []byte) (uint32, error) {
|
||||||
|
if len(bs) != SizeBytes {
|
||||||
|
return 0, errors.UnexpectedReadSize(SizeBytes, len(bs))
|
||||||
|
}
|
||||||
|
|
||||||
|
return binary.BigEndian.Uint32(bs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Read(ioctx radoswrapper.IOContextW, rtName string) (uint32, error) {
|
||||||
|
verBytes := make([]byte, SizeBytes)
|
||||||
|
readSize, err := ioctx.GetXattr(rtName, XattrName, verBytes)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if readSize != SizeBytes {
|
||||||
|
return 0, errors.UnexpectedReadSize(SizeBytes, readSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FromBytes(verBytes)
|
||||||
|
}
|
111
internal/util/reftracker/version/version_test.go
Normal file
111
internal/util/reftracker/version/version_test.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Ceph-CSI Authors.
|
||||||
|
|
||||||
|
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 version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/internal/util/reftracker/radoswrapper"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
v1Bytes = []byte{0, 0, 0, 1}
|
||||||
|
v1Value = uint32(1)
|
||||||
|
|
||||||
|
wrongSizeVersionBytes = []byte{0, 0, 1}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersionBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("ToBytes", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
bs := ToBytes(v1Value)
|
||||||
|
assert.Equal(ts, v1Bytes, bs)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FromBytes", func(ts *testing.T) {
|
||||||
|
ts.Parallel()
|
||||||
|
|
||||||
|
ver, err := FromBytes(v1Bytes)
|
||||||
|
assert.NoError(ts, err)
|
||||||
|
assert.Equal(ts, v1Value, ver)
|
||||||
|
|
||||||
|
_, err = FromBytes(wrongSizeVersionBytes)
|
||||||
|
assert.Error(ts, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionRead(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const rtName = "hello-rt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
validObj = radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Oid: rtName,
|
||||||
|
Xattrs: map[string][]byte{
|
||||||
|
XattrName: v1Bytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
invalidObjs = []*radoswrapper.FakeIOContext{
|
||||||
|
// Missing object.
|
||||||
|
radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{},
|
||||||
|
}),
|
||||||
|
// Missing xattr.
|
||||||
|
radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Oid: rtName,
|
||||||
|
Xattrs: map[string][]byte{
|
||||||
|
"some-other-xattr": v1Bytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// Wrongly sized version value.
|
||||||
|
radoswrapper.NewFakeIOContext(&radoswrapper.FakeRados{
|
||||||
|
Objs: map[string]*radoswrapper.FakeObj{
|
||||||
|
rtName: {
|
||||||
|
Oid: rtName,
|
||||||
|
Xattrs: map[string][]byte{
|
||||||
|
XattrName: wrongSizeVersionBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
ver, err := Read(validObj, rtName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, v1Value, ver)
|
||||||
|
|
||||||
|
for i := range invalidObjs {
|
||||||
|
_, err = Read(invalidObjs[i], rtName)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
@ -168,14 +168,14 @@ func GetTopologyFromRequest(
|
|||||||
return &topologyPools, accessibilityRequirements, nil
|
return &topologyPools, accessibilityRequirements, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchTopologyForPool returns the topology map, if the passed in pool matches any
|
// MatchPoolAndTopology returns the topology map, if the passed in pool matches any
|
||||||
// passed in accessibility constraints.
|
// passed in accessibility constraints.
|
||||||
func MatchTopologyForPool(topologyPools *[]TopologyConstrainedPool,
|
func MatchPoolAndTopology(topologyPools *[]TopologyConstrainedPool,
|
||||||
accessibilityRequirements *csi.TopologyRequirement, poolName string) (map[string]string, error) {
|
accessibilityRequirements *csi.TopologyRequirement, poolName string) (string, string, map[string]string, error) {
|
||||||
var topologyPool []TopologyConstrainedPool
|
var topologyPool []TopologyConstrainedPool
|
||||||
|
|
||||||
if topologyPools == nil || accessibilityRequirements == nil {
|
if topologyPools == nil || accessibilityRequirements == nil {
|
||||||
return nil, nil
|
return "", "", nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the pool in the list of topology based pools
|
// find the pool in the list of topology based pools
|
||||||
@ -187,13 +187,11 @@ func MatchTopologyForPool(topologyPools *[]TopologyConstrainedPool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(topologyPool) == 0 {
|
if len(topologyPool) == 0 {
|
||||||
return nil, fmt.Errorf("none of the configured topology pools (%+v) matched passed in pool name (%s)",
|
return "", "", nil, fmt.Errorf("none of the configured topology pools (%+v) matched passed in pool name (%s)",
|
||||||
topologyPools, poolName)
|
topologyPools, poolName)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, topology, err := FindPoolAndTopology(&topologyPool, accessibilityRequirements)
|
return FindPoolAndTopology(&topologyPool, accessibilityRequirements)
|
||||||
|
|
||||||
return topology, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindPoolAndTopology loops through passed in "topologyPools" and also related
|
// FindPoolAndTopology loops through passed in "topologyPools" and also related
|
||||||
|
@ -37,7 +37,7 @@ func checkAndReportError(t *testing.T, msg string, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFindPoolAndTopology also tests MatchTopologyForPool.
|
// TestFindPoolAndTopology also tests MatchPoolAndTopology.
|
||||||
func TestFindPoolAndTopology(t *testing.T) {
|
func TestFindPoolAndTopology(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
var err error
|
var err error
|
||||||
@ -319,15 +319,15 @@ func TestFindPoolAndTopology(t *testing.T) {
|
|||||||
t.Errorf("expected data pool to be named ec-%s, got %s", poolName, dataPoolName)
|
t.Errorf("expected data pool to be named ec-%s, got %s", poolName, dataPoolName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST: MatchTopologyForPool
|
// TEST: MatchPoolAndTopology
|
||||||
// check for non-existent pool
|
// check for non-existent pool
|
||||||
_, err = MatchTopologyForPool(&validMultipleTopoPools, &validAccReq, pool1+"fuzz")
|
_, _, _, err = MatchPoolAndTopology(&validMultipleTopoPools, &validAccReq, pool1+"fuzz")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected failure due to non-existent pool name (%s) got success", pool1+"fuzz")
|
t.Errorf("expected failure due to non-existent pool name (%s) got success", pool1+"fuzz")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for existing pool
|
// check for existing pool
|
||||||
topoSegment, err = MatchTopologyForPool(&validMultipleTopoPools, &validAccReq, pool1)
|
_, _, topoSegment, err = MatchPoolAndTopology(&validMultipleTopoPools, &validAccReq, pool1)
|
||||||
err = checkOutput(err, pool1, topoSegment)
|
err = checkOutput(err, pool1, topoSegment)
|
||||||
checkAndReportError(t, "expected success got:", err)
|
checkAndReportError(t, "expected success got:", err)
|
||||||
}
|
}
|
||||||
|
@ -308,6 +308,18 @@ func IsMountPoint(p string) (bool, error) {
|
|||||||
return !notMnt, nil
|
return !notMnt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsCorruptedMountError checks if the given error is a result of a corrupted
|
||||||
|
// mountpoint.
|
||||||
|
func IsCorruptedMountError(err error) bool {
|
||||||
|
return mount.IsCorruptedMnt(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMountInfoForProc reads /proc/<PID>/mountpoint and marshals it into
|
||||||
|
// MountInfo structs.
|
||||||
|
func ReadMountInfoForProc(proc string) ([]mount.MountInfo, error) {
|
||||||
|
return mount.ParseMountInfo(fmt.Sprintf("/proc/%s/mountinfo", proc))
|
||||||
|
}
|
||||||
|
|
||||||
// Mount mounts the source to target path.
|
// Mount mounts the source to target path.
|
||||||
func Mount(source, target, fstype string, options []string) error {
|
func Mount(source, target, fstype string, options []string) error {
|
||||||
dummyMount := mount.New("")
|
dummyMount := mount.New("")
|
||||||
|
@ -29,6 +29,8 @@ RUN dnf -y install \
|
|||||||
librados-devel \
|
librados-devel \
|
||||||
librbd-devel \
|
librbd-devel \
|
||||||
&& dnf -y update \
|
&& dnf -y update \
|
||||||
|
&& dnf clean all \
|
||||||
|
&& rm -rf /var/cache/yum \
|
||||||
&& true
|
&& true
|
||||||
|
|
||||||
WORKDIR "/go/src/github.com/ceph/ceph-csi"
|
WORKDIR "/go/src/github.com/ceph/ceph-csi"
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
# options for analysis running
|
# options for analysis running
|
||||||
run:
|
run:
|
||||||
build-tags:
|
build-tags:
|
||||||
- @@CEPH_VERSION@@
|
@@BUILD_TAGS@@
|
||||||
|
|
||||||
# default concurrency is a available CPU number
|
# default concurrency is a available CPU number
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
@ -169,7 +169,11 @@ linters:
|
|||||||
- funlen
|
- funlen
|
||||||
- testpackage
|
- testpackage
|
||||||
- exhaustivestruct
|
- exhaustivestruct
|
||||||
# TODO: enable goerr113, see: https://github.com/ceph/ceph-csi/issues/1227
|
# This requires extra addition of unnecessary code. Hence, we
|
||||||
|
# prefer to disable this linter. But, it can be enabled if we
|
||||||
|
# have better resolution. For more details check the
|
||||||
|
# issue below.
|
||||||
|
# see: https://github.com/ceph/ceph-csi/issues/1227
|
||||||
- goerr113
|
- goerr113
|
||||||
- forbidigo
|
- forbidigo
|
||||||
# TODO: enable gomoddirectives
|
# TODO: enable gomoddirectives
|
||||||
|
@ -124,6 +124,21 @@ function validate_container_cmd() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# validate csi sidecar image version
|
||||||
|
function validate_sidecar() {
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "${SCRIPT_DIR}/../build.env"
|
||||||
|
|
||||||
|
sidecars=(CSI_ATTACHER_VERSION CSI_SNAPSHOTTER_VERSION CSI_PROVISIONER_VERSION CSI_RESIZER_VERSION CSI_NODE_DRIVER_REGISTRAR_VERSION)
|
||||||
|
for sidecar in "${sidecars[@]}"; do
|
||||||
|
if [[ -z "${!sidecar}" ]]; then
|
||||||
|
echo "${sidecar}" version is empty, make sure build.env has set this sidecar version
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# Storage providers and the default storage class is not needed for Ceph-CSI
|
# Storage providers and the default storage class is not needed for Ceph-CSI
|
||||||
# testing. In order to reduce resources and potential conflicts between storage
|
# testing. In order to reduce resources and potential conflicts between storage
|
||||||
# plugins, disable them.
|
# plugins, disable them.
|
||||||
@ -161,13 +176,6 @@ else
|
|||||||
DISK_CONFIG=""
|
DISK_CONFIG=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#configure csi sidecar version
|
|
||||||
CSI_ATTACHER_VERSION=${CSI_ATTACHER_VERSION:-"v3.2.1"}
|
|
||||||
CSI_SNAPSHOTTER_VERSION=${CSI_SNAPSHOTTER_VERSION:-"v4.1.1"}
|
|
||||||
CSI_PROVISIONER_VERSION=${CSI_PROVISIONER_VERSION:-"v2.2.2"}
|
|
||||||
CSI_RESIZER_VERSION=${CSI_RESIZER_VERSION:-"v1.2.0"}
|
|
||||||
CSI_NODE_DRIVER_REGISTRAR_VERSION=${CSI_NODE_DRIVER_REGISTRAR_VERSION:-"v2.2.0"}
|
|
||||||
|
|
||||||
# configure csi image version
|
# configure csi image version
|
||||||
CSI_IMAGE_VERSION=${CSI_IMAGE_VERSION:-"canary"}
|
CSI_IMAGE_VERSION=${CSI_IMAGE_VERSION:-"canary"}
|
||||||
|
|
||||||
@ -290,6 +298,8 @@ cephcsi)
|
|||||||
copy_image_to_cluster "${CEPHCSI_IMAGE_REPO}"/cephcsi:"${CSI_IMAGE_VERSION}" "${CEPHCSI_IMAGE_REPO}"/cephcsi:"${CSI_IMAGE_VERSION}"
|
copy_image_to_cluster "${CEPHCSI_IMAGE_REPO}"/cephcsi:"${CSI_IMAGE_VERSION}" "${CEPHCSI_IMAGE_REPO}"/cephcsi:"${CSI_IMAGE_VERSION}"
|
||||||
;;
|
;;
|
||||||
k8s-sidecar)
|
k8s-sidecar)
|
||||||
|
echo "validating sidecar's image version"
|
||||||
|
validate_sidecar
|
||||||
echo "copying the kubernetes sidecar images"
|
echo "copying the kubernetes sidecar images"
|
||||||
copy_image_to_cluster "${K8S_IMAGE_REPO}/csi-attacher:${CSI_ATTACHER_VERSION}" "${K8S_IMAGE_REPO}/csi-attacher:${CSI_ATTACHER_VERSION}"
|
copy_image_to_cluster "${K8S_IMAGE_REPO}/csi-attacher:${CSI_ATTACHER_VERSION}" "${K8S_IMAGE_REPO}/csi-attacher:${CSI_ATTACHER_VERSION}"
|
||||||
copy_image_to_cluster "${K8S_IMAGE_REPO}/csi-snapshotter:${CSI_SNAPSHOTTER_VERSION}" "${K8S_IMAGE_REPO}/csi-snapshotter:${CSI_SNAPSHOTTER_VERSION}"
|
copy_image_to_cluster "${K8S_IMAGE_REPO}/csi-snapshotter:${CSI_SNAPSHOTTER_VERSION}" "${K8S_IMAGE_REPO}/csi-snapshotter:${CSI_SNAPSHOTTER_VERSION}"
|
||||||
|
@ -19,8 +19,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ceph/ceph-csi/api/deploy/kubernetes/nfs"
|
||||||
"github.com/ceph/ceph-csi/api/deploy/kubernetes/rbd"
|
"github.com/ceph/ceph-csi/api/deploy/kubernetes/rbd"
|
||||||
"github.com/ceph/ceph-csi/api/deploy/ocp"
|
"github.com/ceph/ceph-csi/api/deploy/ocp"
|
||||||
)
|
)
|
||||||
@ -46,6 +48,11 @@ var yamlArtifacts = []deploymentArtifact{
|
|||||||
reflect.ValueOf(ocp.NewSecurityContextConstraintsYAML),
|
reflect.ValueOf(ocp.NewSecurityContextConstraintsYAML),
|
||||||
reflect.ValueOf(ocp.SecurityContextConstraintsDefaults),
|
reflect.ValueOf(ocp.SecurityContextConstraintsDefaults),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"../deploy/nfs/kubernetes/csidriver.yaml",
|
||||||
|
reflect.ValueOf(nfs.NewCSIDriverYAML),
|
||||||
|
reflect.ValueOf(nfs.CSIDriverDefaults),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"../deploy/rbd/kubernetes/csidriver.yaml",
|
"../deploy/rbd/kubernetes/csidriver.yaml",
|
||||||
reflect.ValueOf(rbd.NewCSIDriverYAML),
|
reflect.ValueOf(rbd.NewCSIDriverYAML),
|
||||||
@ -67,6 +74,15 @@ func main() {
|
|||||||
func writeArtifact(artifact deploymentArtifact) {
|
func writeArtifact(artifact deploymentArtifact) {
|
||||||
fmt.Printf("creating %q...", artifact.filename)
|
fmt.Printf("creating %q...", artifact.filename)
|
||||||
|
|
||||||
|
dir := path.Dir(artifact.filename)
|
||||||
|
_, err := os.Stat(dir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(dir, 0o775)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to create directory %q: %v", dir, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.Create(artifact.filename)
|
f, err := os.Create(artifact.filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("failed to create file %q: %v", artifact.filename, err))
|
panic(fmt.Sprintf("failed to create file %q: %v", artifact.filename, err))
|
||||||
|
202
vendor/github.com/aws/aws-sdk-go-v2/LICENSE.txt
generated
vendored
Normal file
202
vendor/github.com/aws/aws-sdk-go-v2/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
3
vendor/github.com/aws/aws-sdk-go-v2/NOTICE.txt
generated
vendored
Normal file
3
vendor/github.com/aws/aws-sdk-go-v2/NOTICE.txt
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
AWS SDK for Go
|
||||||
|
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
Copyright 2014-2015 Stripe, Inc.
|
166
vendor/github.com/aws/aws-sdk-go-v2/aws/config.go
generated
vendored
Normal file
166
vendor/github.com/aws/aws-sdk-go-v2/aws/config.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go/logging"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPClient provides the interface to provide custom HTTPClients. Generally
|
||||||
|
// *http.Client is sufficient for most use cases. The HTTPClient should not
|
||||||
|
// follow redirects.
|
||||||
|
type HTTPClient interface {
|
||||||
|
Do(*http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Config provides service configuration for service clients.
|
||||||
|
type Config struct {
|
||||||
|
// The region to send requests to. This parameter is required and must
|
||||||
|
// be configured globally or on a per-client basis unless otherwise
|
||||||
|
// noted. A full list of regions is found in the "Regions and Endpoints"
|
||||||
|
// document.
|
||||||
|
//
|
||||||
|
// See http://docs.aws.amazon.com/general/latest/gr/rande.html for
|
||||||
|
// information on AWS regions.
|
||||||
|
Region string
|
||||||
|
|
||||||
|
// The credentials object to use when signing requests. Defaults to a
|
||||||
|
// chain of credential providers to search for credentials in environment
|
||||||
|
// variables, shared credential file, and EC2 Instance Roles.
|
||||||
|
Credentials CredentialsProvider
|
||||||
|
|
||||||
|
// The HTTP Client the SDK's API clients will use to invoke HTTP requests.
|
||||||
|
// The SDK defaults to a BuildableClient allowing API clients to create
|
||||||
|
// copies of the HTTP Client for service specific customizations.
|
||||||
|
//
|
||||||
|
// Use a (*http.Client) for custom behavior. Using a custom http.Client
|
||||||
|
// will prevent the SDK from modifying the HTTP client.
|
||||||
|
HTTPClient HTTPClient
|
||||||
|
|
||||||
|
// An endpoint resolver that can be used to provide or override an endpoint
|
||||||
|
// for the given service and region.
|
||||||
|
//
|
||||||
|
// See the `aws.EndpointResolver` documentation for additional usage
|
||||||
|
// information.
|
||||||
|
//
|
||||||
|
// Deprecated: See Config.EndpointResolverWithOptions
|
||||||
|
EndpointResolver EndpointResolver
|
||||||
|
|
||||||
|
// An endpoint resolver that can be used to provide or override an endpoint
|
||||||
|
// for the given service and region.
|
||||||
|
//
|
||||||
|
// When EndpointResolverWithOptions is specified, it will be used by a
|
||||||
|
// service client rather than using EndpointResolver if also specified.
|
||||||
|
//
|
||||||
|
// See the `aws.EndpointResolverWithOptions` documentation for additional
|
||||||
|
// usage information.
|
||||||
|
EndpointResolverWithOptions EndpointResolverWithOptions
|
||||||
|
|
||||||
|
// RetryMaxAttempts specifies the maximum number attempts an API client
|
||||||
|
// will call an operation that fails with a retryable error.
|
||||||
|
//
|
||||||
|
// API Clients will only use this value to construct a retryer if the
|
||||||
|
// Config.Retryer member is not nil. This value will be ignored if
|
||||||
|
// Retryer is not nil.
|
||||||
|
RetryMaxAttempts int
|
||||||
|
|
||||||
|
// RetryMode specifies the retry model the API client will be created with.
|
||||||
|
//
|
||||||
|
// API Clients will only use this value to construct a retryer if the
|
||||||
|
// Config.Retryer member is not nil. This value will be ignored if
|
||||||
|
// Retryer is not nil.
|
||||||
|
RetryMode RetryMode
|
||||||
|
|
||||||
|
// Retryer is a function that provides a Retryer implementation. A Retryer
|
||||||
|
// guides how HTTP requests should be retried in case of recoverable
|
||||||
|
// failures. When nil the API client will use a default retryer.
|
||||||
|
//
|
||||||
|
// In general, the provider function should return a new instance of a
|
||||||
|
// Retryer if you are attempting to provide a consistent Retryer
|
||||||
|
// configuration across all clients. This will ensure that each client will
|
||||||
|
// be provided a new instance of the Retryer implementation, and will avoid
|
||||||
|
// issues such as sharing the same retry token bucket across services.
|
||||||
|
//
|
||||||
|
// If not nil, RetryMaxAttempts, and RetryMode will be ignored by API
|
||||||
|
// clients.
|
||||||
|
Retryer func() Retryer
|
||||||
|
|
||||||
|
// ConfigSources are the sources that were used to construct the Config.
|
||||||
|
// Allows for additional configuration to be loaded by clients.
|
||||||
|
ConfigSources []interface{}
|
||||||
|
|
||||||
|
// APIOptions provides the set of middleware mutations modify how the API
|
||||||
|
// client requests will be handled. This is useful for adding additional
|
||||||
|
// tracing data to a request, or changing behavior of the SDK's client.
|
||||||
|
APIOptions []func(*middleware.Stack) error
|
||||||
|
|
||||||
|
// The logger writer interface to write logging messages to. Defaults to
|
||||||
|
// standard error.
|
||||||
|
Logger logging.Logger
|
||||||
|
|
||||||
|
// Configures the events that will be sent to the configured logger. This
|
||||||
|
// can be used to configure the logging of signing, retries, request, and
|
||||||
|
// responses of the SDK clients.
|
||||||
|
//
|
||||||
|
// See the ClientLogMode type documentation for the complete set of logging
|
||||||
|
// modes and available configuration.
|
||||||
|
ClientLogMode ClientLogMode
|
||||||
|
|
||||||
|
// The configured DefaultsMode. If not specified, service clients will
|
||||||
|
// default to legacy.
|
||||||
|
//
|
||||||
|
// Supported modes are: auto, cross-region, in-region, legacy, mobile,
|
||||||
|
// standard
|
||||||
|
DefaultsMode DefaultsMode
|
||||||
|
|
||||||
|
// The RuntimeEnvironment configuration, only populated if the DefaultsMode
|
||||||
|
// is set to DefaultsModeAuto and is initialized by
|
||||||
|
// `config.LoadDefaultConfig`. You should not populate this structure
|
||||||
|
// programmatically, or rely on the values here within your applications.
|
||||||
|
RuntimeEnvironment RuntimeEnvironment
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig returns a new Config pointer that can be chained with builder
|
||||||
|
// methods to set multiple configuration values inline without using pointers.
|
||||||
|
func NewConfig() *Config {
|
||||||
|
return &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy will return a shallow copy of the Config object. If any additional
|
||||||
|
// configurations are provided they will be merged into the new config returned.
|
||||||
|
func (c Config) Copy() Config {
|
||||||
|
cp := c
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointDiscoveryEnableState indicates if endpoint discovery is
|
||||||
|
// enabled, disabled, auto or unset state.
|
||||||
|
//
|
||||||
|
// Default behavior (Auto or Unset) indicates operations that require endpoint
|
||||||
|
// discovery will use Endpoint Discovery by default. Operations that
|
||||||
|
// optionally use Endpoint Discovery will not use Endpoint Discovery
|
||||||
|
// unless EndpointDiscovery is explicitly enabled.
|
||||||
|
type EndpointDiscoveryEnableState uint
|
||||||
|
|
||||||
|
// Enumeration values for EndpointDiscoveryEnableState
|
||||||
|
const (
|
||||||
|
// EndpointDiscoveryUnset represents EndpointDiscoveryEnableState is unset.
|
||||||
|
// Users do not need to use this value explicitly. The behavior for unset
|
||||||
|
// is the same as for EndpointDiscoveryAuto.
|
||||||
|
EndpointDiscoveryUnset EndpointDiscoveryEnableState = iota
|
||||||
|
|
||||||
|
// EndpointDiscoveryAuto represents an AUTO state that allows endpoint
|
||||||
|
// discovery only when required by the api. This is the default
|
||||||
|
// configuration resolved by the client if endpoint discovery is neither
|
||||||
|
// enabled or disabled.
|
||||||
|
EndpointDiscoveryAuto // default state
|
||||||
|
|
||||||
|
// EndpointDiscoveryDisabled indicates client MUST not perform endpoint
|
||||||
|
// discovery even when required.
|
||||||
|
EndpointDiscoveryDisabled
|
||||||
|
|
||||||
|
// EndpointDiscoveryEnabled indicates client MUST always perform endpoint
|
||||||
|
// discovery if supported for the operation.
|
||||||
|
EndpointDiscoveryEnabled
|
||||||
|
)
|
22
vendor/github.com/aws/aws-sdk-go-v2/aws/context.go
generated
vendored
Normal file
22
vendor/github.com/aws/aws-sdk-go-v2/aws/context.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type suppressedContext struct {
|
||||||
|
context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suppressedContext) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suppressedContext) Done() <-chan struct{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suppressedContext) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
139
vendor/github.com/aws/aws-sdk-go-v2/aws/credential_cache.go
generated
vendored
Normal file
139
vendor/github.com/aws/aws-sdk-go-v2/aws/credential_cache.go
generated
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkrand "github.com/aws/aws-sdk-go-v2/internal/rand"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sync/singleflight"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CredentialsCacheOptions are the options
|
||||||
|
type CredentialsCacheOptions struct {
|
||||||
|
|
||||||
|
// ExpiryWindow will allow the credentials to trigger refreshing prior to
|
||||||
|
// the credentials actually expiring. This is beneficial so race conditions
|
||||||
|
// with expiring credentials do not cause request to fail unexpectedly
|
||||||
|
// due to ExpiredTokenException exceptions.
|
||||||
|
//
|
||||||
|
// An ExpiryWindow of 10s would cause calls to IsExpired() to return true
|
||||||
|
// 10 seconds before the credentials are actually expired. This can cause an
|
||||||
|
// increased number of requests to refresh the credentials to occur.
|
||||||
|
//
|
||||||
|
// If ExpiryWindow is 0 or less it will be ignored.
|
||||||
|
ExpiryWindow time.Duration
|
||||||
|
|
||||||
|
// ExpiryWindowJitterFrac provides a mechanism for randomizing the expiration of credentials
|
||||||
|
// within the configured ExpiryWindow by a random percentage. Valid values are between 0.0 and 1.0.
|
||||||
|
//
|
||||||
|
// As an example if ExpiryWindow is 60 seconds and ExpiryWindowJitterFrac is 0.5 then credentials will be set to
|
||||||
|
// expire between 30 to 60 seconds prior to their actual expiration time.
|
||||||
|
//
|
||||||
|
// If ExpiryWindow is 0 or less then ExpiryWindowJitterFrac is ignored.
|
||||||
|
// If ExpiryWindowJitterFrac is 0 then no randomization will be applied to the window.
|
||||||
|
// If ExpiryWindowJitterFrac < 0 the value will be treated as 0.
|
||||||
|
// If ExpiryWindowJitterFrac > 1 the value will be treated as 1.
|
||||||
|
ExpiryWindowJitterFrac float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CredentialsCache provides caching and concurrency safe credentials retrieval
|
||||||
|
// via the provider's retrieve method.
|
||||||
|
type CredentialsCache struct {
|
||||||
|
// provider is the CredentialProvider implementation to be wrapped by the CredentialCache.
|
||||||
|
provider CredentialsProvider
|
||||||
|
|
||||||
|
options CredentialsCacheOptions
|
||||||
|
creds atomic.Value
|
||||||
|
sf singleflight.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCredentialsCache returns a CredentialsCache that wraps provider. Provider is expected to not be nil. A variadic
|
||||||
|
// list of one or more functions can be provided to modify the CredentialsCache configuration. This allows for
|
||||||
|
// configuration of credential expiry window and jitter.
|
||||||
|
func NewCredentialsCache(provider CredentialsProvider, optFns ...func(options *CredentialsCacheOptions)) *CredentialsCache {
|
||||||
|
options := CredentialsCacheOptions{}
|
||||||
|
|
||||||
|
for _, fn := range optFns {
|
||||||
|
fn(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ExpiryWindow < 0 {
|
||||||
|
options.ExpiryWindow = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ExpiryWindowJitterFrac < 0 {
|
||||||
|
options.ExpiryWindowJitterFrac = 0
|
||||||
|
} else if options.ExpiryWindowJitterFrac > 1 {
|
||||||
|
options.ExpiryWindowJitterFrac = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CredentialsCache{
|
||||||
|
provider: provider,
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve returns the credentials. If the credentials have already been
|
||||||
|
// retrieved, and not expired the cached credentials will be returned. If the
|
||||||
|
// credentials have not been retrieved yet, or expired the provider's Retrieve
|
||||||
|
// method will be called.
|
||||||
|
//
|
||||||
|
// Returns and error if the provider's retrieve method returns an error.
|
||||||
|
func (p *CredentialsCache) Retrieve(ctx context.Context) (Credentials, error) {
|
||||||
|
if creds := p.getCreds(); creds != nil {
|
||||||
|
return *creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resCh := p.sf.DoChan("", func() (interface{}, error) {
|
||||||
|
return p.singleRetrieve(&suppressedContext{ctx})
|
||||||
|
})
|
||||||
|
select {
|
||||||
|
case res := <-resCh:
|
||||||
|
return res.Val.(Credentials), res.Err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return Credentials{}, &RequestCanceledError{Err: ctx.Err()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CredentialsCache) singleRetrieve(ctx context.Context) (interface{}, error) {
|
||||||
|
if creds := p.getCreds(); creds != nil {
|
||||||
|
return *creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := p.provider.Retrieve(ctx)
|
||||||
|
if err == nil {
|
||||||
|
if creds.CanExpire {
|
||||||
|
randFloat64, err := sdkrand.CryptoRandFloat64()
|
||||||
|
if err != nil {
|
||||||
|
return Credentials{}, err
|
||||||
|
}
|
||||||
|
jitter := time.Duration(randFloat64 * p.options.ExpiryWindowJitterFrac * float64(p.options.ExpiryWindow))
|
||||||
|
creds.Expires = creds.Expires.Add(-(p.options.ExpiryWindow - jitter))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.creds.Store(&creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
return creds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CredentialsCache) getCreds() *Credentials {
|
||||||
|
v := p.creds.Load()
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c := v.(*Credentials)
|
||||||
|
if c != nil && c.HasKeys() && !c.Expired() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate will invalidate the cached credentials. The next call to Retrieve
|
||||||
|
// will cause the provider's Retrieve method to be called.
|
||||||
|
func (p *CredentialsCache) Invalidate() {
|
||||||
|
p.creds.Store((*Credentials)(nil))
|
||||||
|
}
|
127
vendor/github.com/aws/aws-sdk-go-v2/aws/credentials.go
generated
vendored
Normal file
127
vendor/github.com/aws/aws-sdk-go-v2/aws/credentials.go
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnonymousCredentials provides a sentinel CredentialsProvider that should be
|
||||||
|
// used to instruct the SDK's signing middleware to not sign the request.
|
||||||
|
//
|
||||||
|
// Using `nil` credentials when configuring an API client will achieve the same
|
||||||
|
// result. The AnonymousCredentials type allows you to configure the SDK's
|
||||||
|
// external config loading to not attempt to source credentials from the shared
|
||||||
|
// config or environment.
|
||||||
|
//
|
||||||
|
// For example you can use this CredentialsProvider with an API client's
|
||||||
|
// Options to instruct the client not to sign a request for accessing public
|
||||||
|
// S3 bucket objects.
|
||||||
|
//
|
||||||
|
// The following example demonstrates using the AnonymousCredentials to prevent
|
||||||
|
// SDK's external config loading attempt to resolve credentials.
|
||||||
|
//
|
||||||
|
// cfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||||
|
// config.WithCredentialsProvider(aws.AnonymousCredentials{}),
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to load config, %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client := s3.NewFromConfig(cfg)
|
||||||
|
//
|
||||||
|
// Alternatively you can leave the API client Option's `Credential` member to
|
||||||
|
// nil. If using the `NewFromConfig` constructor you'll need to explicitly set
|
||||||
|
// the `Credentials` member to nil, if the external config resolved a
|
||||||
|
// credential provider.
|
||||||
|
//
|
||||||
|
// client := s3.New(s3.Options{
|
||||||
|
// // Credentials defaults to a nil value.
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// This can also be configured for specific operations calls too.
|
||||||
|
//
|
||||||
|
// cfg, err := config.LoadDefaultConfig(context.TODO())
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to load config, %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client := s3.NewFromConfig(config)
|
||||||
|
//
|
||||||
|
// result, err := client.GetObject(context.TODO(), s3.GetObject{
|
||||||
|
// Bucket: aws.String("example-bucket"),
|
||||||
|
// Key: aws.String("example-key"),
|
||||||
|
// }, func(o *s3.Options) {
|
||||||
|
// o.Credentials = nil
|
||||||
|
// // Or
|
||||||
|
// o.Credentials = aws.AnonymousCredentials{}
|
||||||
|
// })
|
||||||
|
type AnonymousCredentials struct{}
|
||||||
|
|
||||||
|
// Retrieve implements the CredentialsProvider interface, but will always
|
||||||
|
// return error, and cannot be used to sign a request. The AnonymousCredentials
|
||||||
|
// type is used as a sentinel type instructing the AWS request signing
|
||||||
|
// middleware to not sign a request.
|
||||||
|
func (AnonymousCredentials) Retrieve(context.Context) (Credentials, error) {
|
||||||
|
return Credentials{Source: "AnonymousCredentials"},
|
||||||
|
fmt.Errorf("the AnonymousCredentials is not a valid credential provider, and cannot be used to sign AWS requests with")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Credentials is the AWS credentials value for individual credential fields.
|
||||||
|
type Credentials struct {
|
||||||
|
// AWS Access key ID
|
||||||
|
AccessKeyID string
|
||||||
|
|
||||||
|
// AWS Secret Access Key
|
||||||
|
SecretAccessKey string
|
||||||
|
|
||||||
|
// AWS Session Token
|
||||||
|
SessionToken string
|
||||||
|
|
||||||
|
// Source of the credentials
|
||||||
|
Source string
|
||||||
|
|
||||||
|
// Time the credentials will expire.
|
||||||
|
CanExpire bool
|
||||||
|
Expires time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired returns if the credentials have expired.
|
||||||
|
func (v Credentials) Expired() bool {
|
||||||
|
if v.CanExpire {
|
||||||
|
// Calling Round(0) on the current time will truncate the monotonic reading only. Ensures credential expiry
|
||||||
|
// time is always based on reported wall-clock time.
|
||||||
|
return !v.Expires.After(sdk.NowTime().Round(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasKeys returns if the credentials keys are set.
|
||||||
|
func (v Credentials) HasKeys() bool {
|
||||||
|
return len(v.AccessKeyID) > 0 && len(v.SecretAccessKey) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CredentialsProvider is the interface for any component which will provide
|
||||||
|
// credentials Credentials. A CredentialsProvider is required to manage its own
|
||||||
|
// Expired state, and what to be expired means.
|
||||||
|
//
|
||||||
|
// A credentials provider implementation can be wrapped with a CredentialCache
|
||||||
|
// to cache the credential value retrieved. Without the cache the SDK will
|
||||||
|
// attempt to retrieve the credentials for every request.
|
||||||
|
type CredentialsProvider interface {
|
||||||
|
// Retrieve returns nil if it successfully retrieved the value.
|
||||||
|
// Error is returned if the value were not obtainable, or empty.
|
||||||
|
Retrieve(ctx context.Context) (Credentials, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CredentialsProviderFunc provides a helper wrapping a function value to
|
||||||
|
// satisfy the CredentialsProvider interface.
|
||||||
|
type CredentialsProviderFunc func(context.Context) (Credentials, error)
|
||||||
|
|
||||||
|
// Retrieve delegates to the function value the CredentialsProviderFunc wraps.
|
||||||
|
func (fn CredentialsProviderFunc) Retrieve(ctx context.Context) (Credentials, error) {
|
||||||
|
return fn(ctx)
|
||||||
|
}
|
38
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/auto.go
generated
vendored
Normal file
38
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/auto.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package defaults
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var getGOOS = func() string {
|
||||||
|
return runtime.GOOS
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveDefaultsModeAuto is used to determine the effective aws.DefaultsMode when the mode
|
||||||
|
// is set to aws.DefaultsModeAuto.
|
||||||
|
func ResolveDefaultsModeAuto(region string, environment aws.RuntimeEnvironment) aws.DefaultsMode {
|
||||||
|
goos := getGOOS()
|
||||||
|
if goos == "android" || goos == "ios" {
|
||||||
|
return aws.DefaultsModeMobile
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentRegion string
|
||||||
|
if len(environment.EnvironmentIdentifier) > 0 {
|
||||||
|
currentRegion = environment.Region
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(currentRegion) == 0 && len(environment.EC2InstanceMetadataRegion) > 0 {
|
||||||
|
currentRegion = environment.EC2InstanceMetadataRegion
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(region) > 0 && len(currentRegion) > 0 {
|
||||||
|
if strings.EqualFold(region, currentRegion) {
|
||||||
|
return aws.DefaultsModeInRegion
|
||||||
|
}
|
||||||
|
return aws.DefaultsModeCrossRegion
|
||||||
|
}
|
||||||
|
|
||||||
|
return aws.DefaultsModeStandard
|
||||||
|
}
|
43
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/configuration.go
generated
vendored
Normal file
43
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/configuration.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package defaults
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configuration is the set of SDK configuration options that are determined based
|
||||||
|
// on the configured DefaultsMode.
|
||||||
|
type Configuration struct {
|
||||||
|
// RetryMode is the configuration's default retry mode API clients should
|
||||||
|
// use for constructing a Retryer.
|
||||||
|
RetryMode aws.RetryMode
|
||||||
|
|
||||||
|
// ConnectTimeout is the maximum amount of time a dial will wait for
|
||||||
|
// a connect to complete.
|
||||||
|
//
|
||||||
|
// See https://pkg.go.dev/net#Dialer.Timeout
|
||||||
|
ConnectTimeout *time.Duration
|
||||||
|
|
||||||
|
// TLSNegotiationTimeout specifies the maximum amount of time waiting to
|
||||||
|
// wait for a TLS handshake.
|
||||||
|
//
|
||||||
|
// See https://pkg.go.dev/net/http#Transport.TLSHandshakeTimeout
|
||||||
|
TLSNegotiationTimeout *time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConnectTimeout returns the ConnectTimeout value, returns false if the value is not set.
|
||||||
|
func (c *Configuration) GetConnectTimeout() (time.Duration, bool) {
|
||||||
|
if c.ConnectTimeout == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return *c.ConnectTimeout, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTLSNegotiationTimeout returns the TLSNegotiationTimeout value, returns false if the value is not set.
|
||||||
|
func (c *Configuration) GetTLSNegotiationTimeout() (time.Duration, bool) {
|
||||||
|
if c.TLSNegotiationTimeout == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return *c.TLSNegotiationTimeout, true
|
||||||
|
}
|
50
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/defaults.go
generated
vendored
Normal file
50
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/defaults.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Code generated by github.com/aws/aws-sdk-go-v2/internal/codegen/cmd/defaultsconfig. DO NOT EDIT.
|
||||||
|
|
||||||
|
package defaults
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModeConfiguration returns the default Configuration descriptor for the given mode.
|
||||||
|
//
|
||||||
|
// Supports the following modes: cross-region, in-region, mobile, standard
|
||||||
|
func GetModeConfiguration(mode aws.DefaultsMode) (Configuration, error) {
|
||||||
|
var mv aws.DefaultsMode
|
||||||
|
mv.SetFromString(string(mode))
|
||||||
|
|
||||||
|
switch mv {
|
||||||
|
case aws.DefaultsModeCrossRegion:
|
||||||
|
settings := Configuration{
|
||||||
|
ConnectTimeout: aws.Duration(3100 * time.Millisecond),
|
||||||
|
RetryMode: aws.RetryMode("standard"),
|
||||||
|
TLSNegotiationTimeout: aws.Duration(3100 * time.Millisecond),
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
case aws.DefaultsModeInRegion:
|
||||||
|
settings := Configuration{
|
||||||
|
ConnectTimeout: aws.Duration(1100 * time.Millisecond),
|
||||||
|
RetryMode: aws.RetryMode("standard"),
|
||||||
|
TLSNegotiationTimeout: aws.Duration(1100 * time.Millisecond),
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
case aws.DefaultsModeMobile:
|
||||||
|
settings := Configuration{
|
||||||
|
ConnectTimeout: aws.Duration(30000 * time.Millisecond),
|
||||||
|
RetryMode: aws.RetryMode("standard"),
|
||||||
|
TLSNegotiationTimeout: aws.Duration(30000 * time.Millisecond),
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
case aws.DefaultsModeStandard:
|
||||||
|
settings := Configuration{
|
||||||
|
ConnectTimeout: aws.Duration(3100 * time.Millisecond),
|
||||||
|
RetryMode: aws.RetryMode("standard"),
|
||||||
|
TLSNegotiationTimeout: aws.Duration(3100 * time.Millisecond),
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
default:
|
||||||
|
return Configuration{}, fmt.Errorf("unsupported defaults mode: %v", mode)
|
||||||
|
}
|
||||||
|
}
|
2
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/doc.go
generated
vendored
Normal file
2
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/doc.go
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// Package defaults provides recommended configuration values for AWS SDKs and CLIs.
|
||||||
|
package defaults
|
95
vendor/github.com/aws/aws-sdk-go-v2/aws/defaultsmode.go
generated
vendored
Normal file
95
vendor/github.com/aws/aws-sdk-go-v2/aws/defaultsmode.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Code generated by github.com/aws/aws-sdk-go-v2/internal/codegen/cmd/defaultsmode. DO NOT EDIT.
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultsMode is the SDK defaults mode setting.
|
||||||
|
type DefaultsMode string
|
||||||
|
|
||||||
|
// The DefaultsMode constants.
|
||||||
|
const (
|
||||||
|
// DefaultsModeAuto is an experimental mode that builds on the standard mode.
|
||||||
|
// The SDK will attempt to discover the execution environment to determine the
|
||||||
|
// appropriate settings automatically.
|
||||||
|
//
|
||||||
|
// Note that the auto detection is heuristics-based and does not guarantee 100%
|
||||||
|
// accuracy. STANDARD mode will be used if the execution environment cannot
|
||||||
|
// be determined. The auto detection might query EC2 Instance Metadata service
|
||||||
|
// (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html),
|
||||||
|
// which might introduce latency. Therefore we recommend choosing an explicit
|
||||||
|
// defaults_mode instead if startup latency is critical to your application
|
||||||
|
DefaultsModeAuto DefaultsMode = "auto"
|
||||||
|
|
||||||
|
// DefaultsModeCrossRegion builds on the standard mode and includes optimization
|
||||||
|
// tailored for applications which call AWS services in a different region
|
||||||
|
//
|
||||||
|
// Note that the default values vended from this mode might change as best practices
|
||||||
|
// may evolve. As a result, it is encouraged to perform tests when upgrading
|
||||||
|
// the SDK
|
||||||
|
DefaultsModeCrossRegion DefaultsMode = "cross-region"
|
||||||
|
|
||||||
|
// DefaultsModeInRegion builds on the standard mode and includes optimization
|
||||||
|
// tailored for applications which call AWS services from within the same AWS
|
||||||
|
// region
|
||||||
|
//
|
||||||
|
// Note that the default values vended from this mode might change as best practices
|
||||||
|
// may evolve. As a result, it is encouraged to perform tests when upgrading
|
||||||
|
// the SDK
|
||||||
|
DefaultsModeInRegion DefaultsMode = "in-region"
|
||||||
|
|
||||||
|
// DefaultsModeLegacy provides default settings that vary per SDK and were used
|
||||||
|
// prior to establishment of defaults_mode
|
||||||
|
DefaultsModeLegacy DefaultsMode = "legacy"
|
||||||
|
|
||||||
|
// DefaultsModeMobile builds on the standard mode and includes optimization
|
||||||
|
// tailored for mobile applications
|
||||||
|
//
|
||||||
|
// Note that the default values vended from this mode might change as best practices
|
||||||
|
// may evolve. As a result, it is encouraged to perform tests when upgrading
|
||||||
|
// the SDK
|
||||||
|
DefaultsModeMobile DefaultsMode = "mobile"
|
||||||
|
|
||||||
|
// DefaultsModeStandard provides the latest recommended default values that
|
||||||
|
// should be safe to run in most scenarios
|
||||||
|
//
|
||||||
|
// Note that the default values vended from this mode might change as best practices
|
||||||
|
// may evolve. As a result, it is encouraged to perform tests when upgrading
|
||||||
|
// the SDK
|
||||||
|
DefaultsModeStandard DefaultsMode = "standard"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetFromString sets the DefaultsMode value to one of the pre-defined constants that matches
|
||||||
|
// the provided string when compared using EqualFold. If the value does not match a known
|
||||||
|
// constant it will be set to as-is and the function will return false. As a special case, if the
|
||||||
|
// provided value is a zero-length string, the mode will be set to LegacyDefaultsMode.
|
||||||
|
func (d *DefaultsMode) SetFromString(v string) (ok bool) {
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(v, string(DefaultsModeAuto)):
|
||||||
|
*d = DefaultsModeAuto
|
||||||
|
ok = true
|
||||||
|
case strings.EqualFold(v, string(DefaultsModeCrossRegion)):
|
||||||
|
*d = DefaultsModeCrossRegion
|
||||||
|
ok = true
|
||||||
|
case strings.EqualFold(v, string(DefaultsModeInRegion)):
|
||||||
|
*d = DefaultsModeInRegion
|
||||||
|
ok = true
|
||||||
|
case strings.EqualFold(v, string(DefaultsModeLegacy)):
|
||||||
|
*d = DefaultsModeLegacy
|
||||||
|
ok = true
|
||||||
|
case strings.EqualFold(v, string(DefaultsModeMobile)):
|
||||||
|
*d = DefaultsModeMobile
|
||||||
|
ok = true
|
||||||
|
case strings.EqualFold(v, string(DefaultsModeStandard)):
|
||||||
|
*d = DefaultsModeStandard
|
||||||
|
ok = true
|
||||||
|
case len(v) == 0:
|
||||||
|
*d = DefaultsModeLegacy
|
||||||
|
ok = true
|
||||||
|
default:
|
||||||
|
*d = DefaultsMode(v)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
62
vendor/github.com/aws/aws-sdk-go-v2/aws/doc.go
generated
vendored
Normal file
62
vendor/github.com/aws/aws-sdk-go-v2/aws/doc.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Package aws provides the core SDK's utilities and shared types. Use this package's
|
||||||
|
// utilities to simplify setting and reading API operations parameters.
|
||||||
|
//
|
||||||
|
// Value and Pointer Conversion Utilities
|
||||||
|
//
|
||||||
|
// This package includes a helper conversion utility for each scalar type the SDK's
|
||||||
|
// API use. These utilities make getting a pointer of the scalar, and dereferencing
|
||||||
|
// a pointer easier.
|
||||||
|
//
|
||||||
|
// Each conversion utility comes in two forms. Value to Pointer and Pointer to Value.
|
||||||
|
// The Pointer to value will safely dereference the pointer and return its value.
|
||||||
|
// If the pointer was nil, the scalar's zero value will be returned.
|
||||||
|
//
|
||||||
|
// The value to pointer functions will be named after the scalar type. So get a
|
||||||
|
// *string from a string value use the "String" function. This makes it easy to
|
||||||
|
// to get pointer of a literal string value, because getting the address of a
|
||||||
|
// literal requires assigning the value to a variable first.
|
||||||
|
//
|
||||||
|
// var strPtr *string
|
||||||
|
//
|
||||||
|
// // Without the SDK's conversion functions
|
||||||
|
// str := "my string"
|
||||||
|
// strPtr = &str
|
||||||
|
//
|
||||||
|
// // With the SDK's conversion functions
|
||||||
|
// strPtr = aws.String("my string")
|
||||||
|
//
|
||||||
|
// // Convert *string to string value
|
||||||
|
// str = aws.ToString(strPtr)
|
||||||
|
//
|
||||||
|
// In addition to scalars the aws package also includes conversion utilities for
|
||||||
|
// map and slice for commonly types used in API parameters. The map and slice
|
||||||
|
// conversion functions use similar naming pattern as the scalar conversion
|
||||||
|
// functions.
|
||||||
|
//
|
||||||
|
// var strPtrs []*string
|
||||||
|
// var strs []string = []string{"Go", "Gophers", "Go"}
|
||||||
|
//
|
||||||
|
// // Convert []string to []*string
|
||||||
|
// strPtrs = aws.StringSlice(strs)
|
||||||
|
//
|
||||||
|
// // Convert []*string to []string
|
||||||
|
// strs = aws.ToStringSlice(strPtrs)
|
||||||
|
//
|
||||||
|
// SDK Default HTTP Client
|
||||||
|
//
|
||||||
|
// The SDK will use the http.DefaultClient if a HTTP client is not provided to
|
||||||
|
// the SDK's Session, or service client constructor. This means that if the
|
||||||
|
// http.DefaultClient is modified by other components of your application the
|
||||||
|
// modifications will be picked up by the SDK as well.
|
||||||
|
//
|
||||||
|
// In some cases this might be intended, but it is a better practice to create
|
||||||
|
// a custom HTTP Client to share explicitly through your application. You can
|
||||||
|
// configure the SDK to use the custom HTTP Client by setting the HTTPClient
|
||||||
|
// value of the SDK's Config type when creating a Session or service client.
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// generate.go uses a build tag of "ignore", go run doesn't need to specify
|
||||||
|
// this because go run ignores all build flags when running a go file directly.
|
||||||
|
//go:generate go run -tags codegen generate.go
|
||||||
|
//go:generate go run -tags codegen logging_generate.go
|
||||||
|
//go:generate gofmt -w -s .
|
229
vendor/github.com/aws/aws-sdk-go-v2/aws/endpoints.go
generated
vendored
Normal file
229
vendor/github.com/aws/aws-sdk-go-v2/aws/endpoints.go
generated
vendored
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DualStackEndpointState is a constant to describe the dual-stack endpoint resolution behavior.
|
||||||
|
type DualStackEndpointState uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DualStackEndpointStateUnset is the default value behavior for dual-stack endpoint resolution.
|
||||||
|
DualStackEndpointStateUnset DualStackEndpointState = iota
|
||||||
|
|
||||||
|
// DualStackEndpointStateEnabled enables dual-stack endpoint resolution for service endpoints.
|
||||||
|
DualStackEndpointStateEnabled
|
||||||
|
|
||||||
|
// DualStackEndpointStateDisabled disables dual-stack endpoint resolution for endpoints.
|
||||||
|
DualStackEndpointStateDisabled
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetUseDualStackEndpoint takes a service's EndpointResolverOptions and returns the UseDualStackEndpoint value.
|
||||||
|
// Returns boolean false if the provided options does not have a method to retrieve the DualStackEndpointState.
|
||||||
|
func GetUseDualStackEndpoint(options ...interface{}) (value DualStackEndpointState, found bool) {
|
||||||
|
type iface interface {
|
||||||
|
GetUseDualStackEndpoint() DualStackEndpointState
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
if i, ok := option.(iface); ok {
|
||||||
|
value = i.GetUseDualStackEndpoint()
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, found
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIPSEndpointState is a constant to describe the FIPS endpoint resolution behavior.
|
||||||
|
type FIPSEndpointState uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FIPSEndpointStateUnset is the default value behavior for FIPS endpoint resolution.
|
||||||
|
FIPSEndpointStateUnset FIPSEndpointState = iota
|
||||||
|
|
||||||
|
// FIPSEndpointStateEnabled enables FIPS endpoint resolution for service endpoints.
|
||||||
|
FIPSEndpointStateEnabled
|
||||||
|
|
||||||
|
// FIPSEndpointStateDisabled disables FIPS endpoint resolution for endpoints.
|
||||||
|
FIPSEndpointStateDisabled
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetUseFIPSEndpoint takes a service's EndpointResolverOptions and returns the UseDualStackEndpoint value.
|
||||||
|
// Returns boolean false if the provided options does not have a method to retrieve the DualStackEndpointState.
|
||||||
|
func GetUseFIPSEndpoint(options ...interface{}) (value FIPSEndpointState, found bool) {
|
||||||
|
type iface interface {
|
||||||
|
GetUseFIPSEndpoint() FIPSEndpointState
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
if i, ok := option.(iface); ok {
|
||||||
|
value = i.GetUseFIPSEndpoint()
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint represents the endpoint a service client should make API operation
|
||||||
|
// calls to.
|
||||||
|
//
|
||||||
|
// The SDK will automatically resolve these endpoints per API client using an
|
||||||
|
// internal endpoint resolvers. If you'd like to provide custom endpoint
|
||||||
|
// resolving behavior you can implement the EndpointResolver interface.
|
||||||
|
type Endpoint struct {
|
||||||
|
// The base URL endpoint the SDK API clients will use to make API calls to.
|
||||||
|
// The SDK will suffix URI path and query elements to this endpoint.
|
||||||
|
URL string
|
||||||
|
|
||||||
|
// Specifies if the endpoint's hostname can be modified by the SDK's API
|
||||||
|
// client.
|
||||||
|
//
|
||||||
|
// If the hostname is mutable the SDK API clients may modify any part of
|
||||||
|
// the hostname based on the requirements of the API, (e.g. adding, or
|
||||||
|
// removing content in the hostname). Such as, Amazon S3 API client
|
||||||
|
// prefixing "bucketname" to the hostname, or changing the
|
||||||
|
// hostname service name component from "s3." to "s3-accesspoint.dualstack."
|
||||||
|
// for the dualstack endpoint of an S3 Accesspoint resource.
|
||||||
|
//
|
||||||
|
// Care should be taken when providing a custom endpoint for an API. If the
|
||||||
|
// endpoint hostname is mutable, and the client cannot modify the endpoint
|
||||||
|
// correctly, the operation call will most likely fail, or have undefined
|
||||||
|
// behavior.
|
||||||
|
//
|
||||||
|
// If hostname is immutable, the SDK API clients will not modify the
|
||||||
|
// hostname of the URL. This may cause the API client not to function
|
||||||
|
// correctly if the API requires the operation specific hostname values
|
||||||
|
// to be used by the client.
|
||||||
|
//
|
||||||
|
// This flag does not modify the API client's behavior if this endpoint
|
||||||
|
// will be used instead of Endpoint Discovery, or if the endpoint will be
|
||||||
|
// used to perform Endpoint Discovery. That behavior is configured via the
|
||||||
|
// API Client's Options.
|
||||||
|
HostnameImmutable bool
|
||||||
|
|
||||||
|
// The AWS partition the endpoint belongs to.
|
||||||
|
PartitionID string
|
||||||
|
|
||||||
|
// The service name that should be used for signing the requests to the
|
||||||
|
// endpoint.
|
||||||
|
SigningName string
|
||||||
|
|
||||||
|
// The region that should be used for signing the request to the endpoint.
|
||||||
|
SigningRegion string
|
||||||
|
|
||||||
|
// The signing method that should be used for signing the requests to the
|
||||||
|
// endpoint.
|
||||||
|
SigningMethod string
|
||||||
|
|
||||||
|
// The source of the Endpoint. By default, this will be EndpointSourceServiceMetadata.
|
||||||
|
// When providing a custom endpoint, you should set the source as EndpointSourceCustom.
|
||||||
|
// If source is not provided when providing a custom endpoint, the SDK may not
|
||||||
|
// perform required host mutations correctly. Source should be used along with
|
||||||
|
// HostnameImmutable property as per the usage requirement.
|
||||||
|
Source EndpointSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointSource is the endpoint source type.
|
||||||
|
type EndpointSource int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EndpointSourceServiceMetadata denotes service modeled endpoint metadata is used as Endpoint Source.
|
||||||
|
EndpointSourceServiceMetadata EndpointSource = iota
|
||||||
|
|
||||||
|
// EndpointSourceCustom denotes endpoint is a custom endpoint. This source should be used when
|
||||||
|
// user provides a custom endpoint to be used by the SDK.
|
||||||
|
EndpointSourceCustom
|
||||||
|
)
|
||||||
|
|
||||||
|
// EndpointNotFoundError is a sentinel error to indicate that the
|
||||||
|
// EndpointResolver implementation was unable to resolve an endpoint for the
|
||||||
|
// given service and region. Resolvers should use this to indicate that an API
|
||||||
|
// client should fallback and attempt to use it's internal default resolver to
|
||||||
|
// resolve the endpoint.
|
||||||
|
type EndpointNotFoundError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is the error message.
|
||||||
|
func (e *EndpointNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("endpoint not found, %v", e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying error.
|
||||||
|
func (e *EndpointNotFoundError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointResolver is an endpoint resolver that can be used to provide or
|
||||||
|
// override an endpoint for the given service and region. API clients will
|
||||||
|
// attempt to use the EndpointResolver first to resolve an endpoint if
|
||||||
|
// available. If the EndpointResolver returns an EndpointNotFoundError error,
|
||||||
|
// API clients will fallback to attempting to resolve the endpoint using its
|
||||||
|
// internal default endpoint resolver.
|
||||||
|
//
|
||||||
|
// Deprecated: See EndpointResolverWithOptions
|
||||||
|
type EndpointResolver interface {
|
||||||
|
ResolveEndpoint(service, region string) (Endpoint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointResolverFunc wraps a function to satisfy the EndpointResolver interface.
|
||||||
|
//
|
||||||
|
// Deprecated: See EndpointResolverWithOptionsFunc
|
||||||
|
type EndpointResolverFunc func(service, region string) (Endpoint, error)
|
||||||
|
|
||||||
|
// ResolveEndpoint calls the wrapped function and returns the results.
|
||||||
|
//
|
||||||
|
// Deprecated: See EndpointResolverWithOptions.ResolveEndpoint
|
||||||
|
func (e EndpointResolverFunc) ResolveEndpoint(service, region string) (Endpoint, error) {
|
||||||
|
return e(service, region)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointResolverWithOptions is an endpoint resolver that can be used to provide or
|
||||||
|
// override an endpoint for the given service, region, and the service client's EndpointOptions. API clients will
|
||||||
|
// attempt to use the EndpointResolverWithOptions first to resolve an endpoint if
|
||||||
|
// available. If the EndpointResolverWithOptions returns an EndpointNotFoundError error,
|
||||||
|
// API clients will fallback to attempting to resolve the endpoint using its
|
||||||
|
// internal default endpoint resolver.
|
||||||
|
type EndpointResolverWithOptions interface {
|
||||||
|
ResolveEndpoint(service, region string, options ...interface{}) (Endpoint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointResolverWithOptionsFunc wraps a function to satisfy the EndpointResolverWithOptions interface.
|
||||||
|
type EndpointResolverWithOptionsFunc func(service, region string, options ...interface{}) (Endpoint, error)
|
||||||
|
|
||||||
|
// ResolveEndpoint calls the wrapped function and returns the results.
|
||||||
|
func (e EndpointResolverWithOptionsFunc) ResolveEndpoint(service, region string, options ...interface{}) (Endpoint, error) {
|
||||||
|
return e(service, region, options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisableHTTPS takes a service's EndpointResolverOptions and returns the DisableHTTPS value.
|
||||||
|
// Returns boolean false if the provided options does not have a method to retrieve the DisableHTTPS.
|
||||||
|
func GetDisableHTTPS(options ...interface{}) (value bool, found bool) {
|
||||||
|
type iface interface {
|
||||||
|
GetDisableHTTPS() bool
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
if i, ok := option.(iface); ok {
|
||||||
|
value = i.GetDisableHTTPS()
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, found
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResolvedRegion takes a service's EndpointResolverOptions and returns the ResolvedRegion value.
|
||||||
|
// Returns boolean false if the provided options does not have a method to retrieve the ResolvedRegion.
|
||||||
|
func GetResolvedRegion(options ...interface{}) (value string, found bool) {
|
||||||
|
type iface interface {
|
||||||
|
GetResolvedRegion() string
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
if i, ok := option.(iface); ok {
|
||||||
|
value = i.GetResolvedRegion()
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, found
|
||||||
|
}
|
9
vendor/github.com/aws/aws-sdk-go-v2/aws/errors.go
generated
vendored
Normal file
9
vendor/github.com/aws/aws-sdk-go-v2/aws/errors.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
// MissingRegionError is an error that is returned if region configuration
|
||||||
|
// value was not found.
|
||||||
|
type MissingRegionError struct{}
|
||||||
|
|
||||||
|
func (*MissingRegionError) Error() string {
|
||||||
|
return "an AWS region is required, but was not found"
|
||||||
|
}
|
365
vendor/github.com/aws/aws-sdk-go-v2/aws/from_ptr.go
generated
vendored
Normal file
365
vendor/github.com/aws/aws-sdk-go-v2/aws/from_ptr.go
generated
vendored
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
// Code generated by aws/generate.go DO NOT EDIT.
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/smithy-go/ptr"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToBool returns bool value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a bool zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToBool(p *bool) (v bool) {
|
||||||
|
return ptr.ToBool(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBoolSlice returns a slice of bool values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a bool
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToBoolSlice(vs []*bool) []bool {
|
||||||
|
return ptr.ToBoolSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBoolMap returns a map of bool values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The bool
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToBoolMap(vs map[string]*bool) map[string]bool {
|
||||||
|
return ptr.ToBoolMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToByte returns byte value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a byte zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToByte(p *byte) (v byte) {
|
||||||
|
return ptr.ToByte(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToByteSlice returns a slice of byte values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a byte
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToByteSlice(vs []*byte) []byte {
|
||||||
|
return ptr.ToByteSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToByteMap returns a map of byte values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The byte
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToByteMap(vs map[string]*byte) map[string]byte {
|
||||||
|
return ptr.ToByteMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString returns string value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a string zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToString(p *string) (v string) {
|
||||||
|
return ptr.ToString(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStringSlice returns a slice of string values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a string
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToStringSlice(vs []*string) []string {
|
||||||
|
return ptr.ToStringSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStringMap returns a map of string values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The string
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToStringMap(vs map[string]*string) map[string]string {
|
||||||
|
return ptr.ToStringMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt returns int value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt(p *int) (v int) {
|
||||||
|
return ptr.ToInt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToIntSlice returns a slice of int values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToIntSlice(vs []*int) []int {
|
||||||
|
return ptr.ToIntSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToIntMap returns a map of int values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToIntMap(vs map[string]*int) map[string]int {
|
||||||
|
return ptr.ToIntMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt8 returns int8 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int8 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt8(p *int8) (v int8) {
|
||||||
|
return ptr.ToInt8(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt8Slice returns a slice of int8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int8
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt8Slice(vs []*int8) []int8 {
|
||||||
|
return ptr.ToInt8Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt8Map returns a map of int8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int8
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt8Map(vs map[string]*int8) map[string]int8 {
|
||||||
|
return ptr.ToInt8Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt16 returns int16 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int16 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt16(p *int16) (v int16) {
|
||||||
|
return ptr.ToInt16(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt16Slice returns a slice of int16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int16
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt16Slice(vs []*int16) []int16 {
|
||||||
|
return ptr.ToInt16Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt16Map returns a map of int16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int16
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt16Map(vs map[string]*int16) map[string]int16 {
|
||||||
|
return ptr.ToInt16Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt32 returns int32 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int32 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt32(p *int32) (v int32) {
|
||||||
|
return ptr.ToInt32(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt32Slice returns a slice of int32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int32
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt32Slice(vs []*int32) []int32 {
|
||||||
|
return ptr.ToInt32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt32Map returns a map of int32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int32
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt32Map(vs map[string]*int32) map[string]int32 {
|
||||||
|
return ptr.ToInt32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64 returns int64 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int64 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt64(p *int64) (v int64) {
|
||||||
|
return ptr.ToInt64(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64Slice returns a slice of int64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int64
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt64Slice(vs []*int64) []int64 {
|
||||||
|
return ptr.ToInt64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64Map returns a map of int64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int64
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt64Map(vs map[string]*int64) map[string]int64 {
|
||||||
|
return ptr.ToInt64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint returns uint value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint(p *uint) (v uint) {
|
||||||
|
return ptr.ToUint(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUintSlice returns a slice of uint values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUintSlice(vs []*uint) []uint {
|
||||||
|
return ptr.ToUintSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUintMap returns a map of uint values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUintMap(vs map[string]*uint) map[string]uint {
|
||||||
|
return ptr.ToUintMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint8 returns uint8 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint8 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint8(p *uint8) (v uint8) {
|
||||||
|
return ptr.ToUint8(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint8Slice returns a slice of uint8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint8
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint8Slice(vs []*uint8) []uint8 {
|
||||||
|
return ptr.ToUint8Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint8Map returns a map of uint8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint8
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint8Map(vs map[string]*uint8) map[string]uint8 {
|
||||||
|
return ptr.ToUint8Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint16 returns uint16 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint16 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint16(p *uint16) (v uint16) {
|
||||||
|
return ptr.ToUint16(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint16Slice returns a slice of uint16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint16
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint16Slice(vs []*uint16) []uint16 {
|
||||||
|
return ptr.ToUint16Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint16Map returns a map of uint16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint16
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint16Map(vs map[string]*uint16) map[string]uint16 {
|
||||||
|
return ptr.ToUint16Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint32 returns uint32 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint32 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint32(p *uint32) (v uint32) {
|
||||||
|
return ptr.ToUint32(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint32Slice returns a slice of uint32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint32
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint32Slice(vs []*uint32) []uint32 {
|
||||||
|
return ptr.ToUint32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint32Map returns a map of uint32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint32
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint32Map(vs map[string]*uint32) map[string]uint32 {
|
||||||
|
return ptr.ToUint32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint64 returns uint64 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint64 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint64(p *uint64) (v uint64) {
|
||||||
|
return ptr.ToUint64(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint64Slice returns a slice of uint64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint64
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint64Slice(vs []*uint64) []uint64 {
|
||||||
|
return ptr.ToUint64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint64Map returns a map of uint64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint64
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint64Map(vs map[string]*uint64) map[string]uint64 {
|
||||||
|
return ptr.ToUint64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat32 returns float32 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a float32 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToFloat32(p *float32) (v float32) {
|
||||||
|
return ptr.ToFloat32(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat32Slice returns a slice of float32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a float32
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToFloat32Slice(vs []*float32) []float32 {
|
||||||
|
return ptr.ToFloat32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat32Map returns a map of float32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The float32
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToFloat32Map(vs map[string]*float32) map[string]float32 {
|
||||||
|
return ptr.ToFloat32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64 returns float64 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a float64 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToFloat64(p *float64) (v float64) {
|
||||||
|
return ptr.ToFloat64(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64Slice returns a slice of float64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a float64
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToFloat64Slice(vs []*float64) []float64 {
|
||||||
|
return ptr.ToFloat64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64Map returns a map of float64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The float64
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToFloat64Map(vs map[string]*float64) map[string]float64 {
|
||||||
|
return ptr.ToFloat64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTime returns time.Time value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a time.Time zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToTime(p *time.Time) (v time.Time) {
|
||||||
|
return ptr.ToTime(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTimeSlice returns a slice of time.Time values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a time.Time
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToTimeSlice(vs []*time.Time) []time.Time {
|
||||||
|
return ptr.ToTimeSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTimeMap returns a map of time.Time values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The time.Time
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToTimeMap(vs map[string]*time.Time) map[string]time.Time {
|
||||||
|
return ptr.ToTimeMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDuration returns time.Duration value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a time.Duration zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToDuration(p *time.Duration) (v time.Duration) {
|
||||||
|
return ptr.ToDuration(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDurationSlice returns a slice of time.Duration values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a time.Duration
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToDurationSlice(vs []*time.Duration) []time.Duration {
|
||||||
|
return ptr.ToDurationSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDurationMap returns a map of time.Duration values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The time.Duration
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToDurationMap(vs map[string]*time.Duration) map[string]time.Duration {
|
||||||
|
return ptr.ToDurationMap(vs)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user