From 650eba6904fa3f6d826e0450709cdd2700ec31ff Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Tue, 31 Aug 2021 08:39:55 +0200 Subject: [PATCH] generic ephemeral volumes: helper functions The name concatenation and ownership check were originally considered small enough to not warrant dedicated functions, but the intent of the code is more readable with them. --- .../storage/ephemeral/ephemeral.go | 57 ++++++++ .../storage/ephemeral/ephemeral_test.go | 126 ++++++++++++++++++ vendor/modules.txt | 1 + 3 files changed, 184 insertions(+) create mode 100644 staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral.go create mode 100644 staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral_test.go diff --git a/staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral.go b/staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral.go new file mode 100644 index 00000000000..67dddaf5c31 --- /dev/null +++ b/staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral.go @@ -0,0 +1,57 @@ +/* +Copyright 2021 The Kubernetes 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 ephemeral provides code that supports the usual pattern +// for accessing the PVC that provides a generic ephemeral inline volume: +// +// - determine the PVC name that corresponds to the inline volume source +// - retrieve the PVC +// - verify that the PVC is owned by the pod +// - use the PVC +package ephemeral + +import ( + "fmt" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// VolumeClaimName returns the name of the PersistentVolumeClaim +// object that gets created for the generic ephemeral inline volume. The +// name is deterministic and therefore this function does not need any +// additional information besides the Pod name and volume name and it +// will never fail. +// +// Before using the PVC for the Pod, the caller must check that it is +// indeed the PVC that was created for the Pod by calling IsUsable. +func VolumeClaimName(pod *v1.Pod, volume *v1.Volume) string { + return pod.Name + "-" + volume.Name +} + +// VolumeIsForPod checks that the PVC is the ephemeral volume that +// was created for the Pod. It returns an error that is informative +// enough to be returned by the caller without adding further details +// about the Pod or PVC. +func VolumeIsForPod(pod *v1.Pod, pvc *v1.PersistentVolumeClaim) error { + // Checking the namespaces is just a precaution. The caller should + // never pass in a PVC that isn't from the same namespace as the + // Pod. + if pvc.Namespace != pod.Namespace || !metav1.IsControlledBy(pvc, pod) { + return fmt.Errorf("PVC %s/%s was not created for pod %s/%s (pod is not owner)", pvc.Namespace, pvc.Name, pod.Namespace, pod.Name) + } + return nil +} diff --git a/staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral_test.go b/staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral_test.go new file mode 100644 index 00000000000..09f1d5844eb --- /dev/null +++ b/staging/src/k8s.io/component-helpers/storage/ephemeral/ephemeral_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2021 The Kubernetes 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 ephemeral + +import ( + "fmt" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestVolumeIsForPod(t *testing.T) { + uid := 0 + newUID := func() types.UID { + uid++ + return types.UID(fmt.Sprintf("%d", uid)) + } + isController := true + + podNotOwner := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "podNotOwner", + UID: newUID(), + }, + } + podOwner := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "podOwner", + UID: newUID(), + }, + } + pvcNoOwner := &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "pvcNoOwner", + UID: newUID(), + }, + } + pvcWithOwner := &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "pvcNoOwner", + UID: newUID(), + OwnerReferences: []metav1.OwnerReference{ + { + UID: podOwner.UID, + Controller: &isController, + }, + }, + }, + } + userPVCWithOwner := &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "user-namespace", + Name: "userPVCWithOwner", + UID: newUID(), + OwnerReferences: []metav1.OwnerReference{ + { + UID: podOwner.UID, + Controller: &isController, + }, + }, + }, + } + + testcases := map[string]struct { + pod *v1.Pod + pvc *v1.PersistentVolumeClaim + expectedError string + }{ + "owned": { + pod: podOwner, + pvc: pvcWithOwner, + }, + "other-pod": { + pod: podNotOwner, + pvc: pvcWithOwner, + expectedError: `PVC kube-system/pvcNoOwner was not created for pod kube-system/podNotOwner (pod is not owner)`, + }, + "no-owner": { + pod: podOwner, + pvc: pvcNoOwner, + expectedError: `PVC kube-system/pvcNoOwner was not created for pod kube-system/podOwner (pod is not owner)`, + }, + "different-namespace": { + pod: podOwner, + pvc: userPVCWithOwner, + expectedError: `PVC user-namespace/userPVCWithOwner was not created for pod kube-system/podOwner (pod is not owner)`, + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + err := VolumeIsForPod(tc.pod, tc.pvc) + if tc.expectedError == "" { + if err != nil { + t.Errorf("expected no error, got %v", err) + } + } else { + if err == nil { + t.Errorf("expected error %q, got nil", tc.expectedError) + } else if tc.expectedError != err.Error() { + t.Errorf("expected error %q, got %v", tc.expectedError, err) + } + } + }) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 18b3f39ef90..9b51749c9ee 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1976,6 +1976,7 @@ k8s.io/component-helpers/node/utils/sysctl k8s.io/component-helpers/node/utils/sysctl/testing k8s.io/component-helpers/scheduling/corev1 k8s.io/component-helpers/scheduling/corev1/nodeaffinity +k8s.io/component-helpers/storage/ephemeral k8s.io/component-helpers/storage/volume # k8s.io/controller-manager v0.0.0 => ./staging/src/k8s.io/controller-manager ## explicit