k8s.io/kubernetes@v1.29.3/pkg/registry/core/persistentvolume/storage/storage_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package storage 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 "github.com/google/go-cmp/cmp" 25 apiequality "k8s.io/apimachinery/pkg/api/equality" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/fields" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apimachinery/pkg/runtime" 31 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 32 "k8s.io/apiserver/pkg/registry/generic" 33 genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" 34 "k8s.io/apiserver/pkg/registry/rest" 35 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" 36 utilfeature "k8s.io/apiserver/pkg/util/feature" 37 featuregatetesting "k8s.io/component-base/featuregate/testing" 38 api "k8s.io/kubernetes/pkg/apis/core" 39 "k8s.io/kubernetes/pkg/features" 40 "k8s.io/kubernetes/pkg/registry/core/persistentvolume" 41 "k8s.io/kubernetes/pkg/registry/registrytest" 42 ) 43 44 func newStorage(t *testing.T) (*REST, *StatusREST, *etcd3testing.EtcdTestServer) { 45 etcdStorage, server := registrytest.NewEtcdStorage(t, "") 46 restOptions := generic.RESTOptions{ 47 StorageConfig: etcdStorage, 48 Decorator: generic.UndecoratedStorage, 49 DeleteCollectionWorkers: 1, 50 ResourcePrefix: "persistentvolumes", 51 } 52 persistentVolumeStorage, statusStorage, err := NewREST(restOptions) 53 if err != nil { 54 t.Fatalf("unexpected error from REST storage: %v", err) 55 } 56 return persistentVolumeStorage, statusStorage, server 57 } 58 59 func newHostPathType(pathType string) *api.HostPathType { 60 hostPathType := new(api.HostPathType) 61 *hostPathType = api.HostPathType(pathType) 62 return hostPathType 63 } 64 65 func validNewPersistentVolume(name string) *api.PersistentVolume { 66 now := persistentvolume.NowFunc() 67 pv := &api.PersistentVolume{ 68 ObjectMeta: metav1.ObjectMeta{ 69 Name: name, 70 }, 71 Spec: api.PersistentVolumeSpec{ 72 Capacity: api.ResourceList{ 73 api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), 74 }, 75 AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, 76 PersistentVolumeSource: api.PersistentVolumeSource{ 77 HostPath: &api.HostPathVolumeSource{Path: "/foo", Type: newHostPathType(string(api.HostPathDirectoryOrCreate))}, 78 }, 79 PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRetain, 80 }, 81 Status: api.PersistentVolumeStatus{ 82 Phase: api.VolumePending, 83 Message: "bar", 84 Reason: "foo", 85 LastPhaseTransitionTime: &now, 86 }, 87 } 88 return pv 89 } 90 91 func TestCreate(t *testing.T) { 92 storage, _, server := newStorage(t) 93 defer server.Terminate(t) 94 defer storage.Store.DestroyFunc() 95 test := genericregistrytest.New(t, storage.Store).ClusterScope() 96 pv := validNewPersistentVolume("foo") 97 pv.ObjectMeta = metav1.ObjectMeta{GenerateName: "foo"} 98 test.TestCreate( 99 // valid 100 pv, 101 // invalid 102 &api.PersistentVolume{ 103 ObjectMeta: metav1.ObjectMeta{Name: "*BadName!"}, 104 }, 105 ) 106 } 107 108 func TestUpdate(t *testing.T) { 109 storage, _, server := newStorage(t) 110 defer server.Terminate(t) 111 defer storage.Store.DestroyFunc() 112 test := genericregistrytest.New(t, storage.Store).ClusterScope() 113 test.TestUpdate( 114 // valid 115 validNewPersistentVolume("foo"), 116 // updateFunc 117 func(obj runtime.Object) runtime.Object { 118 object := obj.(*api.PersistentVolume) 119 object.Spec.Capacity = api.ResourceList{ 120 api.ResourceName(api.ResourceStorage): resource.MustParse("20G"), 121 } 122 return object 123 }, 124 ) 125 } 126 127 func TestDelete(t *testing.T) { 128 storage, _, server := newStorage(t) 129 defer server.Terminate(t) 130 defer storage.Store.DestroyFunc() 131 test := genericregistrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() 132 test.TestDelete(validNewPersistentVolume("foo")) 133 } 134 135 func TestGet(t *testing.T) { 136 storage, _, server := newStorage(t) 137 defer server.Terminate(t) 138 defer storage.Store.DestroyFunc() 139 test := genericregistrytest.New(t, storage.Store).ClusterScope() 140 test.TestGet(validNewPersistentVolume("foo")) 141 } 142 143 func TestList(t *testing.T) { 144 storage, _, server := newStorage(t) 145 defer server.Terminate(t) 146 defer storage.Store.DestroyFunc() 147 test := genericregistrytest.New(t, storage.Store).ClusterScope() 148 test.TestList(validNewPersistentVolume("foo")) 149 } 150 151 func TestWatch(t *testing.T) { 152 storage, _, server := newStorage(t) 153 defer server.Terminate(t) 154 defer storage.Store.DestroyFunc() 155 test := genericregistrytest.New(t, storage.Store).ClusterScope() 156 test.TestWatch( 157 validNewPersistentVolume("foo"), 158 // matching labels 159 []labels.Set{}, 160 // not matching labels 161 []labels.Set{ 162 {"foo": "bar"}, 163 }, 164 // matching fields 165 []fields.Set{ 166 {"metadata.name": "foo"}, 167 {"name": "foo"}, 168 }, 169 // not matching fields 170 []fields.Set{ 171 {"metadata.name": "bar"}, 172 }, 173 ) 174 } 175 176 func TestUpdateStatus(t *testing.T) { 177 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PersistentVolumeLastPhaseTransitionTime, true)() 178 storage, statusStorage, server := newStorage(t) 179 defer server.Terminate(t) 180 defer storage.Store.DestroyFunc() 181 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceNone) 182 key, _ := storage.KeyFunc(ctx, "foo") 183 pvStart := validNewPersistentVolume("foo") 184 err := storage.Storage.Create(ctx, key, pvStart, nil, 0, false) 185 if err != nil { 186 t.Errorf("unexpected error: %v", err) 187 } 188 189 pvStartTimestamp, err := getPhaseTranstitionTime(ctx, pvStart.Name, storage) 190 if err != nil { 191 t.Errorf("unexpected error: %v", err) 192 } 193 194 // We need to set custom timestamp which is not the same as the one on existing PV 195 // - doing so will prevent timestamp update on phase change and custom one is used instead. 196 pvStartTimestamp = &metav1.Time{Time: pvStartTimestamp.Time.Add(time.Second)} 197 198 pvIn := &api.PersistentVolume{ 199 ObjectMeta: metav1.ObjectMeta{ 200 Name: "foo", 201 }, 202 Status: api.PersistentVolumeStatus{ 203 Phase: api.VolumeBound, 204 LastPhaseTransitionTime: pvStartTimestamp, 205 }, 206 } 207 208 _, _, err = statusStorage.Update(ctx, pvIn.Name, rest.DefaultUpdatedObjectInfo(pvIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 209 if err != nil { 210 t.Fatalf("Unexpected error: %v", err) 211 } 212 obj, err := storage.Get(ctx, "foo", &metav1.GetOptions{}) 213 if err != nil { 214 t.Errorf("unexpected error: %v", err) 215 } 216 pvOut := obj.(*api.PersistentVolume) 217 // only compare the relevant change b/c metadata will differ 218 if !apiequality.Semantic.DeepEqual(pvIn.Status, pvOut.Status) { 219 t.Errorf("unexpected object: %s", cmp.Diff(pvIn.Status, pvOut.Status)) 220 } 221 } 222 223 func getPhaseTranstitionTime(ctx context.Context, pvName string, storage *REST) (*metav1.Time, error) { 224 obj, err := storage.Get(ctx, pvName, &metav1.GetOptions{}) 225 if err != nil { 226 return nil, err 227 } 228 return obj.(*api.PersistentVolume).Status.LastPhaseTransitionTime, nil 229 } 230 231 func TestShortNames(t *testing.T) { 232 storage, _, server := newStorage(t) 233 defer server.Terminate(t) 234 defer storage.Store.DestroyFunc() 235 expected := []string{"pv"} 236 registrytest.AssertShortNames(t, storage, expected) 237 }