Merge pull request #90 from ceph/devel

sync devel branch with upstream
This commit is contained in:
OpenShift Merge Robot 2022-04-12 09:19:38 -04:00 committed by GitHub
commit 776d354a05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 878 additions and 209 deletions

View File

@ -37,23 +37,6 @@ queue_rules:
- "status-success=ci/centos/mini-e2e/k8s-1.23" - "status-success=ci/centos/mini-e2e/k8s-1.23"
- "status-success=ci/centos/upgrade-tests-cephfs" - "status-success=ci/centos/upgrade-tests-cephfs"
- "status-success=ci/centos/upgrade-tests-rbd" - "status-success=ci/centos/upgrade-tests-rbd"
- and:
- base=release-v3.4
- "status-success=codespell"
- "status-success=multi-arch-build"
- "status-success=go-test"
- "status-success=commitlint"
- "status-success=golangci-lint"
- "status-success=mod-check"
- "status-success=lint-extras"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.21"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.22"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.23"
- "status-success=ci/centos/mini-e2e/k8s-1.21"
- "status-success=ci/centos/mini-e2e/k8s-1.22"
- "status-success=ci/centos/mini-e2e/k8s-1.23"
- "status-success=ci/centos/upgrade-tests-cephfs"
- "status-success=ci/centos/upgrade-tests-rbd"
- and: - and:
- base=ci/centos - base=ci/centos
- "status-success=ci/centos/job-validation" - "status-success=ci/centos/job-validation"
@ -186,76 +169,14 @@ pull_request_rules:
backport: backport:
branches: branches:
- release-v3.5 - release-v3.5
# automerge backports if CI successfully ran - name: backport patches to release-v3.6 branch
- name: automerge backport release-v3.5
conditions:
- author=mergify[bot]
- base=release-v3.5
- label!=DNM
- "#approved-reviews-by>=2"
- "approved-reviews-by=@ceph/ceph-csi-contributors"
- "approved-reviews-by=@ceph/ceph-csi-maintainers"
- "#changes-requested-reviews-by=0"
- "status-success=codespell"
- "status-success=multi-arch-build"
- "status-success=go-test"
- "status-success=commitlint"
- "status-success=golangci-lint"
- "status-success=mod-check"
- "status-success=lint-extras"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.21"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.22"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.23"
- "status-success=ci/centos/mini-e2e/k8s-1.21"
- "status-success=ci/centos/mini-e2e/k8s-1.22"
- "status-success=ci/centos/mini-e2e/k8s-1.23"
- "status-success=ci/centos/k8s-e2e-external-storage/1.21"
- "status-success=ci/centos/k8s-e2e-external-storage/1.22"
- "status-success=ci/centos/k8s-e2e-external-storage/1.23"
- "status-success=ci/centos/upgrade-tests-cephfs"
- "status-success=ci/centos/upgrade-tests-rbd"
- "status-success=DCO"
actions:
queue:
name: default
- name: backport patches to release-v3.4 branch
conditions: conditions:
- base=devel - base=devel
- label=backport-to-release-v3.4 - label=backport-to-release-v3.6
actions: actions:
backport: backport:
branches: branches:
- release-v3.4 - release-v3.6
# automerge backports if CI successfully ran
- name: automerge backport release-v3.4
conditions:
- author=mergify[bot]
- base=release-v3.4
- label!=DNM
- "#approved-reviews-by>=2"
- "approved-reviews-by=@ceph/ceph-csi-contributors"
- "approved-reviews-by=@ceph/ceph-csi-maintainers"
- "status-success=codespell"
- "status-success=multi-arch-build"
- "status-success=go-test"
- "status-success=commitlint"
- "status-success=golangci-lint"
- "status-success=mod-check"
- "status-success=lint-extras"
- "#changes-requested-reviews-by=0"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.21"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.22"
- "status-success=ci/centos/mini-e2e-helm/k8s-1.23"
- "status-success=ci/centos/mini-e2e/k8s-1.21"
- "status-success=ci/centos/mini-e2e/k8s-1.22"
- "status-success=ci/centos/mini-e2e/k8s-1.23"
- "status-success=ci/centos/upgrade-tests-cephfs"
- "status-success=ci/centos/upgrade-tests-rbd"
- "status-success=DCO"
actions:
queue:
name: default
delete_head_branch: {}
- name: remove outdated approvals on ci/centos - name: remove outdated approvals on ci/centos
conditions: conditions:
- base=ci/centos - base=ci/centos

View File

@ -128,10 +128,10 @@ in the Kubernetes documentation.
| v3.6.0 (Release) | quay.io/cephcsi/cephcsi | v3.6.0 | | v3.6.0 (Release) | quay.io/cephcsi/cephcsi | v3.6.0 |
| v3.5.1 (Release) | quay.io/cephcsi/cephcsi | v3.5.1 | | v3.5.1 (Release) | quay.io/cephcsi/cephcsi | v3.5.1 |
| v3.5.0 (Release) | quay.io/cephcsi/cephcsi | v3.5.0 | | v3.5.0 (Release) | quay.io/cephcsi/cephcsi | v3.5.0 |
| v3.4.0 (Release) | quay.io/cephcsi/cephcsi | v3.4.0 |
| Deprecated Ceph CSI Release/Branch | Container image name | Image Tag | | Deprecated Ceph CSI Release/Branch | Container image name | Image Tag |
| ----------------------- | --------------------------------| --------- | | ----------------------- | --------------------------------| --------- |
| v3.4.0 (Release) | quay.io/cephcsi/cephcsi | v3.4.0 |
| v3.3.1 (Release) | quay.io/cephcsi/cephcsi | v3.3.1 | | v3.3.1 (Release) | quay.io/cephcsi/cephcsi | v3.3.1 |
| v3.3.0 (Release) | quay.io/cephcsi/cephcsi | v3.3.0 | | v3.3.0 (Release) | quay.io/cephcsi/cephcsi | v3.3.0 |
| v3.2.2 (Release) | quay.io/cephcsi/cephcsi | v3.2.2 | | v3.2.2 (Release) | quay.io/cephcsi/cephcsi | v3.2.2 |

View File

@ -31,6 +31,7 @@ spec:
priorityClassName: {{ .Values.nodeplugin.priorityClassName }} priorityClassName: {{ .Values.nodeplugin.priorityClassName }}
{{- end }} {{- end }}
hostNetwork: true hostNetwork: true
hostPID: true
# to use e.g. Rook orchestrated cluster, and mons' FQDN is # to use e.g. Rook orchestrated cluster, and mons' FQDN is
# resolved through k8s service, set dns policy to cluster first # resolved through k8s service, set dns policy to cluster first
dnsPolicy: ClusterFirstWithHostNet dnsPolicy: ClusterFirstWithHostNet

View File

@ -27,6 +27,7 @@ serviceAccounts:
# - "<MONValue2>" # - "<MONValue2>"
# cephFS: # cephFS:
# subvolumeGroup: "csi" # subvolumeGroup: "csi"
# netNamespaceFilePath: "{{ .kubeletDir }}/plugins/{{ .driverName }}/net"
csiConfig: [] csiConfig: []
# Set logging level for csi containers. # Set logging level for csi containers.

View File

@ -102,6 +102,7 @@ spec:
- "--v={{ .Values.logLevel }}" - "--v={{ .Values.logLevel }}"
- "--timeout={{ .Values.provisioner.timeout }}" - "--timeout={{ .Values.provisioner.timeout }}"
- "--leader-election=true" - "--leader-election=true"
- "--extra-create-metadata=true"
env: env:
- name: ADDRESS - name: ADDRESS
value: "unix:///csi/{{ .Values.provisionerSocketFile }}" value: "unix:///csi/{{ .Values.provisionerSocketFile }}"

View File

@ -25,6 +25,7 @@ serviceAccounts:
# monitors: # monitors:
# - "<MONValue1>" # - "<MONValue1>"
# - "<MONValue2>" # - "<MONValue2>"
# netNamespaceFilePath: "{{ .kubeletDir }}/plugins/{{ .driverName }}/net"
csiConfig: [] csiConfig: []
# Configuration details of clusterID,PoolID and FscID mapping # Configuration details of clusterID,PoolID and FscID mapping

View File

@ -15,6 +15,7 @@ spec:
serviceAccountName: cephfs-csi-nodeplugin serviceAccountName: cephfs-csi-nodeplugin
priorityClassName: system-node-critical priorityClassName: system-node-critical
hostNetwork: true hostNetwork: true
hostPID: true
# to use e.g. Rook orchestrated cluster, and mons' FQDN is # to use e.g. Rook orchestrated cluster, and mons' FQDN is
# resolved through k8s service, set dns policy to cluster first # resolved through k8s service, set dns policy to cluster first
dnsPolicy: ClusterFirstWithHostNet dnsPolicy: ClusterFirstWithHostNet

View File

@ -73,6 +73,7 @@ spec:
- "--v=5" - "--v=5"
- "--timeout=150s" - "--timeout=150s"
- "--leader-election=true" - "--leader-election=true"
- "--extra-create-metadata=true"
env: env:
- name: ADDRESS - name: ADDRESS
value: unix:///csi/csi-provisioner.sock value: unix:///csi/csi-provisioner.sock

View File

@ -25,7 +25,7 @@ it is **highly** encouraged to:
package](https://github.com/ceph/go-ceph). It is required to install the package](https://github.com/ceph/go-ceph). It is required to install the
Ceph C headers in order to compile Ceph-CSI. The packages are called Ceph C headers in order to compile Ceph-CSI. The packages are called
`librados-devel` and `librbd-devel` on many Linux distributions. See the `librados-devel` and `librbd-devel` on many Linux distributions. See the
[go-ceph installaton [go-ceph installation
instructions](https://github.com/ceph/go-ceph#installation) for more instructions](https://github.com/ceph/go-ceph#installation) for more
details. details.
* Run * Run
@ -169,7 +169,7 @@ The `component` in the subject of the commit message can be one of the following
* `doc`: documentation updates * `doc`: documentation updates
* `util`: utilities shared between components use `cephfs` or `rbd` if the * `util`: utilities shared between components use `cephfs` or `rbd` if the
change is only relevant for one of the type of storage change is only relevant for one of the type of storage
* `journal`: any of the journalling functionalities * `journal`: any of the journaling functionalities
* `helm`: deployment changes for the Helm charts * `helm`: deployment changes for the Helm charts
* `deploy`: updates to Kubernetes templates for deploying components * `deploy`: updates to Kubernetes templates for deploying components
* `build`: anything related to building Ceph-CSI, the executable or container * `build`: anything related to building Ceph-CSI, the executable or container
@ -312,12 +312,12 @@ Right now, we also have below commands to manually retrigger the CI jobs
**Caution**: Please do not retrigger the CI jobs without an understanding of **Caution**: Please do not retrigger the CI jobs without an understanding of
the root cause, because: the root cause, because:
* We may miss some of the important corner cases which are true negatives, * We may miss some important corner cases which are true negatives,
and hard to reproduce and hard to reproduce
* Retriggering jobs for known failures can unnecessarily put CI resources * Retriggering jobs for known failures can unnecessarily put CI resources
under pressure under pressure
Hence it is recommended that you please go through the CI logs first, if you Hence, it is recommended that you please go through the CI logs first, if you
are certain about the flaky test failure behavior, then comment on the PR are certain about the flaky test failure behavior, then comment on the PR
indicating the logs about a particular test that went flaky and use the indicating the logs about a particular test that went flaky and use the
appropriate command to retrigger the job[s]. appropriate command to retrigger the job[s].

View File

@ -26,10 +26,10 @@ Promethues can be deployed through the promethues operator described [here](http
The [service-monitor](../examples/service-monitor.yaml) will tell promethues how The [service-monitor](../examples/service-monitor.yaml) will tell promethues how
to pull metrics out of CSI. to pull metrics out of CSI.
Each CSI pod has a service to expose the endpoint to prometheus. By default rbd Each CSI pod has a service to expose the endpoint to prometheus. By default, rbd
pods run on port 8080 and cephfs 8081. pods run on port 8080 and cephfs 8081.
These can be changed if desired or if multiple ceph clusters are deployed more These can be changed if desired or if multiple ceph clusters are deployed more
ports will be used for additional CSI pods. ports will be used for additional CSI pods.
Note: You may need to open the ports used in your firewall depending on how you Note: You may need to open the ports used in your firewall depending on how your
cluster is setup. cluster has set up.

View File

@ -90,12 +90,12 @@ are available while running tests:
| flag | description | | flag | description |
| ----------------- | ----------------------------------------------------------------------------- | | ----------------- | ----------------------------------------------------------------------------- |
| deploy-timeout | Timeout to wait for created kubernetes resources (default: 10 minutes) | | deploy-timeout | Timeout to wait for created kubernetes resources (default: 10 minutes) |
| deploy-cephfs | Deploy cephFS csi driver as part of E2E (default: true) | | deploy-cephfs | Deploy cephFS CSI driver as part of E2E (default: true) |
| deploy-rbd | Deploy rbd csi driver as part of E2E (default: true) | | deploy-rbd | Deploy rbd CSI driver as part of E2E (default: true) |
| test-cephfs | Test cephFS csi driver as part of E2E (default: true) | | test-cephfs | Test cephFS CSI driver as part of E2E (default: true) |
| upgrade-testing | Perform upgrade testing (default: false) | | upgrade-testing | Perform upgrade testing (default: false) |
| upgrade-version | Target version for upgrade testing (default: "v3.3.1") | | upgrade-version | Target version for upgrade testing (default: "v3.5.1") |
| test-rbd | Test rbd csi driver as part of E2E (default: true) | | test-rbd | Test rbd CSI driver as part of E2E (default: true) |
| cephcsi-namespace | The namespace in which cephcsi driver will be created (default: "default") | | cephcsi-namespace | The namespace in which cephcsi driver will be created (default: "default") |
| rook-namespace | The namespace in which rook operator is installed (default: "rook-ceph") | | rook-namespace | The namespace in which rook operator is installed (default: "rook-ceph") |
| kubeconfig | Path to kubeconfig containing embedded authinfo (default: $HOME/.kube/config) | | kubeconfig | Path to kubeconfig containing embedded authinfo (default: $HOME/.kube/config) |
@ -106,30 +106,6 @@ are available while running tests:
After the support for snapshot/clone has been added to ceph-csi, After the support for snapshot/clone has been added to ceph-csi,
you need to follow these steps before running e2e. you need to follow these steps before running e2e.
Please note that the snapshot operation works only if the Kubernetes version
is greater than or equal to 1.17.0.
- Delete Alpha snapshot CRD created by ceph-csi in rook.
- Check if you have any `v1alpha1` CRD created in our Kubernetes cluster
```bash
$ kubectl get crd volumesnapshotclasses.snapshot.storage.k8s.io -o yaml |grep v1alpha1
- name: v1alpha1
- v1alpha1
$ kubectl get crd volumesnapshotcontents.snapshot.storage.k8s.io -o yaml |grep v1alpha1
- name: v1alpha1
- v1alpha1
$ kubectl get crd volumesnapshots.snapshot.storage.k8s.io -o yaml |grep v1alpha1
- name: v1alpha1
- v1alpha1
```
- If you have Alpha CRD, delete it as from Kubernetes 1.17.0+ the snapshot
should be `v1beta1`
```console
./scripts/install-snapshot.sh delete-crd
```
- Install snapshot controller and Beta snapshot CRD - Install snapshot controller and Beta snapshot CRD

View File

@ -575,7 +575,7 @@ var _ = Describe("cephfs", func() {
if err != nil { if err != nil {
e2elog.Failf("killall command failed: err %v, stderr %s", err, stdErr) e2elog.Failf("killall command failed: err %v, stderr %s", err, stdErr)
} }
// Verify Pod podName2 that stat()-ing the mountpoint results in ENOTCONN. // Verify Pod pod2Name that stat()-ing the mountpoint results in ENOTCONN.
stdErr, err = doStat(pod2Name) stdErr, err = doStat(pod2Name)
if err == nil || !strings.Contains(stdErr, "not connected") { if err == nil || !strings.Contains(stdErr, "not connected") {
e2elog.Failf( e2elog.Failf(
@ -583,7 +583,7 @@ var _ = Describe("cephfs", func() {
err, stdErr, err, stdErr,
) )
} }
// Delete podName2 Pod. This serves two purposes: it verifies that deleting pods with // Delete pod2Name Pod. This serves two purposes: it verifies that deleting pods with
// corrupted ceph-fuse mountpoints works, and it lets the replicaset controller recreate // corrupted ceph-fuse mountpoints works, and it lets the replicaset controller recreate
// the pod with hopefully mounts working again. // the pod with hopefully mounts working again.
err = deletePod(pod2Name, depl.Namespace, c, deployTimeout) err = deletePod(pod2Name, depl.Namespace, c, deployTimeout)
@ -616,7 +616,7 @@ var _ = Describe("cephfs", func() {
} }
e2elog.Failf("no new replica found ; found replicas %v", podNames) e2elog.Failf("no new replica found ; found replicas %v", podNames)
} }
// Verify Pod podName3 has its ceph-fuse mount working again. // Verify Pod pod2Name has its ceph-fuse mount working again.
err = ensureStatSucceeds(pod2Name) err = ensureStatSucceeds(pod2Name)
if err != nil { if err != nil {
e2elog.Failf(err.Error()) e2elog.Failf(err.Error())

View File

@ -40,7 +40,7 @@ func init() {
flag.BoolVar(&testRBD, "test-rbd", true, "test rbd csi driver") flag.BoolVar(&testRBD, "test-rbd", true, "test rbd csi driver")
flag.BoolVar(&helmTest, "helm-test", false, "tests running on deployment via helm") flag.BoolVar(&helmTest, "helm-test", false, "tests running on deployment via helm")
flag.BoolVar(&upgradeTesting, "upgrade-testing", false, "perform upgrade testing") flag.BoolVar(&upgradeTesting, "upgrade-testing", false, "perform upgrade testing")
flag.StringVar(&upgradeVersion, "upgrade-version", "v3.3.1", "target version for upgrade testing") flag.StringVar(&upgradeVersion, "upgrade-version", "v3.5.1", "target version for upgrade testing")
flag.StringVar(&cephCSINamespace, "cephcsi-namespace", defaultNs, "namespace in which cephcsi deployed") flag.StringVar(&cephCSINamespace, "cephcsi-namespace", defaultNs, "namespace in which cephcsi deployed")
flag.StringVar(&rookNamespace, "rook-namespace", "rook-ceph", "namespace in which rook is deployed") flag.StringVar(&rookNamespace, "rook-namespace", "rook-ceph", "namespace in which rook is deployed")
setDefaultKubeconfig() setDefaultKubeconfig()

View File

@ -31,7 +31,7 @@ const (
// kmsConfig is an interface that should be used when passing a configuration // kmsConfig is an interface that should be used when passing a configuration
// for a KMS to validation functions. This allows the validation functions to // for a KMS to validation functions. This allows the validation functions to
// work independently from the actual KMS. // work independently of the actual KMS.
type kmsConfig interface { type kmsConfig interface {
canGetPassphrase() bool canGetPassphrase() bool
getPassphrase(f *framework.Framework, key string) (string, string) getPassphrase(f *framework.Framework, key string) (string, string)

View File

@ -29,6 +29,7 @@ import (
. "github.com/onsi/ginkgo" // nolint . "github.com/onsi/ginkgo" // nolint
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/framework"
@ -89,6 +90,17 @@ var (
nbdMapOptions = "nbd:debug-rbd=20" nbdMapOptions = "nbd:debug-rbd=20"
e2eDefaultCephLogStrategy = "preserve" e2eDefaultCephLogStrategy = "preserve"
// PV and PVC metadata keys used by external provisioner as part of
// create requests as parameters, when `extra-create-metadata` is true.
pvcNameKey = "csi.storage.k8s.io/pvc/name"
pvcNamespaceKey = "csi.storage.k8s.io/pvc/namespace"
pvNameKey = "csi.storage.k8s.io/pv/name"
// snapshot metadata keys.
volSnapNameKey = "csi.storage.k8s.io/volumesnapshot/name"
volSnapNamespaceKey = "csi.storage.k8s.io/volumesnapshot/namespace"
volSnapContentNameKey = "csi.storage.k8s.io/volumesnapshotcontent/name"
) )
func deployRBDPlugin() { func deployRBDPlugin() {
@ -394,6 +406,286 @@ var _ = Describe("RBD", func() {
} }
}) })
} }
By("create a PVC and check PVC/PV metadata on RBD image", func() {
pvc, err := loadPVC(pvcPath)
if err != nil {
e2elog.Failf("failed to load PVC: %v", err)
}
pvc.Namespace = f.UniqueName
err = createPVCAndvalidatePV(f.ClientSet, pvc, deployTimeout)
if err != nil {
e2elog.Failf("failed to create PVC: %v", err)
}
// validate created backend rbd images
validateRBDImageCount(f, 1, defaultRBDPool)
imageList, err := listRBDImages(f, defaultRBDPool)
if err != nil {
e2elog.Failf("failed to list rbd images: %v", err)
}
pvcName, stdErr, err := execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], pvcNameKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get PVC name %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], pvcNameKey, err, stdErr)
}
pvcName = strings.TrimSuffix(pvcName, "\n")
if pvcName != pvc.Name {
e2elog.Failf("expected pvcName %q got %q", pvc.Name, pvcName)
}
pvcNamespace, stdErr, err := execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], pvcNamespaceKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get PVC namespace %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], pvcNamespaceKey, err, stdErr)
}
pvcNamespace = strings.TrimSuffix(pvcNamespace, "\n")
if pvcNamespace != pvc.Namespace {
e2elog.Failf("expected pvcNamespace %q got %q", pvc.Namespace, pvcNamespace)
}
pvcObj, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(
context.TODO(),
pvc.Name,
metav1.GetOptions{})
if err != nil {
e2elog.Logf("error getting pvc %q in namespace %q: %v", pvc.Name, pvc.Namespace, err)
}
if pvcObj.Spec.VolumeName == "" {
e2elog.Logf("pv name is empty %q in namespace %q: %v", pvc.Name, pvc.Namespace, err)
}
pvName, stdErr, err := execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], pvNameKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get PV name %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], pvNameKey, err, stdErr)
}
pvName = strings.TrimSuffix(pvName, "\n")
if pvName != pvcObj.Spec.VolumeName {
e2elog.Failf("expected pvName %q got %q", pvcObj.Spec.VolumeName, pvName)
}
err = deletePVCAndValidatePV(f.ClientSet, pvc, deployTimeout)
if err != nil {
e2elog.Failf("failed to delete pvc: %v", err)
}
validateRBDImageCount(f, 0, defaultRBDPool)
})
By("reattach the old PV to a new PVC and check if PVC metadata is updated on RBD image", func() {
pvc, err := loadPVC(pvcPath)
if err != nil {
e2elog.Failf("failed to load PVC: %v", err)
}
pvc.Namespace = f.UniqueName
err = createPVCAndvalidatePV(f.ClientSet, pvc, deployTimeout)
if err != nil {
e2elog.Failf("failed to create PVC: %v", err)
}
// validate created backend rbd images
validateRBDImageCount(f, 1, defaultRBDPool)
imageList, err := listRBDImages(f, defaultRBDPool)
if err != nil {
e2elog.Failf("failed to list rbd images: %v", err)
}
pvcName, stdErr, err := execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], pvcNameKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get PVC name %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], pvcNameKey, err, stdErr)
}
pvcName = strings.TrimSuffix(pvcName, "\n")
if pvcName != pvc.Name {
e2elog.Failf("expected pvcName %q got %q", pvc.Name, pvcName)
}
pvcObj, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(
context.TODO(),
pvc.Name,
metav1.GetOptions{})
if err != nil {
e2elog.Logf("error getting pvc %q in namespace %q: %v", pvc.Name, pvc.Namespace, err)
}
if pvcObj.Spec.VolumeName == "" {
e2elog.Logf("pv name is empty %q in namespace %q: %v", pvc.Name, pvc.Namespace, err)
}
patchBytes := []byte(`{"spec":{"persistentVolumeReclaimPolicy": "Retain", "claimRef": null}}`)
_, err = c.CoreV1().PersistentVolumes().Patch(
context.TODO(),
pvcObj.Spec.VolumeName,
types.StrategicMergePatchType,
patchBytes,
metav1.PatchOptions{})
if err != nil {
e2elog.Logf("error Patching PV %q for persistentVolumeReclaimPolicy and claimRef: %v",
pvcObj.Spec.VolumeName, err)
}
err = c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Delete(
context.TODO(),
pvc.Name,
metav1.DeleteOptions{})
if err != nil {
e2elog.Logf("failed to delete pvc: %w", err)
}
// validate created backend rbd images
validateRBDImageCount(f, 1, defaultRBDPool)
pvcObj.Name = "rbd-pvc-new"
// unset the resource version as should not be set on objects to be created
pvcObj.ResourceVersion = ""
err = createPVCAndvalidatePV(f.ClientSet, pvcObj, deployTimeout)
if err != nil {
e2elog.Failf("failed to create new PVC: %v", err)
}
// validate created backend rbd images
validateRBDImageCount(f, 1, defaultRBDPool)
pvcName, stdErr, err = execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], pvcNameKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get PVC name %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], pvcNameKey, err, stdErr)
}
pvcName = strings.TrimSuffix(pvcName, "\n")
if pvcName != pvcObj.Name {
e2elog.Failf("expected pvcName %q got %q", pvcObj.Name, pvcName)
}
patchBytes = []byte(`{"spec":{"persistentVolumeReclaimPolicy": "Delete"}}`)
_, err = c.CoreV1().PersistentVolumes().Patch(
context.TODO(),
pvcObj.Spec.VolumeName,
types.StrategicMergePatchType,
patchBytes,
metav1.PatchOptions{})
if err != nil {
e2elog.Logf("error Patching PV %q for persistentVolumeReclaimPolicy: %v", pvcObj.Spec.VolumeName, err)
}
err = deletePVCAndValidatePV(f.ClientSet, pvcObj, deployTimeout)
if err != nil {
e2elog.Failf("failed to delete pvc: %v", err)
}
validateRBDImageCount(f, 0, defaultRBDPool)
})
By("create a snapshot and check metadata on RBD snapshot image", func() {
err := createRBDSnapshotClass(f)
if err != nil {
e2elog.Failf("failed to create storageclass: %v", err)
}
defer func() {
err = deleteRBDSnapshotClass()
if err != nil {
e2elog.Failf("failed to delete VolumeSnapshotClass: %v", err)
}
}()
pvc, app, err := createPVCAndAppBinding(pvcPath, appPath, f, deployTimeout)
if err != nil {
e2elog.Failf("failed to create pvc and application binding: %v", err)
}
// validate created backend rbd images
validateRBDImageCount(f, 1, defaultRBDPool)
// delete pod as we should not create snapshot for in-use pvc
err = deletePod(app.Name, app.Namespace, f.ClientSet, deployTimeout)
if err != nil {
e2elog.Failf("failed to delete application: %v", err)
}
snap := getSnapshot(snapshotPath)
snap.Namespace = f.UniqueName
snap.Spec.Source.PersistentVolumeClaimName = &pvc.Name
err = createSnapshot(&snap, deployTimeout)
if err != nil {
e2elog.Failf("failed to create snapshot: %v", err)
}
// validate created backend rbd images
// parent PVC + snapshot
totalImages := 2
validateRBDImageCount(f, totalImages, defaultRBDPool)
imageList, err := listRBDImages(f, defaultRBDPool)
if err != nil {
e2elog.Failf("failed to list rbd images: %v", err)
}
volSnapName, stdErr, err := execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], volSnapNameKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get volume snapshot name %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], volSnapNameKey, err, stdErr)
}
volSnapName = strings.TrimSuffix(volSnapName, "\n")
if volSnapName != snap.Name {
e2elog.Failf("expected volSnapName %q got %q", snap.Name, volSnapName)
}
volSnapNamespace, stdErr, err := execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], volSnapNamespaceKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get volume snapshot namespace %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], volSnapNamespaceKey, err, stdErr)
}
volSnapNamespace = strings.TrimSuffix(volSnapNamespace, "\n")
if volSnapNamespace != snap.Namespace {
e2elog.Failf("expected volSnapNamespace %q got %q", snap.Namespace, volSnapNamespace)
}
content, err := getVolumeSnapshotContent(snap.Namespace, snap.Name)
if err != nil {
e2elog.Failf("failed to get snapshotcontent for %s in namespace %s: %v",
snap.Name, snap.Namespace, err)
}
volSnapContentName, stdErr, err := execCommandInToolBoxPod(f,
fmt.Sprintf("rbd image-meta get %s --image=%s %s",
rbdOptions(defaultRBDPool), imageList[0], volSnapContentNameKey),
rookNamespace)
if err != nil || stdErr != "" {
e2elog.Failf("failed to get snapshotcontent name %s/%s %s: err=%v stdErr=%q",
rbdOptions(defaultRBDPool), imageList[0], volSnapContentNameKey, err, stdErr)
}
volSnapContentName = strings.TrimSuffix(volSnapContentName, "\n")
if volSnapContentName != content.Name {
e2elog.Failf("expected volSnapContentName %q got %q", content.Name, volSnapContentName)
}
err = deleteSnapshot(&snap, deployTimeout)
if err != nil {
e2elog.Failf("failed to delete snapshot: %v", err)
}
err = deletePVCAndValidatePV(f.ClientSet, pvc, deployTimeout)
if err != nil {
e2elog.Failf("failed to delete pvc: %v", err)
}
validateRBDImageCount(f, 0, defaultRBDPool)
})
By("verify generic ephemeral volume support", func() { By("verify generic ephemeral volume support", func() {
// generic ephemeral volume support is supported from 1.21 // generic ephemeral volume support is supported from 1.21
if k8sVersionGreaterEquals(f.ClientSet, 1, 21) { if k8sVersionGreaterEquals(f.ClientSet, 1, 21) {

View File

@ -50,6 +50,42 @@ kubectl replace -f ./csi-config-map-sample.yaml
Storage class and snapshot class, using `<cluster-id>` as the value for the Storage class and snapshot class, using `<cluster-id>` as the value for the
option `clusterID`, can now be created on the cluster. option `clusterID`, can now be created on the cluster.
## Running CephCSI with pod networking
The current problem with Pod Networking, is when a CephFS/RBD volume is mounted
in a pod using Ceph CSI and then the CSI CephFS/RBD plugin is restarted or
terminated (e.g. by restarting or deleting its DaemonSet), all operations on
the volume become blocked, even after restarting the CSI pods.
The only workaround is to restart the node where the Ceph CSI plugin pod was
restarted. This can be mitigated by running the `rbd map`/`mount -t` commands
in a different network namespace which does not get deleted when the CSI
CephFS/RBD plugin is restarted or terminated.
If someone wants to run the CephCSI with the pod networking they can still do
by setting the `netNamespaceFilePath`. If this path is set CephCSI will execute
the `rbd map`/`mount -t` commands after entering the [network
namespace](https://man7.org/linux/man-pages/man7/network_namespaces.7.html)
specified by `netNamespaceFilePath` with the
[nsenter](https://man7.org/linux/man-pages/man1/nsenter.1.html) command.
`netNamespaceFilePath` should point to the network namespace of some
long-running process, typically it would be a symlink to
`/proc/<long running process id>/ns/net`.
The long-running process can also be another pod which is a Daemonset which
never restarts. This Pod should only be stopped and restarted when a node is
stopped so that volume operations do not become blocked. The new DaemonSet pod
can contain a single container, responsible for holding its pod network alive.
It is used as a passthrough by the CephCSI plugin pod which when mounting or
mapping will use the network namespace of this pod.
Once the pod is created get its PID and create a symlink to
`/proc/<PID>/ns/net` in the hostPath volume shared with the csi-plugin pod and
specify the path in the `netNamespaceFilePath` option.
*Note* This Pod should have `hostPID: true` in the Pod Spec.
## Deploying the storage class ## Deploying the storage class
Once the plugin is successfully deployed, you'll need to customize Once the plugin is successfully deployed, you'll need to customize

View File

@ -20,6 +20,12 @@ kind: ConfigMap
# NOTE: Make sure you don't add radosNamespace option to a currently in use # NOTE: Make sure you don't add radosNamespace option to a currently in use
# configuration as it will cause issues. # configuration as it will cause issues.
# The field "cephFS.subvolumeGroup" is optional and defaults to "csi". # The field "cephFS.subvolumeGroup" is optional and defaults to "csi".
# The <netNamespaceFilePath#> fields are the various network namespace
# path for the Ceph cluster identified by the <cluster-id>, This will be used
# by the CSI plugin to execute the rbd map/unmap and mount -t commands in the
# network namespace specified by the <netNamespaceFilePath#>.
# If a CSI plugin is using more than one Ceph cluster, repeat the section for
# each such cluster in use.
# NOTE: Changes to the configmap is automatically updated in the running pods, # NOTE: Changes to the configmap is automatically updated in the running pods,
# thus restarting existing pods using the configmap is NOT required on edits # thus restarting existing pods using the configmap is NOT required on edits
# to the configmap. # to the configmap.
@ -37,6 +43,7 @@ data:
{ {
"clusterID": "<cluster-id>", "clusterID": "<cluster-id>",
"radosNamespace": "<rados-namespace>", "radosNamespace": "<rados-namespace>",
"netNamespaceFilePath": "<kubeletRootPath>/plugins/rbd.csi.ceph.com/net",
"monitors": [ "monitors": [
"<MONValue1>", "<MONValue1>",
"<MONValue2>", "<MONValue2>",

12
go.mod
View File

@ -5,7 +5,7 @@ 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.32 github.com/aws/aws-sdk-go v1.43.32
github.com/aws/aws-sdk-go-v2/service/sts v1.16.0 github.com/aws/aws-sdk-go-v2/service/sts v1.16.3
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
@ -44,11 +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 v1.16.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 // indirect
github.com/aws/smithy-go v1.11.1 // indirect github.com/aws/smithy-go v1.11.2 // 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

24
go.sum
View File

@ -140,18 +140,18 @@ github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9
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.32 h1:b2NQnfWfImfo7yzXq6gzXEC+6s5v1t2RU3G9o+VirYo= github.com/aws/aws-sdk-go v1.43.32 h1:b2NQnfWfImfo7yzXq6gzXEC+6s5v1t2RU3G9o+VirYo=
github.com/aws/aws-sdk-go v1.43.32/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.43.32/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.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA=
github.com/aws/aws-sdk-go-v2 v1.15.0/go.mod h1:lJYcuZZEHWNIb6ugJjbQY1fykdoobWbOS7kJYb4APoI= github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
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.9 h1:onz/VaaxZ7Z4V+WIN9Txly9XLTmoOh1oJ8XcAC3pako=
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/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
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.3 h1:9stUQR/u2KXU6HkFJYlqnZEjBnbgrVbG6I5HN09xZh0=
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/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
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.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ=
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/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
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.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q=
github.com/aws/aws-sdk-go-v2/service/sts v1.16.0/go.mod h1:+8k4H2ASUZZXmjx/s3DFLo9tGBb44lkz3XcgfypJY7s= github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
github.com/aws/smithy-go v1.11.1 h1:IQ+lPZVkSM3FRtyaDox41R8YS6iwPMYIreejOgPW49g= github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE=
github.com/aws/smithy-go v1.11.1/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/aws/smithy-go v1.11.2/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=

View File

@ -65,12 +65,20 @@ func mountFuse(ctx context.Context, mountPoint string, cr *util.Credentials, vol
if volOptions.FsName != "" { if volOptions.FsName != "" {
args = append(args, "--client_mds_namespace="+volOptions.FsName) args = append(args, "--client_mds_namespace="+volOptions.FsName)
} }
var (
stderr string
err error
)
if volOptions.NetNamespaceFilePath != "" {
_, stderr, err = util.ExecuteCommandWithNSEnter(ctx, volOptions.NetNamespaceFilePath, "ceph-fuse", args[:]...)
} else {
_, stderr, err = util.ExecCommand(ctx, "ceph-fuse", args[:]...)
}
_, stderr, err := util.ExecCommand(ctx, "ceph-fuse", args[:]...)
if err != nil { if err != nil {
return fmt.Errorf("%w stderr: %s", err, stderr) return fmt.Errorf("%w stderr: %s", err, stderr)
} }
// Parse the output: // Parse the output:
// We need "starting fuse" meaning the mount is ok // We need "starting fuse" meaning the mount is ok
// and PID of the ceph-fuse daemon for unmount // and PID of the ceph-fuse daemon for unmount

View File

@ -51,7 +51,16 @@ func mountKernel(ctx context.Context, mountPoint string, cr *util.Credentials, v
args = append(args, "-o", optionsStr) args = append(args, "-o", optionsStr)
_, stderr, err := util.ExecCommand(ctx, "mount", args[:]...) var (
stderr string
err error
)
if volOptions.NetNamespaceFilePath != "" {
_, stderr, err = util.ExecuteCommandWithNSEnter(ctx, volOptions.NetNamespaceFilePath, "mount", args[:]...)
} else {
_, stderr, err = util.ExecCommand(ctx, "mount", args[:]...)
}
if err != nil { if err != nil {
return fmt.Errorf("%w stderr: %s", err, stderr) return fmt.Errorf("%w stderr: %s", err, stderr)
} }

View File

@ -126,6 +126,11 @@ func (ns *NodeServer) NodeStageVolume(
} }
defer volOptions.Destroy() defer volOptions.Destroy()
volOptions.NetNamespaceFilePath, err = util.GetNetNamespaceFilePath(util.CsiConfigFile, volOptions.ClusterID)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
mnt, err := mounter.New(volOptions) mnt, err := mounter.New(volOptions)
if err != nil { if err != nil {
log.ErrorLog(ctx, "failed to create mounter for volume %s: %v", volID, err) log.ErrorLog(ctx, "failed to create mounter for volume %s: %v", volID, err)

View File

@ -50,6 +50,8 @@ type VolumeOptions struct {
ProvisionVolume bool `json:"provisionVolume"` ProvisionVolume bool `json:"provisionVolume"`
KernelMountOptions string `json:"kernelMountOptions"` KernelMountOptions string `json:"kernelMountOptions"`
FuseMountOptions string `json:"fuseMountOptions"` FuseMountOptions string `json:"fuseMountOptions"`
// Network namespace file path to execute nsenter command
NetNamespaceFilePath string
// conn is a connection to the Ceph cluster obtained from a ConnPool // conn is a connection to the Ceph cluster obtained from a ConnPool
conn *util.ClusterConnection conn *util.ClusterConnection

View File

@ -177,7 +177,13 @@ 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, pvcNamespace, cr) rbdVolID, err := rbd.RegenerateJournal(
pv.Spec.CSI.VolumeAttributes,
pv.Spec.ClaimRef.Name,
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)

View File

@ -329,6 +329,12 @@ func (cs *ControllerServer) CreateVolume(
return nil, err return nil, err
} }
// Set Metadata on PV Create
err = rbdVol.setVolumeMetadata(req.GetParameters())
if err != nil {
return nil, err
}
return buildCreateVolumeResponse(req, rbdVol), nil return buildCreateVolumeResponse(req, rbdVol), nil
} }
@ -446,6 +452,12 @@ func (cs *ControllerServer) repairExistingVolume(ctx context.Context, req *csi.C
} }
} }
// Set metadata on restart of provisioner pod when image exist
err := rbdVol.setVolumeMetadata(req.GetParameters())
if err != nil {
return nil, err
}
return buildCreateVolumeResponse(req, rbdVol), nil return buildCreateVolumeResponse(req, rbdVol), nil
} }
@ -951,6 +963,7 @@ func (cs *ControllerServer) ValidateVolumeCapabilities(
} }
// CreateSnapshot creates the snapshot in backend and stores metadata in store. // CreateSnapshot creates the snapshot in backend and stores metadata in store.
// nolint:cyclop // TODO: reduce complexity
func (cs *ControllerServer) CreateSnapshot( func (cs *ControllerServer) CreateSnapshot(
ctx context.Context, ctx context.Context,
req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
@ -1024,7 +1037,7 @@ func (cs *ControllerServer) CreateSnapshot(
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
if found { if found {
return cloneFromSnapshot(ctx, rbdVol, rbdSnap, cr) return cloneFromSnapshot(ctx, rbdVol, rbdSnap, cr, req.GetParameters())
} }
err = flattenTemporaryClonedImages(ctx, rbdVol, cr) err = flattenTemporaryClonedImages(ctx, rbdVol, cr)
@ -1050,6 +1063,16 @@ func (cs *ControllerServer) CreateSnapshot(
return nil, status.Error(codes.Internal, err.Error()) return nil, status.Error(codes.Internal, err.Error())
} }
// Update the metadata on snapshot not on the original image
rbdVol.RbdImageName = rbdSnap.RbdSnapName
// Set snapshot-name/snapshot-namespace/snapshotcontent-name details
// on RBD backend image as metadata on create
err = rbdVol.setSnapshotMetadata(req.GetParameters())
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.CreateSnapshotResponse{ return &csi.CreateSnapshotResponse{
Snapshot: &csi.Snapshot{ Snapshot: &csi.Snapshot{
SizeBytes: vol.VolSize, SizeBytes: vol.VolSize,
@ -1067,7 +1090,8 @@ func cloneFromSnapshot(
ctx context.Context, ctx context.Context,
rbdVol *rbdVolume, rbdVol *rbdVolume,
rbdSnap *rbdSnapshot, rbdSnap *rbdSnapshot,
cr *util.Credentials) (*csi.CreateSnapshotResponse, error) { cr *util.Credentials,
parameters map[string]string) (*csi.CreateSnapshotResponse, error) {
vol := generateVolFromSnap(rbdSnap) vol := generateVolFromSnap(rbdSnap)
err := vol.Connect(cr) err := vol.Connect(cr)
if err != nil { if err != nil {
@ -1100,6 +1124,15 @@ func cloneFromSnapshot(
return nil, status.Errorf(codes.Internal, err.Error()) return nil, status.Errorf(codes.Internal, err.Error())
} }
// Update snapshot-name/snapshot-namespace/snapshotcontent-name details on
// RBD backend image as metadata on restart of provisioner pod when image exist
if len(parameters) != 0 {
err = rbdVol.setSnapshotMetadata(parameters)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}
return &csi.CreateSnapshotResponse{ return &csi.CreateSnapshotResponse{
Snapshot: &csi.Snapshot{ Snapshot: &csi.Snapshot{
SizeBytes: rbdSnap.VolSize, SizeBytes: rbdSnap.VolSize,

View File

@ -153,6 +153,8 @@ func (r *Driver) Run(conf *util.Config) {
log.FatalLogMsg(err.Error()) log.FatalLogMsg(err.Error())
} }
rbd.SetGlobalInt("krbdFeatures", krbdFeatures) rbd.SetGlobalInt("krbdFeatures", krbdFeatures)
rbd.SetRbdNbdToolFeatures()
} }
if conf.IsControllerServer { if conf.IsControllerServer {

View File

@ -332,6 +332,10 @@ func (ns *NodeServer) NodeStageVolume(
} }
defer rv.Destroy() defer rv.Destroy()
rv.NetNamespaceFilePath, err = util.GetNetNamespaceFilePath(util.CsiConfigFile, rv.ClusterID)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if isHealer { if isHealer {
err = healerStageTransaction(ctx, cr, rv, stagingParentPath) err = healerStageTransaction(ctx, cr, rv, stagingParentPath)
if err != nil { if err != nil {

View File

@ -92,10 +92,6 @@ var (
} }
) )
func init() {
setRbdNbdToolFeatures()
}
// rbdDeviceInfo strongly typed JSON spec for rbd device list output (of type krbd). // rbdDeviceInfo strongly typed JSON spec for rbd device list output (of type krbd).
type rbdDeviceInfo struct { type rbdDeviceInfo struct {
ID string `json:"id"` ID string `json:"id"`
@ -216,8 +212,9 @@ func waitForPath(ctx context.Context, pool, namespace, image string, maxRetries
return "", false return "", false
} }
// set features available with rbd-nbd, and NBD module loaded status. // SetRbdNbdToolFeatures sets features available with rbd-nbd, and NBD module
func setRbdNbdToolFeatures() { // loaded status.
func SetRbdNbdToolFeatures() {
var stderr string var stderr string
// check if the module is loaded or compiled in // check if the module is loaded or compiled in
_, err := os.Stat(fmt.Sprintf("/sys/module/%s", moduleNbd)) _, err := os.Stat(fmt.Sprintf("/sys/module/%s", moduleNbd))
@ -457,8 +454,17 @@ func createPath(ctx context.Context, volOpt *rbdVolume, device string, cr *util.
mapArgs = append(mapArgs, "--read-only") mapArgs = append(mapArgs, "--read-only")
} }
// Execute map var (
stdout, stderr, err := util.ExecCommand(ctx, cli, mapArgs...) stdout string
stderr string
err error
)
if volOpt.NetNamespaceFilePath != "" {
stdout, stderr, err = util.ExecuteCommandWithNSEnter(ctx, volOpt.NetNamespaceFilePath, cli, mapArgs...)
} else {
stdout, stderr, err = util.ExecCommand(ctx, cli, mapArgs...)
}
if err != nil { if err != nil {
log.WarningLog(ctx, "rbd: map error %v, rbd output: %s", err, stderr) log.WarningLog(ctx, "rbd: map error %v, rbd output: %s", err, stderr)
// unmap rbd image if connection timeout // unmap rbd image if connection timeout

View File

@ -23,6 +23,7 @@ import (
"github.com/ceph/ceph-csi/internal/journal" "github.com/ceph/ceph-csi/internal/journal"
"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"
) )
@ -533,9 +534,13 @@ func undoVolReservation(ctx context.Context, rbdVol *rbdVolume, cr *util.Credent
// Generate new volume Handler // Generate new volume Handler
// The volume handler won't remain same as its contains poolID,clusterID etc // The volume handler won't remain same as its contains poolID,clusterID etc
// which are not same across clusters. // which are not same across clusters.
// nolint:gocyclo,cyclop // TODO: reduce complexity
func RegenerateJournal( func RegenerateJournal(
volumeAttributes map[string]string, volumeAttributes map[string]string,
volumeID, requestName, owner string, claimName,
volumeID,
requestName,
owner string,
cr *util.Credentials) (string, error) { cr *util.Credentials) (string, error) {
ctx := context.Background() ctx := context.Background()
var ( var (
@ -598,16 +603,24 @@ func RegenerateJournal(
if err != nil { if err != nil {
return "", err return "", err
} }
if imageData != nil { if imageData != nil {
rbdVol.ReservedID = imageData.ImageUUID rbdVol.ReservedID = imageData.ImageUUID
rbdVol.ImageID = imageData.ImageAttributes.ImageID rbdVol.ImageID = imageData.ImageAttributes.ImageID
rbdVol.Owner = imageData.ImageAttributes.Owner rbdVol.Owner = imageData.ImageAttributes.Owner
rbdVol.RbdImageName = imageData.ImageAttributes.ImageName
if rbdVol.ImageID == "" { if rbdVol.ImageID == "" {
err = rbdVol.storeImageID(ctx, j) err = rbdVol.storeImageID(ctx, j)
if err != nil { if err != nil {
return "", err return "", err
} }
} }
// Update Metadata on reattach of the same old PV
parameters := k8s.PrepareVolumeMetadata(claimName, rbdVol.Owner, "")
err = rbdVol.setVolumeMetadata(parameters)
if err != nil {
return "", fmt.Errorf("failed to set volume metadata: %w", err)
}
// As the omap already exists for this image ID return nil. // As the omap already exists for this image ID return nil.
rbdVol.VolID, err = util.GenerateVolID(ctx, rbdVol.Monitors, cr, imagePoolID, rbdVol.Pool, rbdVol.VolID, err = util.GenerateVolID(ctx, rbdVol.Monitors, cr, imagePoolID, rbdVol.Pool,
rbdVol.ClusterID, rbdVol.ReservedID, volIDVersion) rbdVol.ClusterID, rbdVol.ReservedID, volIDVersion)

View File

@ -28,6 +28,7 @@ import (
"time" "time"
"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/ceph/go-ceph/rados" "github.com/ceph/go-ceph/rados"
@ -156,6 +157,8 @@ type rbdVolume struct {
LogStrategy string LogStrategy string
VolName string VolName string
MonValueFromSecret string MonValueFromSecret string
// Network namespace file path to execute nsenter command
NetNamespaceFilePath string
// RequestedVolSize has the size of the volume requested by the user and // RequestedVolSize has the size of the volume requested by the user and
// this value will not be updated when doing getImageInfo() on rbdVolume. // this value will not be updated when doing getImageInfo() on rbdVolume.
RequestedVolSize int64 RequestedVolSize int64
@ -1918,3 +1921,28 @@ func genVolFromVolIDWithMigration(
return rv, err return rv, err
} }
// setVolumeMetadata set PV/PVC/PVCNamespace metadata on RBD image.
func (rv *rbdVolume) setVolumeMetadata(parameters map[string]string) error {
for k, v := range k8s.GetVolumeMetadata(parameters) {
err := rv.SetMetadata(k, v)
if err != nil {
return fmt.Errorf("failed to set metadata key %q, value %q on image: %w", k, v, err)
}
}
return nil
}
// setSnapshotMetadata Set snapshot-name/snapshot-namespace/snapshotcontent-name metadata
// on RBD image.
func (rv *rbdVolume) setSnapshotMetadata(parameters map[string]string) error {
for k, v := range k8s.GetSnapshotMetadata(parameters) {
err := rv.SetMetadata(k, v)
if err != nil {
return fmt.Errorf("failed to set metadata key %q, value %q on image: %w", k, v, err)
}
}
return nil
}

View File

@ -21,6 +21,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"time" "time"
@ -32,6 +33,47 @@ import (
// InvalidPoolID used to denote an invalid pool. // InvalidPoolID used to denote an invalid pool.
const InvalidPoolID int64 = -1 const InvalidPoolID int64 = -1
// ExecuteCommandWithNSEnter executes passed in program with args with nsenter
// and returns separate stdout and stderr streams. In case ctx is not set to
// context.TODO(), the command will be logged after it was executed.
func ExecuteCommandWithNSEnter(ctx context.Context, netPath, program string, args ...string) (string, string, error) {
var (
sanitizedArgs = StripSecretInArgs(args)
stdoutBuf bytes.Buffer
stderrBuf bytes.Buffer
)
// check netPath exists
if _, err := os.Stat(netPath); err != nil {
return "", "", fmt.Errorf("failed to get stat for %s %w", netPath, err)
}
// nsenter --net=%s -- <program> <args>
args = append([]string{fmt.Sprintf("--net=%s", netPath), "--", program}, args...)
cmd := exec.Command("nsenter", args...) // #nosec:G204, commands executing not vulnerable.
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
err := cmd.Run()
stdout := stdoutBuf.String()
stderr := stderrBuf.String()
if err != nil {
err = fmt.Errorf("an error (%w) occurred while running %s args: %v", err, program, sanitizedArgs)
if ctx != context.TODO() {
log.UsefulLog(ctx, "%s", err)
}
return stdout, stderr, err
}
if ctx != context.TODO() {
log.UsefulLog(ctx, "command succeeded: %s %v", program, sanitizedArgs)
}
return stdout, stderr, nil
}
// ExecCommand executes passed in program with args and returns separate stdout // ExecCommand executes passed in program with args and returns separate stdout
// and stderr streams. In case ctx is not set to context.TODO(), the command // and stderr streams. In case ctx is not set to context.TODO(), the command
// will be logged after it was executed. // will be logged after it was executed.

View File

@ -49,6 +49,8 @@ type ClusterInfo struct {
// SubvolumeGroup contains the name of the SubvolumeGroup for CSI volumes // SubvolumeGroup contains the name of the SubvolumeGroup for CSI volumes
SubvolumeGroup string `json:"subvolumeGroup"` SubvolumeGroup string `json:"subvolumeGroup"`
} `json:"cephFS"` } `json:"cephFS"`
// symlink filepath for the network namespace where we need to execute commands.
NetNamespaceFilePath string `json:"netNamespaceFilePath"`
} }
// Expected JSON structure in the passed in config file is, // Expected JSON structure in the passed in config file is,
@ -161,3 +163,12 @@ func GetClusterID(options map[string]string) (string, error) {
return clusterID, nil return clusterID, nil
} }
func GetNetNamespaceFilePath(pathToConfig, clusterID string) (string, error) {
cluster, err := readClusterInfo(pathToConfig, clusterID)
if err != nil {
return "", err
}
return cluster.NetNamespaceFilePath, nil
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package util package util
import ( import (
"encoding/json"
"os" "os"
"testing" "testing"
) )
@ -138,3 +139,69 @@ func TestCSIConfig(t *testing.T) {
t.Errorf("Test setup error %s", err) t.Errorf("Test setup error %s", err)
} }
} }
func TestGetNetNamespaceFilePath(t *testing.T) {
t.Parallel()
tests := []struct {
name string
clusterID string
want string
}{
{
name: "get NetNamespaceFilePath for cluster-1",
clusterID: "cluster-1",
want: "/var/lib/kubelet/plugins/rbd.ceph.csi.com/cluster1-net",
},
{
name: "get NetNamespaceFilePath for cluster-2",
clusterID: "cluster-2",
want: "/var/lib/kubelet/plugins/rbd.ceph.csi.com/cluster2-net",
},
{
name: "when NetNamespaceFilePath is empty",
clusterID: "cluster-3",
want: "",
},
}
csiConfig := []ClusterInfo{
{
ClusterID: "cluster-1",
Monitors: []string{"ip-1", "ip-2"},
NetNamespaceFilePath: "/var/lib/kubelet/plugins/rbd.ceph.csi.com/cluster1-net",
},
{
ClusterID: "cluster-2",
Monitors: []string{"ip-3", "ip-4"},
NetNamespaceFilePath: "/var/lib/kubelet/plugins/rbd.ceph.csi.com/cluster2-net",
},
{
ClusterID: "cluster-3",
Monitors: []string{"ip-5", "ip-6"},
},
}
csiConfigFileContent, err := json.Marshal(csiConfig)
if err != nil {
t.Errorf("failed to marshal csi config info %v", err)
}
tmpConfPath := t.TempDir() + "/ceph-csi.json"
err = os.WriteFile(tmpConfPath, csiConfigFileContent, 0o600)
if err != nil {
t.Errorf("failed to write %s file content: %v", CsiConfigFile, err)
}
for _, tt := range tests {
ts := tt
t.Run(ts.name, func(t *testing.T) {
t.Parallel()
got, err := GetNetNamespaceFilePath(tmpConfPath, ts.clusterID)
if err != nil {
t.Errorf("GetNetNamespaceFilePath() error = %v", err)
return
}
if got != ts.want {
t.Errorf("GetNetNamespaceFilePath() = %v, want %v", got, ts.want)
}
})
}
}

View File

@ -23,7 +23,17 @@ import (
// to the driver on CreateVolumeRequest/CreateSnapshotRequest calls. // to the driver on CreateVolumeRequest/CreateSnapshotRequest calls.
const ( const (
csiParameterPrefix = "csi.storage.k8s.io/" csiParameterPrefix = "csi.storage.k8s.io/"
// PV and PVC metadata keys used by external provisioner as part of
// create requests as parameters, when `extra-create-metadata` is true.
pvcNameKey = "csi.storage.k8s.io/pvc/name"
pvcNamespaceKey = "csi.storage.k8s.io/pvc/namespace" pvcNamespaceKey = "csi.storage.k8s.io/pvc/namespace"
pvNameKey = "csi.storage.k8s.io/pv/name"
// snapshot metadata keys.
volSnapNameKey = "csi.storage.k8s.io/volumesnapshot/name"
volSnapNamespaceKey = "csi.storage.k8s.io/volumesnapshot/namespace"
volSnapContentNameKey = "csi.storage.k8s.io/volumesnapshotcontent/name"
) )
// RemoveCSIPrefixedParameters removes parameters prefixed with csiParameterPrefix. // RemoveCSIPrefixedParameters removes parameters prefixed with csiParameterPrefix.
@ -43,3 +53,50 @@ func RemoveCSIPrefixedParameters(param map[string]string) map[string]string {
func GetOwner(param map[string]string) string { func GetOwner(param map[string]string) string {
return param[pvcNamespaceKey] return param[pvcNamespaceKey]
} }
// GetVolumeMetadata filter parameters, only return PV/PVC/PVCNamespace metadata.
func GetVolumeMetadata(parameters map[string]string) map[string]string {
keys := []string{pvcNameKey, pvcNamespaceKey, pvNameKey}
newParam := map[string]string{}
for k, v := range parameters {
for _, key := range keys {
if strings.Contains(k, key) {
newParam[k] = v
}
}
}
return newParam
}
// PrepareVolumeMetadata return PV/PVC/PVCNamespace metadata based on inputs.
func PrepareVolumeMetadata(pvcName, pvcNamespace, pvName string) map[string]string {
newParam := map[string]string{}
if pvcName != "" {
newParam[pvcNameKey] = pvcName
}
if pvcNamespace != "" {
newParam[pvcNamespaceKey] = pvcNamespace
}
if pvName != "" {
newParam[pvNameKey] = pvName
}
return newParam
}
// GetSnapshotMetadata filter parameters, only return
// snapshot-name/snapshot-namespace/snapshotcontent-name metadata.
func GetSnapshotMetadata(parameters map[string]string) map[string]string {
keys := []string{volSnapNameKey, volSnapNamespaceKey, volSnapContentNameKey}
newParam := map[string]string{}
for k, v := range parameters {
for _, key := range keys {
if strings.Contains(k, key) {
newParam[k] = v
}
}
}
return newParam
}

View File

@ -90,7 +90,7 @@ type Config struct {
EnableGRPCMetrics bool // option to enable grpc metrics EnableGRPCMetrics bool // option to enable grpc metrics
EnableProfiling bool // flag to enable profiling EnableProfiling bool // flag to enable profiling
IsControllerServer bool // if set to true start provisoner server IsControllerServer bool // if set to true start provisioner server
IsNodeServer bool // if set to true start node server IsNodeServer bool // if set to true start node server
Version bool // cephcsi version Version bool // cephcsi version
@ -164,7 +164,7 @@ type KernelVersion struct {
SubLevel int SubLevel int
ExtraVersion int // prefix of the part after the first "-" ExtraVersion int // prefix of the part after the first "-"
Distribution string // component of full extraversion Distribution string // component of full extraversion
Backport bool // backports have a fixed version/patchlevel/sublevel Backport bool // backport have a fixed version/patchlevel/sublevel
} }
// parseKernelRelease parses a kernel release version string into: // parseKernelRelease parses a kernel release version string into:
@ -202,9 +202,9 @@ func parseKernelRelease(release string) (int, int, int, int, error) {
// CheckKernelSupport checks the running kernel and comparing it to known // CheckKernelSupport checks the running kernel and comparing it to known
// versions that have support for required features . Distributors of // versions that have support for required features . Distributors of
// enterprise Linux have backported quota support to previous versions. This // enterprise Linux have backport quota support to previous versions. This
// function checks if the running kernel is one of the versions that have the // function checks if the running kernel is one of the versions that have the
// feature/fixes backported. // feature/fixes backport.
// //
// `uname -r` (or Uname().Utsname.Release has a format like 1.2.3-rc.vendor // `uname -r` (or Uname().Utsname.Release has a format like 1.2.3-rc.vendor
// This can be slit up in the following components: - version (1) - patchlevel // This can be slit up in the following components: - version (1) - patchlevel

View File

@ -1,6 +1,6 @@
#!/bin/bash -e #!/bin/bash -e
# This script can be used to install/delete snapshotcontroller and snapshot beta CRD # This script can be used to install/delete snapshotcontroller and snapshot CRD
SCRIPT_DIR="$(dirname "${0}")" SCRIPT_DIR="$(dirname "${0}")"

View File

@ -2,6 +2,7 @@ package aws
import ( import (
"context" "context"
"fmt"
"sync/atomic" "sync/atomic"
"time" "time"
@ -24,11 +25,13 @@ type CredentialsCacheOptions struct {
// If ExpiryWindow is 0 or less it will be ignored. // If ExpiryWindow is 0 or less it will be ignored.
ExpiryWindow time.Duration ExpiryWindow time.Duration
// ExpiryWindowJitterFrac provides a mechanism for randomizing the expiration of credentials // ExpiryWindowJitterFrac provides a mechanism for randomizing the
// within the configured ExpiryWindow by a random percentage. Valid values are between 0.0 and 1.0. // 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 // As an example if ExpiryWindow is 60 seconds and ExpiryWindowJitterFrac
// expire between 30 to 60 seconds prior to their actual expiration time. // 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 ExpiryWindow is 0 or less then ExpiryWindowJitterFrac is ignored.
// If ExpiryWindowJitterFrac is 0 then no randomization will be applied to the window. // If ExpiryWindowJitterFrac is 0 then no randomization will be applied to the window.
@ -39,8 +42,19 @@ type CredentialsCacheOptions struct {
// CredentialsCache provides caching and concurrency safe credentials retrieval // CredentialsCache provides caching and concurrency safe credentials retrieval
// via the provider's retrieve method. // via the provider's retrieve method.
//
// CredentialsCache will look for optional interfaces on the Provider to adjust
// how the credential cache handles credentials caching.
//
// * HandleFailRefreshCredentialsCacheStrategy - Allows provider to handle
// credential refresh failures. This could return an updated Credentials
// value, or attempt another means of retrieving credentials.
//
// * AdjustExpiresByCredentialsCacheStrategy - Allows provider to adjust how
// credentials Expires is modified. This could modify how the Credentials
// Expires is adjusted based on the CredentialsCache ExpiryWindow option.
// Such as providing a floor not to reduce the Expires below.
type CredentialsCache struct { type CredentialsCache struct {
// provider is the CredentialProvider implementation to be wrapped by the CredentialCache.
provider CredentialsProvider provider CredentialsProvider
options CredentialsCacheOptions options CredentialsCacheOptions
@ -48,8 +62,9 @@ type CredentialsCache struct {
sf singleflight.Group sf singleflight.Group
} }
// NewCredentialsCache returns a CredentialsCache that wraps provider. Provider is expected to not be nil. A variadic // NewCredentialsCache returns a CredentialsCache that wraps provider. Provider
// list of one or more functions can be provided to modify the CredentialsCache configuration. This allows for // 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. // configuration of credential expiry window and jitter.
func NewCredentialsCache(provider CredentialsProvider, optFns ...func(options *CredentialsCacheOptions)) *CredentialsCache { func NewCredentialsCache(provider CredentialsProvider, optFns ...func(options *CredentialsCacheOptions)) *CredentialsCache {
options := CredentialsCacheOptions{} options := CredentialsCacheOptions{}
@ -81,8 +96,8 @@ func NewCredentialsCache(provider CredentialsProvider, optFns ...func(options *C
// //
// Returns and error if the provider's retrieve method returns an error. // Returns and error if the provider's retrieve method returns an error.
func (p *CredentialsCache) Retrieve(ctx context.Context) (Credentials, error) { func (p *CredentialsCache) Retrieve(ctx context.Context) (Credentials, error) {
if creds := p.getCreds(); creds != nil { if creds, ok := p.getCreds(); ok && !creds.Expired() {
return *creds, nil return creds, nil
} }
resCh := p.sf.DoChan("", func() (interface{}, error) { resCh := p.sf.DoChan("", func() (interface{}, error) {
@ -97,39 +112,64 @@ func (p *CredentialsCache) Retrieve(ctx context.Context) (Credentials, error) {
} }
func (p *CredentialsCache) singleRetrieve(ctx context.Context) (interface{}, error) { func (p *CredentialsCache) singleRetrieve(ctx context.Context) (interface{}, error) {
if creds := p.getCreds(); creds != nil { currCreds, ok := p.getCreds()
return *creds, nil if ok && !currCreds.Expired() {
return currCreds, nil
}
newCreds, err := p.provider.Retrieve(ctx)
if err != nil {
handleFailToRefresh := defaultHandleFailToRefresh
if cs, ok := p.provider.(HandleFailRefreshCredentialsCacheStrategy); ok {
handleFailToRefresh = cs.HandleFailToRefresh
}
newCreds, err = handleFailToRefresh(ctx, currCreds, err)
if err != nil {
return Credentials{}, fmt.Errorf("failed to refresh cached credentials, %w", err)
}
}
if newCreds.CanExpire && p.options.ExpiryWindow > 0 {
adjustExpiresBy := defaultAdjustExpiresBy
if cs, ok := p.provider.(AdjustExpiresByCredentialsCacheStrategy); ok {
adjustExpiresBy = cs.AdjustExpiresBy
} }
creds, err := p.provider.Retrieve(ctx)
if err == nil {
if creds.CanExpire {
randFloat64, err := sdkrand.CryptoRandFloat64() randFloat64, err := sdkrand.CryptoRandFloat64()
if err != nil { if err != nil {
return Credentials{}, err return Credentials{}, fmt.Errorf("failed to get random provider, %w", 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) var jitter time.Duration
if p.options.ExpiryWindowJitterFrac > 0 {
jitter = time.Duration(randFloat64 *
p.options.ExpiryWindowJitterFrac * float64(p.options.ExpiryWindow))
} }
return creds, err newCreds, err = adjustExpiresBy(newCreds, -(p.options.ExpiryWindow - jitter))
if err != nil {
return Credentials{}, fmt.Errorf("failed to adjust credentials expires, %w", err)
}
} }
func (p *CredentialsCache) getCreds() *Credentials { p.creds.Store(&newCreds)
return newCreds, nil
}
// getCreds returns the currently stored credentials and true. Returning false
// if no credentials were stored.
func (p *CredentialsCache) getCreds() (Credentials, bool) {
v := p.creds.Load() v := p.creds.Load()
if v == nil { if v == nil {
return nil return Credentials{}, false
} }
c := v.(*Credentials) c := v.(*Credentials)
if c != nil && c.HasKeys() && !c.Expired() { if c == nil || !c.HasKeys() {
return c return Credentials{}, false
} }
return nil return *c, true
} }
// Invalidate will invalidate the cached credentials. The next call to Retrieve // Invalidate will invalidate the cached credentials. The next call to Retrieve
@ -137,3 +177,42 @@ func (p *CredentialsCache) getCreds() *Credentials {
func (p *CredentialsCache) Invalidate() { func (p *CredentialsCache) Invalidate() {
p.creds.Store((*Credentials)(nil)) p.creds.Store((*Credentials)(nil))
} }
// HandleFailRefreshCredentialsCacheStrategy is an interface for
// CredentialsCache to allow CredentialsProvider how failed to refresh
// credentials is handled.
type HandleFailRefreshCredentialsCacheStrategy interface {
// Given the previously cached Credentials, if any, and refresh error, may
// returns new or modified set of Credentials, or error.
//
// Credential caches may use default implementation if nil.
HandleFailToRefresh(context.Context, Credentials, error) (Credentials, error)
}
// defaultHandleFailToRefresh returns the passed in error.
func defaultHandleFailToRefresh(ctx context.Context, _ Credentials, err error) (Credentials, error) {
return Credentials{}, err
}
// AdjustExpiresByCredentialsCacheStrategy is an interface for CredentialCache
// to allow CredentialsProvider to intercept adjustments to Credentials expiry
// based on expectations and use cases of CredentialsProvider.
//
// Credential caches may use default implementation if nil.
type AdjustExpiresByCredentialsCacheStrategy interface {
// Given a Credentials as input, applying any mutations and
// returning the potentially updated Credentials, or error.
AdjustExpiresBy(Credentials, time.Duration) (Credentials, error)
}
// defaultAdjustExpiresBy adds the duration to the passed in credentials Expires,
// and returns the updated credentials value. If Credentials value's CanExpire
// is false, the passed in credentials are returned unchanged.
func defaultAdjustExpiresBy(creds Credentials, dur time.Duration) (Credentials, error) {
if !creds.CanExpire {
return creds, nil
}
creds.Expires = creds.Expires.Add(dur)
return creds, nil
}

View File

@ -83,16 +83,20 @@ type Credentials struct {
// Source of the credentials // Source of the credentials
Source string Source string
// Time the credentials will expire. // States if the credentials can expire or not.
CanExpire bool CanExpire bool
// The time the credentials will expire at. Should be ignored if CanExpire
// is false.
Expires time.Time Expires time.Time
} }
// Expired returns if the credentials have expired. // Expired returns if the credentials have expired.
func (v Credentials) Expired() bool { func (v Credentials) Expired() bool {
if v.CanExpire { if v.CanExpire {
// Calling Round(0) on the current time will truncate the monotonic reading only. Ensures credential expiry // Calling Round(0) on the current time will truncate the monotonic
// time is always based on reported wall-clock time. // reading only. Ensures credential expiry time is always based on
// reported wall-clock time.
return !v.Expires.After(sdk.NowTime().Round(0)) return !v.Expires.After(sdk.NowTime().Round(0))
} }

View File

@ -3,4 +3,4 @@
package aws package aws
// goModuleVersion is the tagged release for this module // goModuleVersion is the tagged release for this module
const goModuleVersion = "1.15.0" const goModuleVersion = "1.16.2"

View File

@ -1,3 +1,15 @@
# v1.1.9 (2022-03-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.8 (2022-03-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.7 (2022-03-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.6 (2022-03-08) # v1.1.6 (2022-03-08)
* **Dependency Update**: Updated to the latest SDK module versions * **Dependency Update**: Updated to the latest SDK module versions

View File

@ -3,4 +3,4 @@
package configsources package configsources
// goModuleVersion is the tagged release for this module // goModuleVersion is the tagged release for this module
const goModuleVersion = "1.1.6" const goModuleVersion = "1.1.9"

View File

@ -1,3 +1,15 @@
# v2.4.3 (2022-03-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.4.2 (2022-03-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.4.1 (2022-03-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.4.0 (2022-03-08) # v2.4.0 (2022-03-08)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version * **Feature**: Updated `github.com/aws/smithy-go` to latest version

View File

@ -3,4 +3,4 @@
package endpoints package endpoints
// goModuleVersion is the tagged release for this module // goModuleVersion is the tagged release for this module
const goModuleVersion = "2.4.0" const goModuleVersion = "2.4.3"

View File

@ -29,5 +29,5 @@ func Float64(reader io.Reader) (float64, error) {
// CryptoRandFloat64 returns a random float64 obtained from the crypto rand // CryptoRandFloat64 returns a random float64 obtained from the crypto rand
// source. // source.
func CryptoRandFloat64() (float64, error) { func CryptoRandFloat64() (float64, error) {
return Float64(rand.Reader) return Float64(Reader)
} }

View File

@ -1,3 +1,15 @@
# v1.9.3 (2022-03-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.9.2 (2022-03-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.9.1 (2022-03-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.9.0 (2022-03-08) # v1.9.0 (2022-03-08)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version * **Feature**: Updated `github.com/aws/smithy-go` to latest version

View File

@ -3,4 +3,4 @@
package presignedurl package presignedurl
// goModuleVersion is the tagged release for this module // goModuleVersion is the tagged release for this module
const goModuleVersion = "1.9.0" const goModuleVersion = "1.9.3"

View File

@ -1,3 +1,15 @@
# v1.16.3 (2022-03-30)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.2 (2022-03-24)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.1 (2022-03-23)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.16.0 (2022-03-08) # v1.16.0 (2022-03-08)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version * **Feature**: Updated `github.com/aws/smithy-go` to latest version

View File

@ -3,4 +3,4 @@
package sts package sts
// goModuleVersion is the tagged release for this module // goModuleVersion is the tagged release for this module
const goModuleVersion = "1.16.0" const goModuleVersion = "1.16.3"

View File

@ -1,3 +1,7 @@
# Release (v1.11.2)
* No change notes available for this release.
# Release (v1.11.1) # Release (v1.11.1)
## Module Highlights ## Module Highlights

View File

@ -20,6 +20,9 @@ func newURIValue(path *[]byte, rawPath *[]byte, buffer *[]byte, key string) URIV
func (u URIValue) modifyURI(value string) (err error) { func (u URIValue) modifyURI(value string) (err error) {
*u.path, *u.buffer, err = replacePathElement(*u.path, *u.buffer, u.key, value, false) *u.path, *u.buffer, err = replacePathElement(*u.path, *u.buffer, u.key, value, false)
if err != nil {
return err
}
*u.rawPath, *u.buffer, err = replacePathElement(*u.rawPath, *u.buffer, u.key, value, true) *u.rawPath, *u.buffer, err = replacePathElement(*u.rawPath, *u.buffer, u.key, value, true)
return err return err
} }

View File

@ -3,4 +3,4 @@
package smithy package smithy
// goModuleVersion is the tagged release for this module // goModuleVersion is the tagged release for this module
const goModuleVersion = "1.11.1" const goModuleVersion = "1.11.2"

12
vendor/modules.txt vendored
View File

@ -53,7 +53,7 @@ github.com/aws/aws-sdk-go/service/sso
github.com/aws/aws-sdk-go/service/sso/ssoiface github.com/aws/aws-sdk-go/service/sso/ssoiface
github.com/aws/aws-sdk-go/service/sts github.com/aws/aws-sdk-go/service/sts
github.com/aws/aws-sdk-go/service/sts/stsiface github.com/aws/aws-sdk-go/service/sts/stsiface
# github.com/aws/aws-sdk-go-v2 v1.15.0 # github.com/aws/aws-sdk-go-v2 v1.16.2
## explicit; go 1.15 ## explicit; go 1.15
github.com/aws/aws-sdk-go-v2/aws github.com/aws/aws-sdk-go-v2/aws
github.com/aws/aws-sdk-go-v2/aws/defaults github.com/aws/aws-sdk-go-v2/aws/defaults
@ -70,21 +70,21 @@ github.com/aws/aws-sdk-go-v2/internal/sdk
github.com/aws/aws-sdk-go-v2/internal/strings github.com/aws/aws-sdk-go-v2/internal/strings
github.com/aws/aws-sdk-go-v2/internal/sync/singleflight github.com/aws/aws-sdk-go-v2/internal/sync/singleflight
github.com/aws/aws-sdk-go-v2/internal/timeconv github.com/aws/aws-sdk-go-v2/internal/timeconv
# github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 # github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9
## explicit; go 1.15 ## explicit; go 1.15
github.com/aws/aws-sdk-go-v2/internal/configsources github.com/aws/aws-sdk-go-v2/internal/configsources
# github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0 # github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3
## explicit; go 1.15 ## explicit; go 1.15
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2
# github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.0 # github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3
## explicit; go 1.15 ## explicit; go 1.15
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url github.com/aws/aws-sdk-go-v2/service/internal/presigned-url
# github.com/aws/aws-sdk-go-v2/service/sts v1.16.0 # github.com/aws/aws-sdk-go-v2/service/sts v1.16.3
## explicit; go 1.15 ## explicit; go 1.15
github.com/aws/aws-sdk-go-v2/service/sts github.com/aws/aws-sdk-go-v2/service/sts
github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints
github.com/aws/aws-sdk-go-v2/service/sts/types github.com/aws/aws-sdk-go-v2/service/sts/types
# github.com/aws/smithy-go v1.11.1 # github.com/aws/smithy-go v1.11.2
## explicit; go 1.15 ## explicit; go 1.15
github.com/aws/smithy-go github.com/aws/smithy-go
github.com/aws/smithy-go/document github.com/aws/smithy-go/document