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  }