Add generated file

This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
xing-yang
2018-07-12 10:55:15 -07:00
parent 36b1de0341
commit e213d1890d
17729 changed files with 5090889 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"admission.go",
"doc.go",
],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/cloudprovider:go_default_library",
"//pkg/cloudprovider/providers/aws:go_default_library",
"//pkg/cloudprovider/providers/gce:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//pkg/kubelet/apis:go_default_library",
"//pkg/volume:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/cloudprovider/providers/aws:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,221 @@
/*
Copyright 2015 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 label
import (
"bytes"
"fmt"
"io"
"sync"
"github.com/golang/glog"
"k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
vol "k8s.io/kubernetes/pkg/volume"
)
const (
// PluginName is the name of persistent volume label admission plugin
PluginName = "PersistentVolumeLabel"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
persistentVolumeLabelAdmission := newPersistentVolumeLabel()
return persistentVolumeLabelAdmission, nil
})
}
var _ = admission.Interface(&persistentVolumeLabel{})
type persistentVolumeLabel struct {
*admission.Handler
mutex sync.Mutex
ebsVolumes aws.Volumes
cloudConfig []byte
gceCloudProvider *gce.GCECloud
}
var _ admission.MutationInterface = &persistentVolumeLabel{}
var _ kubeapiserveradmission.WantsCloudConfig = &persistentVolumeLabel{}
// newPersistentVolumeLabel returns an admission.Interface implementation which adds labels to PersistentVolume CREATE requests,
// based on the labels provided by the underlying cloud provider.
//
// As a side effect, the cloud provider may block invalid or non-existent volumes.
func newPersistentVolumeLabel() *persistentVolumeLabel {
// DEPRECATED: cloud-controller-manager will now start NewPersistentVolumeLabelController
// which does exactly what this admission controller used to do. So once GCE and AWS can
// run externally, we can remove this admission controller.
glog.Warning("PersistentVolumeLabel admission controller is deprecated. " +
"Please remove this controller from your configuration files and scripts.")
return &persistentVolumeLabel{
Handler: admission.NewHandler(admission.Create),
}
}
func (l *persistentVolumeLabel) SetCloudConfig(cloudConfig []byte) {
l.cloudConfig = cloudConfig
}
func (l *persistentVolumeLabel) Admit(a admission.Attributes) (err error) {
if a.GetResource().GroupResource() != api.Resource("persistentvolumes") {
return nil
}
obj := a.GetObject()
if obj == nil {
return nil
}
volume, ok := obj.(*api.PersistentVolume)
if !ok {
return nil
}
var volumeLabels map[string]string
if volume.Spec.AWSElasticBlockStore != nil {
labels, err := l.findAWSEBSLabels(volume)
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("error querying AWS EBS volume %s: %v", volume.Spec.AWSElasticBlockStore.VolumeID, err))
}
volumeLabels = labels
}
if volume.Spec.GCEPersistentDisk != nil {
labels, err := l.findGCEPDLabels(volume)
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("error querying GCE PD volume %s: %v", volume.Spec.GCEPersistentDisk.PDName, err))
}
volumeLabels = labels
}
if len(volumeLabels) != 0 {
if volume.Labels == nil {
volume.Labels = make(map[string]string)
}
for k, v := range volumeLabels {
// We (silently) replace labels if they are provided.
// This should be OK because they are in the kubernetes.io namespace
// i.e. we own them
volume.Labels[k] = v
}
}
return nil
}
func (l *persistentVolumeLabel) findAWSEBSLabels(volume *api.PersistentVolume) (map[string]string, error) {
// Ignore any volumes that are being provisioned
if volume.Spec.AWSElasticBlockStore.VolumeID == vol.ProvisionedVolumeName {
return nil, nil
}
ebsVolumes, err := l.getEBSVolumes()
if err != nil {
return nil, err
}
if ebsVolumes == nil {
return nil, fmt.Errorf("unable to build AWS cloud provider for EBS")
}
// TODO: GetVolumeLabels is actually a method on the Volumes interface
// If that gets standardized we can refactor to reduce code duplication
spec := aws.KubernetesVolumeID(volume.Spec.AWSElasticBlockStore.VolumeID)
labels, err := ebsVolumes.GetVolumeLabels(spec)
if err != nil {
return nil, err
}
return labels, nil
}
// getEBSVolumes returns the AWS Volumes interface for ebs
func (l *persistentVolumeLabel) getEBSVolumes() (aws.Volumes, error) {
l.mutex.Lock()
defer l.mutex.Unlock()
if l.ebsVolumes == nil {
var cloudConfigReader io.Reader
if len(l.cloudConfig) > 0 {
cloudConfigReader = bytes.NewReader(l.cloudConfig)
}
cloudProvider, err := cloudprovider.GetCloudProvider("aws", cloudConfigReader)
if err != nil || cloudProvider == nil {
return nil, err
}
awsCloudProvider, ok := cloudProvider.(*aws.Cloud)
if !ok {
// GetCloudProvider has gone very wrong
return nil, fmt.Errorf("error retrieving AWS cloud provider")
}
l.ebsVolumes = awsCloudProvider
}
return l.ebsVolumes, nil
}
func (l *persistentVolumeLabel) findGCEPDLabels(volume *api.PersistentVolume) (map[string]string, error) {
// Ignore any volumes that are being provisioned
if volume.Spec.GCEPersistentDisk.PDName == vol.ProvisionedVolumeName {
return nil, nil
}
provider, err := l.getGCECloudProvider()
if err != nil {
return nil, err
}
if provider == nil {
return nil, fmt.Errorf("unable to build GCE cloud provider for PD")
}
// If the zone is already labeled, honor the hint
zone := volume.Labels[kubeletapis.LabelZoneFailureDomain]
labels, err := provider.GetAutoLabelsForPD(volume.Spec.GCEPersistentDisk.PDName, zone)
if err != nil {
return nil, err
}
return labels, nil
}
// getGCECloudProvider returns the GCE cloud provider, for use for querying volume labels
func (l *persistentVolumeLabel) getGCECloudProvider() (*gce.GCECloud, error) {
l.mutex.Lock()
defer l.mutex.Unlock()
if l.gceCloudProvider == nil {
var cloudConfigReader io.Reader
if len(l.cloudConfig) > 0 {
cloudConfigReader = bytes.NewReader(l.cloudConfig)
}
cloudProvider, err := cloudprovider.GetCloudProvider("gce", cloudConfigReader)
if err != nil || cloudProvider == nil {
return nil, err
}
gceCloudProvider, ok := cloudProvider.(*gce.GCECloud)
if !ok {
// GetCloudProvider has gone very wrong
return nil, fmt.Errorf("error retrieving GCE cloud provider")
}
l.gceCloudProvider = gceCloudProvider
}
return l.gceCloudProvider, nil
}

View File

@@ -0,0 +1,176 @@
/*
Copyright 2015 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 label
import (
"testing"
"fmt"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
)
type mockVolumes struct {
volumeLabels map[string]string
volumeLabelsError error
}
var _ aws.Volumes = &mockVolumes{}
func (v *mockVolumes) AttachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, error) {
return "", fmt.Errorf("not implemented")
}
func (v *mockVolumes) DetachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, error) {
return "", fmt.Errorf("not implemented")
}
func (v *mockVolumes) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName aws.KubernetesVolumeID, err error) {
return "", fmt.Errorf("not implemented")
}
func (v *mockVolumes) DeleteDisk(volumeName aws.KubernetesVolumeID) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (v *mockVolumes) GetVolumeLabels(volumeName aws.KubernetesVolumeID) (map[string]string, error) {
return v.volumeLabels, v.volumeLabelsError
}
func (v *mockVolumes) GetDiskPath(volumeName aws.KubernetesVolumeID) (string, error) {
return "", fmt.Errorf("not implemented")
}
func (v *mockVolumes) DiskIsAttached(volumeName aws.KubernetesVolumeID, nodeName types.NodeName) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (v *mockVolumes) DisksAreAttached(nodeDisks map[types.NodeName][]aws.KubernetesVolumeID) (map[types.NodeName]map[aws.KubernetesVolumeID]bool, error) {
return nil, fmt.Errorf("not implemented")
}
func (v *mockVolumes) ResizeDisk(
diskName aws.KubernetesVolumeID,
oldSize resource.Quantity,
newSize resource.Quantity) (resource.Quantity, error) {
return oldSize, nil
}
func mockVolumeFailure(err error) *mockVolumes {
return &mockVolumes{volumeLabelsError: err}
}
func mockVolumeLabels(labels map[string]string) *mockVolumes {
return &mockVolumes{volumeLabels: labels}
}
// TestAdmission
func TestAdmission(t *testing.T) {
pvHandler := newPersistentVolumeLabel()
handler := admission.NewChainHandler(pvHandler)
ignoredPV := api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/",
},
},
},
}
awsPV := api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
VolumeID: "123",
},
},
},
}
// Non-cloud PVs are ignored
err := handler.Admit(admission.NewAttributesRecord(&ignoredPV, nil, api.Kind("PersistentVolume").WithVersion("version"), ignoredPV.Namespace, ignoredPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler (on ignored pv): %v", err)
}
// We only add labels on creation
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Delete, nil))
if err != nil {
t.Errorf("Unexpected error returned from admission handler (when deleting aws pv): %v", err)
}
// Errors from the cloudprovider block creation of the volume
pvHandler.ebsVolumes = mockVolumeFailure(fmt.Errorf("invalid volume"))
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("Expected error when aws pv info fails")
}
// Don't add labels if the cloudprovider doesn't return any
labels := make(map[string]string)
pvHandler.ebsVolumes = mockVolumeLabels(labels)
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Expected no error when creating aws pv")
}
if len(awsPV.ObjectMeta.Labels) != 0 {
t.Errorf("Unexpected number of labels")
}
// Don't panic if the cloudprovider returns nil, nil
pvHandler.ebsVolumes = mockVolumeFailure(nil)
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Expected no error when cloud provider returns empty labels")
}
// Labels from the cloudprovider should be applied to the volume
labels = make(map[string]string)
labels["a"] = "1"
labels["b"] = "2"
pvHandler.ebsVolumes = mockVolumeLabels(labels)
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Expected no error when creating aws pv")
}
if awsPV.Labels["a"] != "1" || awsPV.Labels["b"] != "2" {
t.Errorf("Expected label a to be added when creating aws pv")
}
// User-provided labels should be honored, but cloudprovider labels replace them when they overlap
awsPV.ObjectMeta.Labels = make(map[string]string)
awsPV.ObjectMeta.Labels["a"] = "not1"
awsPV.ObjectMeta.Labels["c"] = "3"
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
if err != nil {
t.Errorf("Expected no error when creating aws pv")
}
if awsPV.Labels["a"] != "1" || awsPV.Labels["b"] != "2" {
t.Errorf("Expected cloudprovider labels to replace user labels when creating aws pv")
}
if awsPV.Labels["c"] != "3" {
t.Errorf("Expected (non-conflicting) user provided labels to be honored when creating aws pv")
}
}

View File

@@ -0,0 +1,19 @@
/*
Copyright 2014 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 label created persistent volumes with zone information
// as provided by the cloud provider
package label // import "k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/label"

View File

@@ -0,0 +1,52 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["admission.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/storage/persistentvolume/resize",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/client/listers/storage/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,172 @@
/*
Copyright 2017 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 resize
import (
"fmt"
"io"
"k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core"
apihelper "k8s.io/kubernetes/pkg/apis/core/helper"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
pvlister "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
storagelisters "k8s.io/kubernetes/pkg/client/listers/storage/internalversion"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
)
const (
// PluginName is the name of pvc resize admission plugin
PluginName = "PersistentVolumeClaimResize"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
plugin := newPlugin()
return plugin, nil
})
}
var _ admission.Interface = &persistentVolumeClaimResize{}
var _ admission.ValidationInterface = &persistentVolumeClaimResize{}
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&persistentVolumeClaimResize{})
type persistentVolumeClaimResize struct {
*admission.Handler
pvLister pvlister.PersistentVolumeLister
scLister storagelisters.StorageClassLister
}
func newPlugin() *persistentVolumeClaimResize {
return &persistentVolumeClaimResize{
Handler: admission.NewHandler(admission.Update),
}
}
func (pvcr *persistentVolumeClaimResize) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
pvcInformer := f.Core().InternalVersion().PersistentVolumes()
pvcr.pvLister = pvcInformer.Lister()
scInformer := f.Storage().InternalVersion().StorageClasses()
pvcr.scLister = scInformer.Lister()
pvcr.SetReadyFunc(func() bool {
return pvcInformer.Informer().HasSynced() && scInformer.Informer().HasSynced()
})
}
// ValidateInitialization ensures lister is set.
func (pvcr *persistentVolumeClaimResize) ValidateInitialization() error {
if pvcr.pvLister == nil {
return fmt.Errorf("missing persistent volume lister")
}
if pvcr.scLister == nil {
return fmt.Errorf("missing storageclass lister")
}
return nil
}
func (pvcr *persistentVolumeClaimResize) Validate(a admission.Attributes) error {
if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") {
return nil
}
if len(a.GetSubresource()) != 0 {
return nil
}
pvc, ok := a.GetObject().(*api.PersistentVolumeClaim)
// if we can't convert then we don't handle this object so just return
if !ok {
return nil
}
oldPvc, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
if !ok {
return nil
}
oldSize := oldPvc.Spec.Resources.Requests[api.ResourceStorage]
newSize := pvc.Spec.Resources.Requests[api.ResourceStorage]
if newSize.Cmp(oldSize) <= 0 {
return nil
}
if oldPvc.Status.Phase != api.ClaimBound {
return admission.NewForbidden(a, fmt.Errorf("Only bound persistent volume claims can be expanded"))
}
// Growing Persistent volumes is only allowed for PVCs for which their StorageClass
// explicitly allows it
if !pvcr.allowResize(pvc, oldPvc) {
return admission.NewForbidden(a, fmt.Errorf("only dynamically provisioned pvc can be resized and "+
"the storageclass that provisions the pvc must support resize"))
}
// volume plugin must support resize
pv, err := pvcr.pvLister.Get(pvc.Spec.VolumeName)
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("Error updating persistent volume claim because fetching associated persistent volume failed"))
}
if !pvcr.checkVolumePlugin(pv) {
return admission.NewForbidden(a, fmt.Errorf("volume plugin does not support resize"))
}
return nil
}
// Growing Persistent volumes is only allowed for PVCs for which their StorageClass
// explicitly allows it.
func (pvcr *persistentVolumeClaimResize) allowResize(pvc, oldPvc *api.PersistentVolumeClaim) bool {
pvcStorageClass := apihelper.GetPersistentVolumeClaimClass(pvc)
oldPvcStorageClass := apihelper.GetPersistentVolumeClaimClass(oldPvc)
if pvcStorageClass == "" || oldPvcStorageClass == "" || pvcStorageClass != oldPvcStorageClass {
return false
}
sc, err := pvcr.scLister.Get(pvcStorageClass)
if err != nil {
return false
}
if sc.AllowVolumeExpansion != nil {
return *sc.AllowVolumeExpansion
}
return false
}
// checkVolumePlugin checks whether the volume plugin supports resize
func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVolume) bool {
if pv.Spec.Glusterfs != nil || pv.Spec.Cinder != nil || pv.Spec.RBD != nil || pv.Spec.PortworxVolume != nil {
return true
}
if pv.Spec.GCEPersistentDisk != nil {
return true
}
if pv.Spec.AWSElasticBlockStore != nil {
return true
}
if pv.Spec.AzureFile != nil {
return true
}
if pv.Spec.AzureDisk != nil {
return true
}
return false
}

View File

@@ -0,0 +1,334 @@
/*
Copyright 2017 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 resize
import (
"fmt"
"strings"
"testing"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/storage"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/controller"
)
func getResourceList(storage string) api.ResourceList {
res := api.ResourceList{}
if storage != "" {
res[api.ResourceStorage] = resource.MustParse(storage)
}
return res
}
func TestPVCResizeAdmission(t *testing.T) {
goldClassName := "gold"
trueVal := true
falseVar := false
goldClass := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: goldClassName,
},
Provisioner: "kubernetes.io/glusterfs",
AllowVolumeExpansion: &trueVal,
}
silverClassName := "silver"
silverClass := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: silverClassName,
},
Provisioner: "kubernetes.io/glusterfs",
AllowVolumeExpansion: &falseVar,
}
expectNoError := func(err error) bool {
return err == nil
}
expectDynamicallyProvisionedError := func(err error) bool {
return strings.Contains(err.Error(), "only dynamically provisioned pvc can be resized and "+
"the storageclass that provisions the pvc must support resize")
}
expectVolumePluginError := func(err error) bool {
return strings.Contains(err.Error(), "volume plugin does not support resize")
}
tests := []struct {
name string
resource schema.GroupVersionResource
subresource string
oldObj runtime.Object
newObj runtime.Object
checkError func(error) bool
}{
{
name: "pvc-resize, update, no error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume1",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
Phase: api.ClaimBound,
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume1",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
Phase: api.ClaimBound,
},
},
checkError: expectNoError,
},
{
name: "pvc-resize, update, volume plugin error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume2",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
Phase: api.ClaimBound,
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume2",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
StorageClassName: &goldClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
Phase: api.ClaimBound,
},
},
checkError: expectVolumePluginError,
},
{
name: "pvc-resize, update, dynamically provisioned error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume3",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
Phase: api.ClaimBound,
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume3",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
Phase: api.ClaimBound,
},
},
checkError: expectDynamicallyProvisionedError,
},
{
name: "pvc-resize, update, dynamically provisioned error",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume4",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
Phase: api.ClaimBound,
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume4",
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("2Gi"),
Phase: api.ClaimBound,
},
},
checkError: expectDynamicallyProvisionedError,
},
{
name: "PVC update with no change in size",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("0Gi"),
Phase: api.ClaimPending,
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
VolumeName: "volume4",
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("1Gi"),
Phase: api.ClaimBound,
},
},
checkError: expectNoError,
},
{
name: "expand pvc in pending state",
resource: api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
oldObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
Resources: api.ResourceRequirements{
Requests: getResourceList("1Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("0Gi"),
Phase: api.ClaimPending,
},
},
newObj: &api.PersistentVolumeClaim{
Spec: api.PersistentVolumeClaimSpec{
Resources: api.ResourceRequirements{
Requests: getResourceList("2Gi"),
},
StorageClassName: &silverClassName,
},
Status: api.PersistentVolumeClaimStatus{
Capacity: getResourceList("0Gi"),
Phase: api.ClaimPending,
},
},
checkError: func(err error) bool {
return strings.Contains(err.Error(), "Only bound persistent volume claims can be expanded")
},
},
}
ctrl := newPlugin()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
ctrl.SetInternalKubeInformerFactory(informerFactory)
err := ctrl.ValidateInitialization()
if err != nil {
t.Fatalf("neither pv lister nor storageclass lister can be nil")
}
pv1 := &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "volume1"},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
Glusterfs: &api.GlusterfsVolumeSource{
EndpointsName: "http://localhost:8080/",
Path: "/heketi",
ReadOnly: false,
},
},
StorageClassName: goldClassName,
},
}
pv2 := &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: "volume2"},
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{},
},
StorageClassName: goldClassName,
},
}
pvs := []*api.PersistentVolume{}
pvs = append(pvs, pv1, pv2)
for _, pv := range pvs {
err := informerFactory.Core().InternalVersion().PersistentVolumes().Informer().GetStore().Add(pv)
if err != nil {
fmt.Println("add pv error: ", err)
}
}
scs := []*storage.StorageClass{}
scs = append(scs, goldClass, silverClass)
for _, sc := range scs {
err := informerFactory.Storage().InternalVersion().StorageClasses().Informer().GetStore().Add(sc)
if err != nil {
fmt.Println("add storageclass error: ", err)
}
}
for _, tc := range tests {
operation := admission.Update
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, nil)
err := ctrl.Validate(attributes)
fmt.Println(tc.name)
fmt.Println(err)
if !tc.checkError(err) {
t.Errorf("%v: unexpected err: %v", tc.name, err)
}
}
}

View File

@@ -0,0 +1,55 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["admission.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/storage/storageclass/setdefault",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/apis/storage/util:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/storage/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/storage:go_default_library",
"//pkg/apis/storage/util:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,147 @@
/*
Copyright 2016 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 setdefault
import (
"fmt"
"io"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
admission "k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/storage"
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
storagelisters "k8s.io/kubernetes/pkg/client/listers/storage/internalversion"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
)
const (
// PluginName is the name of this admission controller plugin
PluginName = "DefaultStorageClass"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
plugin := newPlugin()
return plugin, nil
})
}
// claimDefaulterPlugin holds state for and implements the admission plugin.
type claimDefaulterPlugin struct {
*admission.Handler
lister storagelisters.StorageClassLister
}
var _ admission.Interface = &claimDefaulterPlugin{}
var _ admission.MutationInterface = &claimDefaulterPlugin{}
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&claimDefaulterPlugin{})
// newPlugin creates a new admission plugin.
func newPlugin() *claimDefaulterPlugin {
return &claimDefaulterPlugin{
Handler: admission.NewHandler(admission.Create),
}
}
func (a *claimDefaulterPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
informer := f.Storage().InternalVersion().StorageClasses()
a.lister = informer.Lister()
a.SetReadyFunc(informer.Informer().HasSynced)
}
// ValidateInitialization ensures lister is set.
func (a *claimDefaulterPlugin) ValidateInitialization() error {
if a.lister == nil {
return fmt.Errorf("missing lister")
}
return nil
}
// Admit sets the default value of a PersistentVolumeClaim's storage class, in case the user did
// not provide a value.
//
// 1. Find available StorageClasses.
// 2. Figure which is the default
// 3. Write to the PVClaim
func (a *claimDefaulterPlugin) Admit(attr admission.Attributes) error {
if attr.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") {
return nil
}
if len(attr.GetSubresource()) != 0 {
return nil
}
pvc, ok := attr.GetObject().(*api.PersistentVolumeClaim)
// if we can't convert then we don't handle this object so just return
if !ok {
return nil
}
if helper.PersistentVolumeClaimHasClass(pvc) {
// The user asked for a class.
return nil
}
glog.V(4).Infof("no storage class for claim %s (generate: %s)", pvc.Name, pvc.GenerateName)
def, err := getDefaultClass(a.lister)
if err != nil {
return admission.NewForbidden(attr, err)
}
if def == nil {
// No default class selected, do nothing about the PVC.
return nil
}
glog.V(4).Infof("defaulting storage class for claim %s (generate: %s) to %s", pvc.Name, pvc.GenerateName, def.Name)
pvc.Spec.StorageClassName = &def.Name
return nil
}
// getDefaultClass returns the default StorageClass from the store, or nil.
func getDefaultClass(lister storagelisters.StorageClassLister) (*storage.StorageClass, error) {
list, err := lister.List(labels.Everything())
if err != nil {
return nil, err
}
defaultClasses := []*storage.StorageClass{}
for _, class := range list {
if storageutil.IsDefaultAnnotation(class.ObjectMeta) {
defaultClasses = append(defaultClasses, class)
glog.V(4).Infof("getDefaultClass added: %s", class.Name)
}
}
if len(defaultClasses) == 0 {
return nil, nil
}
if len(defaultClasses) > 1 {
glog.V(4).Infof("getDefaultClass %s defaults found", len(defaultClasses))
return nil, errors.NewInternalError(fmt.Errorf("%d default StorageClasses were found", len(defaultClasses)))
}
return defaultClasses[0], nil
}

View File

@@ -0,0 +1,233 @@
/*
Copyright 2016 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 setdefault
import (
"testing"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/storage"
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/controller"
)
func TestAdmission(t *testing.T) {
empty := ""
foo := "foo"
defaultClass1 := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "default1",
Annotations: map[string]string{
storageutil.IsDefaultStorageClassAnnotation: "true",
},
},
Provisioner: "default1",
}
defaultClass2 := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "default2",
Annotations: map[string]string{
storageutil.IsDefaultStorageClassAnnotation: "true",
},
},
Provisioner: "default2",
}
// Class that has explicit default = false
classWithFalseDefault := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nondefault1",
Annotations: map[string]string{
storageutil.IsDefaultStorageClassAnnotation: "false",
},
},
Provisioner: "nondefault1",
}
// Class with missing default annotation (=non-default)
classWithNoDefault := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nondefault2",
},
Provisioner: "nondefault1",
}
// Class with empty default annotation (=non-default)
classWithEmptyDefault := &storage.StorageClass{
TypeMeta: metav1.TypeMeta{
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nondefault2",
Annotations: map[string]string{
storageutil.IsDefaultStorageClassAnnotation: "",
},
},
Provisioner: "nondefault1",
}
claimWithClass := &api.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
},
ObjectMeta: metav1.ObjectMeta{
Name: "claimWithClass",
Namespace: "ns",
},
Spec: api.PersistentVolumeClaimSpec{
StorageClassName: &foo,
},
}
claimWithEmptyClass := &api.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
},
ObjectMeta: metav1.ObjectMeta{
Name: "claimWithEmptyClass",
Namespace: "ns",
},
Spec: api.PersistentVolumeClaimSpec{
StorageClassName: &empty,
},
}
claimWithNoClass := &api.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
},
ObjectMeta: metav1.ObjectMeta{
Name: "claimWithNoClass",
Namespace: "ns",
},
}
tests := []struct {
name string
classes []*storage.StorageClass
claim *api.PersistentVolumeClaim
expectError bool
expectedClassName string
}{
{
"no default, no modification of PVCs",
[]*storage.StorageClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
claimWithNoClass,
false,
"",
},
{
"one default, modify PVC with class=nil",
[]*storage.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
claimWithNoClass,
false,
"default1",
},
{
"one default, no modification of PVC with class=''",
[]*storage.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
claimWithEmptyClass,
false,
"",
},
{
"one default, no modification of PVC with class='foo'",
[]*storage.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
claimWithClass,
false,
"foo",
},
{
"two defaults, error with PVC with class=nil",
[]*storage.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
claimWithNoClass,
true,
"",
},
{
"two defaults, no modification of PVC with class=''",
[]*storage.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
claimWithEmptyClass,
false,
"",
},
{
"two defaults, no modification of PVC with class='foo'",
[]*storage.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
claimWithClass,
false,
"foo",
},
}
for _, test := range tests {
glog.V(4).Infof("starting test %q", test.name)
// clone the claim, it's going to be modified
claim := test.claim.DeepCopy()
ctrl := newPlugin()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
ctrl.SetInternalKubeInformerFactory(informerFactory)
for _, c := range test.classes {
informerFactory.Storage().InternalVersion().StorageClasses().Informer().GetStore().Add(c)
}
attrs := admission.NewAttributesRecord(
claim, // new object
nil, // old object
api.Kind("PersistentVolumeClaim").WithVersion("version"),
claim.Namespace,
claim.Name,
api.Resource("persistentvolumeclaims").WithVersion("version"),
"", // subresource
admission.Create,
nil, // userInfo
)
err := ctrl.Admit(attrs)
glog.Infof("Got %v", err)
if err != nil && !test.expectError {
t.Errorf("Test %q: unexpected error received: %v", test.name, err)
}
if err == nil && test.expectError {
t.Errorf("Test %q: expected error and no error recevied", test.name)
}
class := ""
if claim.Spec.StorageClassName != nil {
class = *claim.Spec.StorageClassName
}
if test.expectedClassName != "" && test.expectedClassName != class {
t.Errorf("Test %q: expected class name %q, got %q", test.name, test.expectedClassName, class)
}
if test.expectedClassName == "" && class != "" {
t.Errorf("Test %q: expected class name %q, got %q", test.name, test.expectedClassName, class)
}
}
}

View File

@@ -0,0 +1,51 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["admission.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/storage/storageobjectinuseprotection",
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//pkg/volume/util:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/volume/util:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,156 @@
/*
Copyright 2017 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 storageobjectinuseprotection
import (
"fmt"
"io"
"github.com/golang/glog"
admission "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
"k8s.io/kubernetes/pkg/features"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
)
const (
// PluginName is the name of this admission controller plugin
PluginName = "StorageObjectInUseProtection"
)
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
plugin := newPlugin()
return plugin, nil
})
}
// storageProtectionPlugin holds state for and implements the admission plugin.
type storageProtectionPlugin struct {
*admission.Handler
pvcLister corelisters.PersistentVolumeClaimLister
pvLister corelisters.PersistentVolumeLister
}
var _ admission.Interface = &storageProtectionPlugin{}
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&storageProtectionPlugin{})
// newPlugin creates a new admission plugin.
func newPlugin() *storageProtectionPlugin {
return &storageProtectionPlugin{
Handler: admission.NewHandler(admission.Create),
}
}
func (c *storageProtectionPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
pvcInformer := f.Core().InternalVersion().PersistentVolumeClaims()
c.pvcLister = pvcInformer.Lister()
pvInformer := f.Core().InternalVersion().PersistentVolumes()
c.pvLister = pvInformer.Lister()
c.SetReadyFunc(func() bool {
return pvcInformer.Informer().HasSynced() && pvInformer.Informer().HasSynced()
})
}
// ValidateInitialization ensures lister is set.
func (c *storageProtectionPlugin) ValidateInitialization() error {
if c.pvcLister == nil {
return fmt.Errorf("missing PVC lister")
}
if c.pvLister == nil {
return fmt.Errorf("missing PV lister")
}
return nil
}
var (
pvResource = api.Resource("persistentvolumes")
pvcResource = api.Resource("persistentvolumeclaims")
)
// Admit sets finalizer on all PVCs(PVs). The finalizer is removed by
// PVCProtectionController(PVProtectionController) when it's not referenced.
//
// This prevents users from deleting a PVC that's used by a running pod.
// This also prevents admin from deleting a PV that's bound by a PVC
func (c *storageProtectionPlugin) Admit(a admission.Attributes) error {
if !feature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection) {
return nil
}
switch a.GetResource().GroupResource() {
case pvResource:
return c.admitPV(a)
case pvcResource:
return c.admitPVC(a)
default:
return nil
}
}
func (c *storageProtectionPlugin) admitPV(a admission.Attributes) error {
if len(a.GetSubresource()) != 0 {
return nil
}
pv, ok := a.GetObject().(*api.PersistentVolume)
// if we can't convert the obj to PV, just return
if !ok {
return nil
}
for _, f := range pv.Finalizers {
if f == volumeutil.PVProtectionFinalizer {
// Finalizer is already present, nothing to do
return nil
}
}
glog.V(4).Infof("adding PV protection finalizer to %s", pv.Name)
pv.Finalizers = append(pv.Finalizers, volumeutil.PVProtectionFinalizer)
return nil
}
func (c *storageProtectionPlugin) admitPVC(a admission.Attributes) error {
if len(a.GetSubresource()) != 0 {
return nil
}
pvc, ok := a.GetObject().(*api.PersistentVolumeClaim)
// if we can't convert the obj to PVC, just return
if !ok {
return nil
}
for _, f := range pvc.Finalizers {
if f == volumeutil.PVCProtectionFinalizer {
// Finalizer is already present, nothing to do
return nil
}
}
glog.V(4).Infof("adding PVC protection finalizer to %s/%s", pvc.Namespace, pvc.Name)
pvc.Finalizers = append(pvc.Finalizers, volumeutil.PVCProtectionFinalizer)
return nil
}

View File

@@ -0,0 +1,151 @@
/*
Copyright 2017 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 storageobjectinuseprotection
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
"k8s.io/kubernetes/pkg/controller"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
)
func TestAdmit(t *testing.T) {
claim := &api.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
},
ObjectMeta: metav1.ObjectMeta{
Name: "claim",
Namespace: "ns",
},
}
pv := &api.PersistentVolume{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolume",
},
ObjectMeta: metav1.ObjectMeta{
Name: "pv",
},
}
claimWithFinalizer := claim.DeepCopy()
claimWithFinalizer.Finalizers = []string{volumeutil.PVCProtectionFinalizer}
pvWithFinalizer := pv.DeepCopy()
pvWithFinalizer.Finalizers = []string{volumeutil.PVProtectionFinalizer}
tests := []struct {
name string
resource schema.GroupVersionResource
object runtime.Object
expectedObject runtime.Object
featureEnabled bool
namespace string
}{
{
"create -> add finalizer",
api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
claim,
claimWithFinalizer,
true,
claim.Namespace,
},
{
"finalizer already exists -> no new finalizer",
api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
claimWithFinalizer,
claimWithFinalizer,
true,
claimWithFinalizer.Namespace,
},
{
"disabled feature -> no finalizer",
api.SchemeGroupVersion.WithResource("persistentvolumeclaims"),
claim,
claim,
false,
claim.Namespace,
},
{
"create -> add finalizer",
api.SchemeGroupVersion.WithResource("persistentvolumes"),
pv,
pvWithFinalizer,
true,
pv.Namespace,
},
{
"finalizer already exists -> no new finalizer",
api.SchemeGroupVersion.WithResource("persistentvolumes"),
pvWithFinalizer,
pvWithFinalizer,
true,
pvWithFinalizer.Namespace,
},
{
"disabled feature -> no finalizer",
api.SchemeGroupVersion.WithResource("persistentvolumes"),
pv,
pv,
false,
pv.Namespace,
},
}
ctrl := newPlugin()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
ctrl.SetInternalKubeInformerFactory(informerFactory)
for _, test := range tests {
feature.DefaultFeatureGate.Set(fmt.Sprintf("StorageObjectInUseProtection=%v", test.featureEnabled))
obj := test.object.DeepCopyObject()
attrs := admission.NewAttributesRecord(
obj, // new object
obj.DeepCopyObject(), // old object, copy to be sure it's not modified
schema.GroupVersionKind{},
test.namespace,
"foo",
test.resource,
"", // subresource
admission.Create,
nil, // userInfo
)
err := ctrl.Admit(attrs)
if err != nil {
t.Errorf("Test %q: got unexpected error: %v", test.name, err)
}
if !reflect.DeepEqual(test.expectedObject, obj) {
t.Errorf("Test %q: Expected object:\n%s\ngot:\n%s", test.name, spew.Sdump(test.expectedObject), spew.Sdump(obj))
}
}
// Disable the feature for rest of the tests.
// TODO: remove after alpha
feature.DefaultFeatureGate.Set("StorageObjectInUseProtection=false")
}