Commit c7884550 authored by Michelle Au's avatar Michelle Au

Add VolumeNodeAffinity to PersistentVolumeSpec

parent 20a6749c
...@@ -467,6 +467,16 @@ type PersistentVolumeSpec struct { ...@@ -467,6 +467,16 @@ type PersistentVolumeSpec struct {
// This is an alpha feature and may change in the future. // This is an alpha feature and may change in the future.
// +optional // +optional
VolumeMode *PersistentVolumeMode VolumeMode *PersistentVolumeMode
// NodeAffinity defines constraints that limit what nodes this volume can be accessed from.
// This field influences the scheduling of pods that use this volume.
// +optional
NodeAffinity *VolumeNodeAffinity
}
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
type VolumeNodeAffinity struct {
// Required specifies hard node constraints that must be met.
Required *NodeSelector
} }
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes // PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes
......
...@@ -1383,6 +1383,9 @@ func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path) ...@@ -1383,6 +1383,9 @@ func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path)
return allErrs return allErrs
} }
if !path.IsAbs(ls.Path) {
allErrs = append(allErrs, field.Invalid(fldPath, ls.Path, "must be an absolute path"))
}
allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...) allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...)
return allErrs return allErrs
} }
...@@ -1497,6 +1500,10 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList { ...@@ -1497,6 +1500,10 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations")) nodeAffinitySpecified, errs := validateStorageNodeAffinityAnnotation(pv.ObjectMeta.Annotations, metaPath.Child("annotations"))
allErrs = append(allErrs, errs...) allErrs = append(allErrs, errs...)
volumeNodeAffinitySpecified, errs := validateVolumeNodeAffinity(pv.Spec.NodeAffinity, specPath.Child("nodeAffinity"))
nodeAffinitySpecified = nodeAffinitySpecified || volumeNodeAffinitySpecified
allErrs = append(allErrs, errs...)
numVolumes := 0 numVolumes := 0
if pv.Spec.HostPath != nil { if pv.Spec.HostPath != nil {
if numVolumes > 0 { if numVolumes > 0 {
...@@ -1725,6 +1732,10 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E ...@@ -1725,6 +1732,10 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...) allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
} }
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.NodeAffinity, oldPv.Spec.NodeAffinity, field.NewPath("nodeAffinity"))...)
}
return allErrs return allErrs
} }
...@@ -4936,7 +4947,7 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat ...@@ -4936,7 +4947,7 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat
return false, allErrs return false, allErrs
} }
if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) { if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate")) allErrs = append(allErrs, field.Forbidden(fldPath, "Storage node affinity is disabled by feature-gate"))
} }
...@@ -4952,6 +4963,27 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat ...@@ -4952,6 +4963,27 @@ func validateStorageNodeAffinityAnnotation(annotations map[string]string, fldPat
return policySpecified, allErrs return policySpecified, allErrs
} }
// validateVolumeNodeAffinity tests that the PersistentVolume.NodeAffinity has valid data
func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) (bool, field.ErrorList) {
allErrs := field.ErrorList{}
if nodeAffinity == nil {
return false, allErrs
}
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
allErrs = append(allErrs, field.Forbidden(fldPath, "Volume node affinity is disabled by feature-gate"))
}
if nodeAffinity.Required != nil {
allErrs = append(allErrs, ValidateNodeSelector(nodeAffinity.Required, fldPath.Child("required"))...)
} else {
allErrs = append(allErrs, field.Required(fldPath.Child("required"), "must specify required node constraints"))
}
return true, allErrs
}
// ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR // ValidateCIDR validates whether a CIDR matches the conventions expected by net.ParseCIDR
func ValidateCIDR(cidr string) (*net.IPNet, error) { func ValidateCIDR(cidr string) (*net.IPNet, error) {
_, net, err := net.ParseCIDR(cidr) _, net, err := net.ParseCIDR(cidr)
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/apis/storage"
storageapi "k8s.io/kubernetes/pkg/apis/storage"
) )
// Funcs returns the fuzzer functions for the storage api group. // Funcs returns the fuzzer functions for the storage api group.
...@@ -31,6 +32,8 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { ...@@ -31,6 +32,8 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
c.FuzzNoCustom(obj) // fuzz self without calling this function again c.FuzzNoCustom(obj) // fuzz self without calling this function again
reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimDelete, api.PersistentVolumeReclaimRetain} reclamationPolicies := []api.PersistentVolumeReclaimPolicy{api.PersistentVolumeReclaimDelete, api.PersistentVolumeReclaimRetain}
obj.ReclaimPolicy = &reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))] obj.ReclaimPolicy = &reclamationPolicies[c.Rand.Intn(len(reclamationPolicies))]
bindingModes := []storageapi.VolumeBindingMode{storageapi.VolumeBindingImmediate, storageapi.VolumeBindingWaitForFirstConsumer}
obj.VolumeBindingMode = &bindingModes[c.Rand.Intn(len(bindingModes))]
}, },
} }
} }
...@@ -27,6 +27,9 @@ func TestDropAlphaFields(t *testing.T) { ...@@ -27,6 +27,9 @@ func TestDropAlphaFields(t *testing.T) {
bindingMode := storage.VolumeBindingWaitForFirstConsumer bindingMode := storage.VolumeBindingWaitForFirstConsumer
// Test that field gets dropped when feature gate is not set // Test that field gets dropped when feature gate is not set
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false"); err != nil {
t.Fatalf("Failed to set feature gate for VolumeScheduling: %v", err)
}
class := &storage.StorageClass{ class := &storage.StorageClass{
VolumeBindingMode: &bindingMode, VolumeBindingMode: &bindingMode,
} }
......
...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class := &storagev1.StorageClass{} class := &storagev1.StorageClass{}
// When feature gate is disabled, field should not be defaulted // When feature gate is disabled, field should not be defaulted
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
}
output := roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass) output := roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass)
if output.VolumeBindingMode != nil { if output.VolumeBindingMode != nil {
t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode) t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode)
...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class = &storagev1.StorageClass{} class = &storagev1.StorageClass{}
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") // When feature gate is enabled, field should be defaulted
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
if err != nil { if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
} }
// When feature gate is enabled, field should be defaulted
defaultMode := storagev1.VolumeBindingImmediate defaultMode := storagev1.VolumeBindingImmediate
output = roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass) output = roundTrip(t, runtime.Object(class)).(*storagev1.StorageClass)
outMode := output.VolumeBindingMode outMode := output.VolumeBindingMode
......
...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -52,6 +52,10 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class := &storagev1beta1.StorageClass{} class := &storagev1beta1.StorageClass{}
// When feature gate is disabled, field should not be defaulted // When feature gate is disabled, field should not be defaulted
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
}
output := roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass) output := roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass)
if output.VolumeBindingMode != nil { if output.VolumeBindingMode != nil {
t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode) t.Errorf("Expected VolumeBindingMode to not be defaulted, got: %+v", output.VolumeBindingMode)
...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) { ...@@ -59,12 +63,11 @@ func TestSetDefaultVolumeBindingMode(t *testing.T) {
class = &storagev1beta1.StorageClass{} class = &storagev1beta1.StorageClass{}
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true") // When feature gate is enabled, field should be defaulted
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
if err != nil { if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err) t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
} }
// When feature gate is enabled, field should be defaulted
defaultMode := storagev1beta1.VolumeBindingImmediate defaultMode := storagev1beta1.VolumeBindingImmediate
output = roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass) output = roundTrip(t, runtime.Object(class)).(*storagev1beta1.StorageClass)
outMode := output.VolumeBindingMode outMode := output.VolumeBindingMode
......
...@@ -42,16 +42,18 @@ func TestValidateStorageClass(t *testing.T) { ...@@ -42,16 +42,18 @@ func TestValidateStorageClass(t *testing.T) {
successCases := []storage.StorageClass{ successCases := []storage.StorageClass{
{ {
// empty parameters // empty parameters
ObjectMeta: metav1.ObjectMeta{Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Provisioner: "kubernetes.io/foo-provisioner", Provisioner: "kubernetes.io/foo-provisioner",
Parameters: map[string]string{}, Parameters: map[string]string{},
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
{ {
// nil parameters // nil parameters
ObjectMeta: metav1.ObjectMeta{Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Provisioner: "kubernetes.io/foo-provisioner", Provisioner: "kubernetes.io/foo-provisioner",
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
{ {
// some parameters // some parameters
...@@ -62,13 +64,15 @@ func TestValidateStorageClass(t *testing.T) { ...@@ -62,13 +64,15 @@ func TestValidateStorageClass(t *testing.T) {
"foo-parameter": "free-form-string", "foo-parameter": "free-form-string",
"foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}", "foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}",
}, },
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
{ {
// retain reclaimPolicy // retain reclaimPolicy
ObjectMeta: metav1.ObjectMeta{Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Provisioner: "kubernetes.io/foo-provisioner", Provisioner: "kubernetes.io/foo-provisioner",
ReclaimPolicy: &retainReclaimPolicy, ReclaimPolicy: &retainReclaimPolicy,
VolumeBindingMode: &immediateMode1,
}, },
} }
...@@ -144,6 +148,7 @@ func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) { ...@@ -144,6 +148,7 @@ func TestAlphaExpandPersistentVolumesFeatureValidation(t *testing.T) {
Parameters: map[string]string{}, Parameters: map[string]string{},
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
AllowVolumeExpansion: &falseVar, AllowVolumeExpansion: &falseVar,
VolumeBindingMode: &immediateMode1,
} }
// Enable alpha feature ExpandPersistentVolumes // Enable alpha feature ExpandPersistentVolumes
...@@ -462,6 +467,10 @@ func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) { ...@@ -462,6 +467,10 @@ func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) {
"invalid mode": makeClassWithBinding(&invalidMode), "invalid mode": makeClassWithBinding(&invalidMode),
} }
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
if err != nil {
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
}
for testName, storageClass := range errorCases { for testName, storageClass := range errorCases {
if errs := ValidateStorageClass(storageClass); len(errs) == 0 { if errs := ValidateStorageClass(storageClass); len(errs) == 0 {
t.Errorf("Expected failure for test: %v", testName) t.Errorf("Expected failure for test: %v", testName)
......
...@@ -79,7 +79,6 @@ go_test( ...@@ -79,7 +79,6 @@ go_test(
deps = [ deps = [
"//pkg/api/testapi:go_default_library", "//pkg/api/testapi:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
......
...@@ -20,8 +20,6 @@ import ( ...@@ -20,8 +20,6 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/golang/glog"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
...@@ -29,7 +27,6 @@ import ( ...@@ -29,7 +27,6 @@ import (
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
ref "k8s.io/client-go/tools/reference" ref "k8s.io/client-go/tools/reference"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
) )
...@@ -680,9 +677,8 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -680,9 +677,8 @@ func createTestVolumes() []*v1.PersistentVolume {
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-pv", UID: "affinity-pv",
Name: "affinity001", Name: "affinity001",
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -696,13 +692,13 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -696,13 +692,13 @@ func createTestVolumes() []*v1.PersistentVolume {
v1.ReadOnlyMany, v1.ReadOnlyMany,
}, },
StorageClassName: classWait, StorageClassName: classWait,
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-pv2", UID: "affinity-pv2",
Name: "affinity002", Name: "affinity002",
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -716,13 +712,13 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -716,13 +712,13 @@ func createTestVolumes() []*v1.PersistentVolume {
v1.ReadOnlyMany, v1.ReadOnlyMany,
}, },
StorageClassName: classWait, StorageClassName: classWait,
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-prebound", UID: "affinity-prebound",
Name: "affinity003", Name: "affinity003",
Annotations: getAnnotationWithNodeAffinity("key1", "value1"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -737,13 +733,13 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -737,13 +733,13 @@ func createTestVolumes() []*v1.PersistentVolume {
}, },
StorageClassName: classWait, StorageClassName: classWait,
ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"}, ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"},
NodeAffinity: getVolumeNodeAffinity("key1", "value1"),
}, },
}, },
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
UID: "affinity-pv3", UID: "affinity-pv3",
Name: "affinity003", Name: "affinity003",
Annotations: getAnnotationWithNodeAffinity("key1", "value3"),
}, },
Spec: v1.PersistentVolumeSpec{ Spec: v1.PersistentVolumeSpec{
Capacity: v1.ResourceList{ Capacity: v1.ResourceList{
...@@ -757,6 +753,7 @@ func createTestVolumes() []*v1.PersistentVolume { ...@@ -757,6 +753,7 @@ func createTestVolumes() []*v1.PersistentVolume {
v1.ReadOnlyMany, v1.ReadOnlyMany,
}, },
StorageClassName: classWait, StorageClassName: classWait,
NodeAffinity: getVolumeNodeAffinity("key1", "value3"),
}, },
}, },
} }
...@@ -776,9 +773,9 @@ func testVolume(name, size string) *v1.PersistentVolume { ...@@ -776,9 +773,9 @@ func testVolume(name, size string) *v1.PersistentVolume {
} }
} }
func getAnnotationWithNodeAffinity(key string, value string) map[string]string { func getVolumeNodeAffinity(key string, value string) *v1.VolumeNodeAffinity {
affinity := &v1.NodeAffinity{ return &v1.VolumeNodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{ NodeSelectorTerms: []v1.NodeSelectorTerm{
{ {
MatchExpressions: []v1.NodeSelectorRequirement{ MatchExpressions: []v1.NodeSelectorRequirement{
...@@ -792,14 +789,6 @@ func getAnnotationWithNodeAffinity(key string, value string) map[string]string { ...@@ -792,14 +789,6 @@ func getAnnotationWithNodeAffinity(key string, value string) map[string]string {
}, },
}, },
} }
annotations := map[string]string{}
err := helper.StorageNodeAffinityToAlphaAnnotation(annotations, affinity)
if err != nil {
glog.Fatalf("Failed to get node affinity annotation: %v", err)
}
return annotations
} }
func createVolumeModeBlockTestVolume() *v1.PersistentVolume { func createVolumeModeBlockTestVolume() *v1.PersistentVolume {
......
...@@ -331,7 +331,7 @@ func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentV ...@@ -331,7 +331,7 @@ func makeTestPV(name, node, capacity, version string, boundToPVC *v1.PersistentV
}, },
} }
if node != "" { if node != "" {
pv.Annotations = getAnnotationWithNodeAffinity("key1", node) pv.Spec.NodeAffinity = getVolumeNodeAffinity("key1", node)
} }
if boundToPVC != nil { if boundToPVC != nil {
......
...@@ -263,7 +263,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ...@@ -263,7 +263,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
TaintBasedEvictions: {Default: false, PreRelease: utilfeature.Alpha}, TaintBasedEvictions: {Default: false, PreRelease: utilfeature.Alpha},
RotateKubeletServerCertificate: {Default: false, PreRelease: utilfeature.Alpha}, RotateKubeletServerCertificate: {Default: false, PreRelease: utilfeature.Alpha},
RotateKubeletClientCertificate: {Default: true, PreRelease: utilfeature.Beta}, RotateKubeletClientCertificate: {Default: true, PreRelease: utilfeature.Beta},
PersistentLocalVolumes: {Default: false, PreRelease: utilfeature.Alpha}, PersistentLocalVolumes: {Default: true, PreRelease: utilfeature.Beta},
LocalStorageCapacityIsolation: {Default: false, PreRelease: utilfeature.Alpha}, LocalStorageCapacityIsolation: {Default: false, PreRelease: utilfeature.Alpha},
HugePages: {Default: true, PreRelease: utilfeature.Beta}, HugePages: {Default: true, PreRelease: utilfeature.Beta},
DebugContainers: {Default: false, PreRelease: utilfeature.Alpha}, DebugContainers: {Default: false, PreRelease: utilfeature.Alpha},
...@@ -276,7 +276,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ...@@ -276,7 +276,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
CPUManager: {Default: true, PreRelease: utilfeature.Beta}, CPUManager: {Default: true, PreRelease: utilfeature.Beta},
ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha}, ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha},
MountContainers: {Default: false, PreRelease: utilfeature.Alpha}, MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha}, VolumeScheduling: {Default: true, PreRelease: utilfeature.Beta},
CSIPersistentVolume: {Default: true, PreRelease: utilfeature.Beta}, CSIPersistentVolume: {Default: true, PreRelease: utilfeature.Beta},
CustomPodDNS: {Default: false, PreRelease: utilfeature.Alpha}, CustomPodDNS: {Default: false, PreRelease: utilfeature.Alpha},
BlockVolume: {Default: false, PreRelease: utilfeature.Alpha}, BlockVolume: {Default: false, PreRelease: utilfeature.Alpha},
......
...@@ -45,6 +45,7 @@ func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { ...@@ -45,6 +45,7 @@ func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
func validNewStorageClass(name string) *storageapi.StorageClass { func validNewStorageClass(name string) *storageapi.StorageClass {
deleteReclaimPolicy := api.PersistentVolumeReclaimDelete deleteReclaimPolicy := api.PersistentVolumeReclaimDelete
bindingMode := storageapi.VolumeBindingImmediate
return &storageapi.StorageClass{ return &storageapi.StorageClass{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
...@@ -53,7 +54,8 @@ func validNewStorageClass(name string) *storageapi.StorageClass { ...@@ -53,7 +54,8 @@ func validNewStorageClass(name string) *storageapi.StorageClass {
Parameters: map[string]string{ Parameters: map[string]string{
"foo": "bar", "foo": "bar",
}, },
ReclaimPolicy: &deleteReclaimPolicy, ReclaimPolicy: &deleteReclaimPolicy,
VolumeBindingMode: &bindingMode,
} }
} }
......
...@@ -207,7 +207,8 @@ func TestScheduler(t *testing.T) { ...@@ -207,7 +207,8 @@ func TestScheduler(t *testing.T) {
NextPod: func() *v1.Pod { NextPod: func() *v1.Pod {
return item.sendPod return item.sendPod
}, },
Recorder: eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}), Recorder: eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: "scheduler"}),
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
}, },
} }
...@@ -555,6 +556,7 @@ func setupTestScheduler(queuedPodStore *clientcache.FIFO, scache schedulercache. ...@@ -555,6 +556,7 @@ func setupTestScheduler(queuedPodStore *clientcache.FIFO, scache schedulercache.
Recorder: &record.FakeRecorder{}, Recorder: &record.FakeRecorder{},
PodConditionUpdater: fakePodConditionUpdater{}, PodConditionUpdater: fakePodConditionUpdater{},
PodPreemptor: fakePodPreemptor{}, PodPreemptor: fakePodPreemptor{},
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
}, },
} }
...@@ -604,6 +606,7 @@ func setupTestSchedulerLongBindingWithRetry(queuedPodStore *clientcache.FIFO, sc ...@@ -604,6 +606,7 @@ func setupTestSchedulerLongBindingWithRetry(queuedPodStore *clientcache.FIFO, sc
PodConditionUpdater: fakePodConditionUpdater{}, PodConditionUpdater: fakePodConditionUpdater{},
PodPreemptor: fakePodPreemptor{}, PodPreemptor: fakePodPreemptor{},
StopEverything: stop, StopEverything: stop,
VolumeBinder: volumebinder.NewFakeVolumeBinder(&persistentvolume.FakeVolumeBinderConfig{AllBound: true}),
}, },
} }
......
...@@ -530,6 +530,16 @@ type PersistentVolumeSpec struct { ...@@ -530,6 +530,16 @@ type PersistentVolumeSpec struct {
// This is an alpha feature and may change in the future. // This is an alpha feature and may change in the future.
// +optional // +optional
VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"` VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"`
// NodeAffinity defines constraints that limit what nodes this volume can be accessed from.
// This field influences the scheduling of pods that use this volume.
// +optional
NodeAffinity *VolumeNodeAffinity `json:"nodeAffinity,omitempty" protobuf:"bytes,9,opt,name=nodeAffinity"`
}
// VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.
type VolumeNodeAffinity struct {
// Required specifies hard node constraints that must be met.
Required *NodeSelector `json:"required,omitempty" protobuf:"bytes,1,opt,name=required"`
} }
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes. // PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes.
......
...@@ -50,7 +50,6 @@ go_library( ...@@ -50,7 +50,6 @@ go_library(
"//pkg/apis/apps:go_default_library", "//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library", "//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/apis/extensions:go_default_library", "//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/conditions:go_default_library", "//pkg/client/conditions:go_default_library",
......
...@@ -36,7 +36,6 @@ import ( ...@@ -36,7 +36,6 @@ import (
"k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/uuid"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
awscloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" awscloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/volume/util/volumehelper" "k8s.io/kubernetes/pkg/volume/util/volumehelper"
...@@ -81,7 +80,7 @@ type PersistentVolumeConfig struct { ...@@ -81,7 +80,7 @@ type PersistentVolumeConfig struct {
NamePrefix string NamePrefix string
Labels labels.Set Labels labels.Set
StorageClassName string StorageClassName string
NodeAffinity *v1.NodeAffinity NodeAffinity *v1.VolumeNodeAffinity
} }
// PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to generate a PVC object. // PersistentVolumeClaimConfig is consumed by MakePersistentVolumeClaim() to generate a PVC object.
...@@ -603,13 +602,9 @@ func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume ...@@ -603,13 +602,9 @@ func MakePersistentVolume(pvConfig PersistentVolumeConfig) *v1.PersistentVolume
}, },
ClaimRef: claimRef, ClaimRef: claimRef,
StorageClassName: pvConfig.StorageClassName, StorageClassName: pvConfig.StorageClassName,
NodeAffinity: pvConfig.NodeAffinity,
}, },
} }
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, pvConfig.NodeAffinity)
if err != nil {
Logf("Setting storage node affinity failed: %v", err)
return nil
}
return pv return pv
} }
......
...@@ -141,7 +141,7 @@ var ( ...@@ -141,7 +141,7 @@ var (
Level: "s0:c0,c1"} Level: "s0:c0,c1"}
) )
var _ = utils.SIGDescribe("PersistentVolumes-local [Feature:LocalPersistentVolumes]", func() { var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
f := framework.NewDefaultFramework("persistent-local-volumes-test") f := framework.NewDefaultFramework("persistent-local-volumes-test")
var ( var (
...@@ -680,8 +680,8 @@ func makeLocalPVConfig(config *localTestConfig, volume *localTestVolume) framewo ...@@ -680,8 +680,8 @@ func makeLocalPVConfig(config *localTestConfig, volume *localTestVolume) framewo
}, },
NamePrefix: "local-pv", NamePrefix: "local-pv",
StorageClassName: config.scName, StorageClassName: config.scName,
NodeAffinity: &v1.NodeAffinity{ NodeAffinity: &v1.VolumeNodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{ Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{ NodeSelectorTerms: []v1.NodeSelectorTerm{
{ {
MatchExpressions: []v1.NodeSelectorRequirement{ MatchExpressions: []v1.NodeSelectorRequirement{
......
...@@ -28,7 +28,6 @@ go_test( ...@@ -28,7 +28,6 @@ go_test(
"//pkg/api/legacyscheme:go_default_library", "//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library", "//pkg/api/testapi:go_default_library",
"//pkg/apis/componentconfig:go_default_library", "//pkg/apis/componentconfig:go_default_library",
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller/nodelifecycle:go_default_library", "//pkg/controller/nodelifecycle:go_default_library",
......
...@@ -39,7 +39,6 @@ import ( ...@@ -39,7 +39,6 @@ import (
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/controller/volume/persistentvolume" "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
"k8s.io/kubernetes/pkg/scheduler" "k8s.io/kubernetes/pkg/scheduler"
"k8s.io/kubernetes/pkg/scheduler/factory" "k8s.io/kubernetes/pkg/scheduler/factory"
...@@ -253,31 +252,26 @@ func makeHostBoundPV(t *testing.T, name, scName, pvcName, ns string, node string ...@@ -253,31 +252,26 @@ func makeHostBoundPV(t *testing.T, name, scName, pvcName, ns string, node string
Path: "/tmp/" + node + "/test-path", Path: "/tmp/" + node + "/test-path",
}, },
}, },
}, NodeAffinity: &v1.VolumeNodeAffinity{
} Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
if pvcName != "" {
pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
}
testNodeAffinity := &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{ {
Key: affinityLabelKey, MatchExpressions: []v1.NodeSelectorRequirement{
Operator: v1.NodeSelectorOpIn, {
Values: []string{node}, Key: affinityLabelKey,
Operator: v1.NodeSelectorOpIn,
Values: []string{node},
},
},
}, },
}, },
}, },
}, },
}, },
} }
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, testNodeAffinity)
if err != nil { if pvcName != "" {
t.Fatalf("Setting storage node affinity failed: %v", err) pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
} }
return pv return pv
......
...@@ -29,7 +29,6 @@ import ( ...@@ -29,7 +29,6 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
) )
type testConfig struct { type testConfig struct {
...@@ -252,32 +251,28 @@ func makePV(t *testing.T, name, scName, pvcName, ns string) *v1.PersistentVolume ...@@ -252,32 +251,28 @@ func makePV(t *testing.T, name, scName, pvcName, ns string) *v1.PersistentVolume
Path: "/test-path", Path: "/test-path",
}, },
}, },
}, NodeAffinity: &v1.VolumeNodeAffinity{
} Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
if pvcName != "" {
pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
}
testNodeAffinity := &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{ {
Key: labelKey, MatchExpressions: []v1.NodeSelectorRequirement{
Operator: v1.NodeSelectorOpIn, {
Values: []string{labelValue}, Key: labelKey,
Operator: v1.NodeSelectorOpIn,
Values: []string{labelValue},
},
},
}, },
}, },
}, },
}, },
}, },
} }
err := helper.StorageNodeAffinityToAlphaAnnotation(pv.Annotations, testNodeAffinity)
if err != nil { if pvcName != "" {
t.Fatalf("Setting storage node affinity failed: %v", err) pv.Spec.ClaimRef = &v1.ObjectReference{Name: pvcName, Namespace: ns}
} }
return pv return pv
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment