mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 06:27:05 +00:00
related test update
This commit is contained in:
parent
b4a57f6855
commit
4c43d626f2
@ -39,8 +39,8 @@ func TestDropAlphaFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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,DynamicProvisioningScheduling=false"); err != nil {
|
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false"); err != nil {
|
||||||
t.Fatalf("Failed to set feature gate for VolumeScheduling or DynamicProvisioningScheduling: %v", err)
|
t.Fatalf("Failed to set feature gate for VolumeScheduling: %v", err)
|
||||||
}
|
}
|
||||||
class := &storage.StorageClass{
|
class := &storage.StorageClass{
|
||||||
VolumeBindingMode: &bindingMode,
|
VolumeBindingMode: &bindingMode,
|
||||||
@ -59,8 +59,8 @@ func TestDropAlphaFields(t *testing.T) {
|
|||||||
VolumeBindingMode: &bindingMode,
|
VolumeBindingMode: &bindingMode,
|
||||||
AllowedTopologies: allowedTopologies,
|
AllowedTopologies: allowedTopologies,
|
||||||
}
|
}
|
||||||
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true,DynamicProvisioningScheduling=true"); err != nil {
|
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true"); err != nil {
|
||||||
t.Fatalf("Failed to set feature gate for VolumeScheduling or DynamicProvisioningScheduling: %v", err)
|
t.Fatalf("Failed to set feature gate for VolumeScheduling: %v", err)
|
||||||
}
|
}
|
||||||
DropDisabledAlphaFields(class)
|
DropDisabledAlphaFields(class)
|
||||||
if class.VolumeBindingMode != &bindingMode {
|
if class.VolumeBindingMode != &bindingMode {
|
||||||
@ -70,7 +70,7 @@ func TestDropAlphaFields(t *testing.T) {
|
|||||||
t.Errorf("AllowedTopologies field got unexpectantly modified: %+v", class.AllowedTopologies)
|
t.Errorf("AllowedTopologies field got unexpectantly modified: %+v", class.AllowedTopologies)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false,DynamicProvisioningScheduling=false"); err != nil {
|
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false"); err != nil {
|
||||||
t.Fatalf("Failed to disable feature gate for VolumeScheduling or DynamicProvisioningScheduling: %v", err)
|
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -827,73 +827,67 @@ func TestValidateAllowedTopologies(t *testing.T) {
|
|||||||
|
|
||||||
cases := map[string]bindingTest{
|
cases := map[string]bindingTest{
|
||||||
"no topology": {
|
"no topology": {
|
||||||
class: makeClass(nil, nil),
|
class: makeClass(&waitingMode, nil),
|
||||||
shouldSucceed: true,
|
shouldSucceed: true,
|
||||||
},
|
},
|
||||||
"valid topology": {
|
"valid topology": {
|
||||||
class: makeClass(nil, validTopology),
|
class: makeClass(&waitingMode, validTopology),
|
||||||
shouldSucceed: true,
|
shouldSucceed: true,
|
||||||
},
|
},
|
||||||
"topology invalid key": {
|
"topology invalid key": {
|
||||||
class: makeClass(nil, topologyInvalidKey),
|
class: makeClass(&waitingMode, topologyInvalidKey),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
"topology lack of values": {
|
"topology lack of values": {
|
||||||
class: makeClass(nil, topologyLackOfValues),
|
class: makeClass(&waitingMode, topologyLackOfValues),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
"duplicate TopologySelectorRequirement values": {
|
"duplicate TopologySelectorRequirement values": {
|
||||||
class: makeClass(nil, topologyDupValues),
|
class: makeClass(&waitingMode, topologyDupValues),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
"multiple TopologySelectorRequirement values": {
|
"multiple TopologySelectorRequirement values": {
|
||||||
class: makeClass(nil, topologyMultiValues),
|
class: makeClass(&waitingMode, topologyMultiValues),
|
||||||
shouldSucceed: true,
|
shouldSucceed: true,
|
||||||
},
|
},
|
||||||
"empty MatchLabelExpressions": {
|
"empty MatchLabelExpressions": {
|
||||||
class: makeClass(nil, topologyEmptyMatchLabelExpressions),
|
class: makeClass(&waitingMode, topologyEmptyMatchLabelExpressions),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
"duplicate MatchLabelExpression keys": {
|
"duplicate MatchLabelExpression keys": {
|
||||||
class: makeClass(nil, topologyDupKeys),
|
class: makeClass(&waitingMode, topologyDupKeys),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
"duplicate MatchLabelExpression keys but across separate terms": {
|
"duplicate MatchLabelExpression keys but across separate terms": {
|
||||||
class: makeClass(nil, topologyMultiTerm),
|
class: makeClass(&waitingMode, topologyMultiTerm),
|
||||||
shouldSucceed: true,
|
shouldSucceed: true,
|
||||||
},
|
},
|
||||||
"duplicate AllowedTopologies terms - identical": {
|
"duplicate AllowedTopologies terms - identical": {
|
||||||
class: makeClass(nil, topologyDupTermsIdentical),
|
class: makeClass(&waitingMode, topologyDupTermsIdentical),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
"two AllowedTopologies terms, with a pair of the same MatchLabelExpressions and a pair of different ones": {
|
"two AllowedTopologies terms, with a pair of the same MatchLabelExpressions and a pair of different ones": {
|
||||||
class: makeClass(nil, topologyExprsOneSameOneDiff),
|
class: makeClass(&waitingMode, topologyExprsOneSameOneDiff),
|
||||||
shouldSucceed: true,
|
shouldSucceed: true,
|
||||||
},
|
},
|
||||||
"two AllowedTopologies terms, with a pair of the same Values and a pair of different ones": {
|
"two AllowedTopologies terms, with a pair of the same Values and a pair of different ones": {
|
||||||
class: makeClass(nil, topologyValuesOneSameOneDiff),
|
class: makeClass(&waitingMode, topologyValuesOneSameOneDiff),
|
||||||
shouldSucceed: true,
|
shouldSucceed: true,
|
||||||
},
|
},
|
||||||
"duplicate AllowedTopologies terms - different MatchLabelExpressions order": {
|
"duplicate AllowedTopologies terms - different MatchLabelExpressions order": {
|
||||||
class: makeClass(nil, topologyDupTermsDiffExprOrder),
|
class: makeClass(&waitingMode, topologyDupTermsDiffExprOrder),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
"duplicate AllowedTopologies terms - different TopologySelectorRequirement values order": {
|
"duplicate AllowedTopologies terms - different TopologySelectorRequirement values order": {
|
||||||
class: makeClass(nil, topologyDupTermsDiffValueOrder),
|
class: makeClass(&waitingMode, topologyDupTermsDiffValueOrder),
|
||||||
shouldSucceed: false,
|
shouldSucceed: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable VolumeScheduling so nil VolumeBindingMode doesn't fail to validate.
|
|
||||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove when feature gate not required
|
// TODO: remove when feature gate not required
|
||||||
err = utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=true")
|
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to enable feature gate for DynamicProvisioningScheduling: %v", err)
|
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for testName, testCase := range cases {
|
for testName, testCase := range cases {
|
||||||
@ -906,9 +900,9 @@ func TestValidateAllowedTopologies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=false")
|
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to disable feature gate for DynamicProvisioningScheduling: %v", err)
|
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for testName, testCase := range cases {
|
for testName, testCase := range cases {
|
||||||
|
@ -237,12 +237,21 @@ func addVolumeAnnotation(volume *v1.PersistentVolume, annName, annValue string)
|
|||||||
return volume
|
return volume
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePVCClass(scName *string) *v1.PersistentVolumeClaim {
|
func makePVCClass(scName *string, hasSelectNodeAnno bool) *v1.PersistentVolumeClaim {
|
||||||
return &v1.PersistentVolumeClaim{
|
claim := &v1.PersistentVolumeClaim{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{},
|
||||||
|
},
|
||||||
Spec: v1.PersistentVolumeClaimSpec{
|
Spec: v1.PersistentVolumeClaimSpec{
|
||||||
StorageClassName: scName,
|
StorageClassName: scName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasSelectNodeAnno {
|
||||||
|
claim.Annotations[annSelectedNode] = "node-name"
|
||||||
|
}
|
||||||
|
|
||||||
|
return claim
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeStorageClass(scName string, mode *storagev1.VolumeBindingMode) *storagev1.StorageClass {
|
func makeStorageClass(scName string, mode *storagev1.VolumeBindingMode) *storagev1.StorageClass {
|
||||||
@ -271,26 +280,30 @@ func TestDelayBinding(t *testing.T) {
|
|||||||
shouldFail bool
|
shouldFail bool
|
||||||
}{
|
}{
|
||||||
"nil-class": {
|
"nil-class": {
|
||||||
pvc: makePVCClass(nil),
|
pvc: makePVCClass(nil, false),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
},
|
},
|
||||||
"class-not-found": {
|
"class-not-found": {
|
||||||
pvc: makePVCClass(&classNotHere),
|
pvc: makePVCClass(&classNotHere, false),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
},
|
},
|
||||||
"no-mode-class": {
|
"no-mode-class": {
|
||||||
pvc: makePVCClass(&classNoMode),
|
pvc: makePVCClass(&classNoMode, false),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
},
|
},
|
||||||
"immediate-mode-class": {
|
"immediate-mode-class": {
|
||||||
pvc: makePVCClass(&classImmediateMode),
|
pvc: makePVCClass(&classImmediateMode, false),
|
||||||
shouldDelay: false,
|
shouldDelay: false,
|
||||||
},
|
},
|
||||||
"wait-mode-class": {
|
"wait-mode-class": {
|
||||||
pvc: makePVCClass(&classWaitMode),
|
pvc: makePVCClass(&classWaitMode, false),
|
||||||
shouldDelay: true,
|
shouldDelay: true,
|
||||||
},
|
},
|
||||||
|
"wait-mode-class-with-selectedNode": {
|
||||||
|
pvc: makePVCClass(&classWaitMode, true),
|
||||||
|
shouldDelay: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
classes := []*storagev1.StorageClass{
|
classes := []*storagev1.StorageClass{
|
||||||
@ -314,7 +327,7 @@ func TestDelayBinding(t *testing.T) {
|
|||||||
|
|
||||||
// When volumeScheduling feature gate is disabled, should always be delayed
|
// When volumeScheduling feature gate is disabled, should always be delayed
|
||||||
name := "volumeScheduling-feature-disabled"
|
name := "volumeScheduling-feature-disabled"
|
||||||
shouldDelay, err := ctrl.shouldDelayBinding(makePVCClass(&classWaitMode))
|
shouldDelay, err := ctrl.shouldDelayBinding(makePVCClass(&classWaitMode, false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Test %q returned error: %v", name, err)
|
t.Errorf("Test %q returned error: %v", name, err)
|
||||||
}
|
}
|
||||||
@ -338,43 +351,4 @@ func TestDelayBinding(t *testing.T) {
|
|||||||
t.Errorf("Test %q returned unexpected %v", name, test.shouldDelay)
|
t.Errorf("Test %q returned unexpected %v", name, test.shouldDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When dynamicProvisioningScheduling feature gate is disabled, should be delayed,
|
|
||||||
// even if the pvc has selectedNode annotation.
|
|
||||||
provisionedClaim := makePVCClass(&classWaitMode)
|
|
||||||
provisionedClaim.Annotations = map[string]string{annSelectedNode: "node-name"}
|
|
||||||
name = "dynamicProvisioningScheduling-feature-disabled"
|
|
||||||
shouldDelay, err = ctrl.shouldDelayBinding(provisionedClaim)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test %q returned error: %v", name, err)
|
|
||||||
}
|
|
||||||
if !shouldDelay {
|
|
||||||
t.Errorf("Test %q returned false, expected true", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable DynamicProvisioningScheduling feature gate
|
|
||||||
utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=true")
|
|
||||||
defer utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=false")
|
|
||||||
|
|
||||||
// When the pvc does not have selectedNode annotation, should be delayed,
|
|
||||||
// even if dynamicProvisioningScheduling feature gate is enabled.
|
|
||||||
name = "dynamicProvisioningScheduling-feature-enabled, selectedNode-annotation-not-set"
|
|
||||||
shouldDelay, err = ctrl.shouldDelayBinding(makePVCClass(&classWaitMode))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test %q returned error: %v", name, err)
|
|
||||||
}
|
|
||||||
if !shouldDelay {
|
|
||||||
t.Errorf("Test %q returned false, expected true", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should not be delayed when dynamicProvisioningScheduling feature gate is enabled,
|
|
||||||
// and the pvc has selectedNode annotation.
|
|
||||||
name = "dynamicProvisioningScheduling-feature-enabled, selectedNode-annotation-set"
|
|
||||||
shouldDelay, err = ctrl.shouldDelayBinding(provisionedClaim)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Test %q returned error: %v", name, err)
|
|
||||||
}
|
|
||||||
if shouldDelay {
|
|
||||||
t.Errorf("Test %q returned true, expected false", name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,10 @@ var (
|
|||||||
badPVC = makeBadPVC()
|
badPVC = makeBadPVC()
|
||||||
immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", pvcUnbound, "", "1", &immediateClass)
|
immediateUnboundPVC = makeTestPVC("immediate-unbound-pvc", "1G", pvcUnbound, "", "1", &immediateClass)
|
||||||
immediateBoundPVC = makeTestPVC("immediate-bound-pvc", "1G", pvcBound, "pv-bound-immediate", "1", &immediateClass)
|
immediateBoundPVC = makeTestPVC("immediate-bound-pvc", "1G", pvcBound, "pv-bound-immediate", "1", &immediateClass)
|
||||||
provisionedPVC = makeTestPVC("provisioned-pvc", "1Gi", pvcUnbound, "", "1", &waitClass)
|
provisionedPVC = makeTestPVC("provisioned-pvc", "1Gi", pvcUnbound, "", "1", &waitClassWithProvisioner)
|
||||||
provisionedPVC2 = makeTestPVC("provisioned-pvc2", "1Gi", pvcUnbound, "", "1", &waitClass)
|
provisionedPVC2 = makeTestPVC("provisioned-pvc2", "1Gi", pvcUnbound, "", "1", &waitClassWithProvisioner)
|
||||||
provisionedPVCHigherVersion = makeTestPVC("provisioned-pvc2", "1Gi", pvcUnbound, "", "2", &waitClass)
|
provisionedPVCHigherVersion = makeTestPVC("provisioned-pvc2", "1Gi", pvcUnbound, "", "2", &waitClassWithProvisioner)
|
||||||
noProvisionerPVC = makeTestPVC("no-provisioner-pvc", "1Gi", pvcUnbound, "", "1", &provisionNotSupportClass)
|
noProvisionerPVC = makeTestPVC("no-provisioner-pvc", "1Gi", pvcUnbound, "", "1", &waitClass)
|
||||||
topoMismatchPVC = makeTestPVC("topo-mismatch-pvc", "1Gi", pvcUnbound, "", "1", &topoMismatchClass)
|
topoMismatchPVC = makeTestPVC("topo-mismatch-pvc", "1Gi", pvcUnbound, "", "1", &topoMismatchClass)
|
||||||
|
|
||||||
pvNoNode = makeTestPV("pv-no-node", "", "1G", "1", nil, waitClass)
|
pvNoNode = makeTestPV("pv-no-node", "", "1G", "1", nil, waitClass)
|
||||||
@ -74,7 +74,7 @@ var (
|
|||||||
|
|
||||||
waitClass = "waitClass"
|
waitClass = "waitClass"
|
||||||
immediateClass = "immediateClass"
|
immediateClass = "immediateClass"
|
||||||
provisionNotSupportClass = "provisionNotSupportedClass"
|
waitClassWithProvisioner = "waitClassWithProvisioner"
|
||||||
topoMismatchClass = "topoMismatchClass"
|
topoMismatchClass = "topoMismatchClass"
|
||||||
|
|
||||||
nodeLabelKey = "nodeKey"
|
nodeLabelKey = "nodeKey"
|
||||||
@ -110,7 +110,7 @@ func newTestBinder(t *testing.T) *testEnv {
|
|||||||
classes := []*storagev1.StorageClass{
|
classes := []*storagev1.StorageClass{
|
||||||
{
|
{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: waitClass,
|
Name: waitClassWithProvisioner,
|
||||||
},
|
},
|
||||||
VolumeBindingMode: &waitMode,
|
VolumeBindingMode: &waitMode,
|
||||||
Provisioner: "test-provisioner",
|
Provisioner: "test-provisioner",
|
||||||
@ -133,7 +133,7 @@ func newTestBinder(t *testing.T) *testEnv {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: provisionNotSupportClass,
|
Name: waitClass,
|
||||||
},
|
},
|
||||||
VolumeBindingMode: &waitMode,
|
VolumeBindingMode: &waitMode,
|
||||||
Provisioner: "kubernetes.io/no-provisioner",
|
Provisioner: "kubernetes.io/no-provisioner",
|
||||||
@ -776,9 +776,9 @@ func TestFindPodVolumesWithProvisioning(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set VolumeScheduling and DynamicProvisioningScheduling feature gate
|
// Set VolumeScheduling feature gate
|
||||||
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true,DynamicProvisioningScheduling=true")
|
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||||
defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false,DynamicProvisioningScheduling=false")
|
defer utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||||
|
|
||||||
testNode := &v1.Node{
|
testNode := &v1.Node{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -1032,15 +1032,15 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
// Parameters passed by test to SelectZoneForVolume
|
// Parameters passed by test to SelectZoneForVolume
|
||||||
Name string
|
Name string
|
||||||
ZonePresent bool
|
ZonePresent bool
|
||||||
Zone string
|
Zone string
|
||||||
ZonesPresent bool
|
ZonesPresent bool
|
||||||
Zones string
|
Zones string
|
||||||
ZonesWithNodes string
|
ZonesWithNodes string
|
||||||
Node *v1.Node
|
Node *v1.Node
|
||||||
AllowedTopologies []v1.TopologySelectorTerm
|
AllowedTopologies []v1.TopologySelectorTerm
|
||||||
DynamicProvisioningScheduling bool
|
VolumeScheduling bool
|
||||||
// Expectations around returned zone from SelectZoneForVolume
|
// Expectations around returned zone from SelectZoneForVolume
|
||||||
Reject bool // expect error due to validation failing
|
Reject bool // expect error due to validation failing
|
||||||
ExpectSpecificZone bool // expect returned zone to specifically match a single zone (rather than one from a set)
|
ExpectSpecificZone bool // expect returned zone to specifically match a single zone (rather than one from a set)
|
||||||
@ -1053,7 +1053,7 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] Node irrelevant
|
// [1] Node irrelevant
|
||||||
// [2] Zone and Zones parameters presents
|
// [2] Zone and Zones parameters presents
|
||||||
// [3] AllowedTopologies irrelevant
|
// [3] AllowedTopologies irrelevant
|
||||||
// [4] DynamicProvisioningScheduling irrelevant
|
// [4] VolumeScheduling irrelevant
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_with_Zone_Zones_parameters_present",
|
Name: "Nil_Node_with_Zone_Zones_parameters_present",
|
||||||
ZonePresent: true,
|
ZonePresent: true,
|
||||||
@ -1067,53 +1067,53 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] Node with no zone labels
|
// [1] Node with no zone labels
|
||||||
// [2] Zone/Zones parameter irrelevant
|
// [2] Zone/Zones parameter irrelevant
|
||||||
// [3] AllowedTopologies irrelevant
|
// [3] AllowedTopologies irrelevant
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Node_with_no_Zone_labels",
|
Name: "Node_with_no_Zone_labels",
|
||||||
Node: nodeWithNoLabels,
|
Node: nodeWithNoLabels,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
Reject: true,
|
Reject: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Node with Zone labels as well as Zone parameter specified [Fail]
|
// Node with Zone labels as well as Zone parameter specified [Fail]
|
||||||
// [1] Node with zone labels
|
// [1] Node with zone labels
|
||||||
// [2] Zone parameter specified
|
// [2] Zone parameter specified
|
||||||
// [3] AllowedTopologies irrelevant
|
// [3] AllowedTopologies irrelevant
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Node_with_Zone_labels_and_Zone_parameter_present",
|
Name: "Node_with_Zone_labels_and_Zone_parameter_present",
|
||||||
Node: nodeWithZoneLabels,
|
Node: nodeWithZoneLabels,
|
||||||
ZonePresent: true,
|
ZonePresent: true,
|
||||||
Zone: "zoneX",
|
Zone: "zoneX",
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
Reject: true,
|
Reject: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Node with Zone labels as well as Zones parameter specified [Fail]
|
// Node with Zone labels as well as Zones parameter specified [Fail]
|
||||||
// [1] Node with zone labels
|
// [1] Node with zone labels
|
||||||
// [2] Zones parameter specified
|
// [2] Zones parameter specified
|
||||||
// [3] AllowedTopologies irrelevant
|
// [3] AllowedTopologies irrelevant
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Node_with_Zone_labels_and_Zones_parameter_present",
|
Name: "Node_with_Zone_labels_and_Zones_parameter_present",
|
||||||
Node: nodeWithZoneLabels,
|
Node: nodeWithZoneLabels,
|
||||||
ZonesPresent: true,
|
ZonesPresent: true,
|
||||||
Zones: "zoneX,zoneY",
|
Zones: "zoneX,zoneY",
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
Reject: true,
|
Reject: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Zone parameter as well as AllowedTopologies specified [Fail]
|
// Zone parameter as well as AllowedTopologies specified [Fail]
|
||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] Zone parameter specified
|
// [2] Zone parameter specified
|
||||||
// [3] AllowedTopologies specified
|
// [3] AllowedTopologies specified
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Zone_parameter_and_Allowed_Topology_term",
|
Name: "Nil_Node_and_Zone_parameter_and_Allowed_Topology_term",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
ZonePresent: true,
|
ZonePresent: true,
|
||||||
Zone: "zoneX",
|
Zone: "zoneX",
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
||||||
@ -1131,13 +1131,13 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] Zones parameter specified
|
// [2] Zones parameter specified
|
||||||
// [3] AllowedTopologies specified
|
// [3] AllowedTopologies specified
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Zones_parameter_and_Allowed_Topology_term",
|
Name: "Nil_Node_and_Zones_parameter_and_Allowed_Topology_term",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
ZonesPresent: true,
|
ZonesPresent: true,
|
||||||
Zones: "zoneX,zoneY",
|
Zones: "zoneX,zoneY",
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
||||||
@ -1155,11 +1155,11 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] no Zone/Zones parameter
|
// [2] no Zone/Zones parameter
|
||||||
// [3] AllowedTopologies with invalid key specified
|
// [3] AllowedTopologies with invalid key specified
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Invalid_Allowed_Topology_Key",
|
Name: "Nil_Node_and_Invalid_Allowed_Topology_Key",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
||||||
@ -1181,11 +1181,11 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] no Zone/Zones parameter
|
// [2] no Zone/Zones parameter
|
||||||
// [3] Invalid AllowedTopologies
|
// [3] Invalid AllowedTopologies
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Invalid_AllowedTopologies",
|
Name: "Nil_Node_and_Invalid_AllowedTopologies",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{},
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{},
|
||||||
@ -1194,31 +1194,31 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
Reject: true,
|
Reject: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// POSITIVE TESTS WITH DynamicProvisioningScheduling DISABLED
|
// POSITIVE TESTS WITH VolumeScheduling DISABLED
|
||||||
|
|
||||||
// Select zone from active zones [Pass]
|
// Select zone from active zones [Pass]
|
||||||
// [1] nil Node (Node irrelevant)
|
// [1] nil Node (Node irrelevant)
|
||||||
// [2] no Zone parameter
|
// [2] no Zone parameter
|
||||||
// [3] no AllowedTopologies
|
// [3] no AllowedTopologies
|
||||||
// [4] DynamicProvisioningScheduling disabled
|
// [4] VolumeScheduling disabled
|
||||||
{
|
{
|
||||||
Name: "No_Zone_Zones_parameter_and_DynamicProvisioningScheduling_disabled",
|
Name: "No_Zone_Zones_parameter_and_VolumeScheduling_disabled",
|
||||||
ZonesWithNodes: "zoneX,zoneY",
|
ZonesWithNodes: "zoneX,zoneY",
|
||||||
DynamicProvisioningScheduling: false,
|
VolumeScheduling: false,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
ExpectedZones: "zoneX,zoneY",
|
ExpectedZones: "zoneX,zoneY",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Select zone from single zone parameter [Pass]
|
// Select zone from single zone parameter [Pass]
|
||||||
// [1] nil Node (Node irrelevant)
|
// [1] nil Node (Node irrelevant)
|
||||||
// [2] Zone parameter specified
|
// [2] Zone parameter specified
|
||||||
// [3] no AllowedTopologies
|
// [3] no AllowedTopologies
|
||||||
// [4] DynamicProvisioningScheduling disabled
|
// [4] VolumeScheduling disabled
|
||||||
{
|
{
|
||||||
Name: "Zone_parameter_present_and_DynamicProvisioningScheduling_disabled",
|
Name: "Zone_parameter_present_and_VolumeScheduling_disabled",
|
||||||
ZonePresent: true,
|
ZonePresent: true,
|
||||||
Zone: "zoneX",
|
Zone: "zoneX",
|
||||||
DynamicProvisioningScheduling: false,
|
VolumeScheduling: false,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
ExpectSpecificZone: true,
|
ExpectSpecificZone: true,
|
||||||
ExpectedZone: "zoneX",
|
ExpectedZone: "zoneX",
|
||||||
@ -1228,43 +1228,43 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node (Node irrelevant)
|
// [1] nil Node (Node irrelevant)
|
||||||
// [2] Zones parameter specified
|
// [2] Zones parameter specified
|
||||||
// [3] no AllowedTopologies
|
// [3] no AllowedTopologies
|
||||||
// [4] DynamicProvisioningScheduling disabled
|
// [4] VolumeScheduling disabled
|
||||||
{
|
{
|
||||||
Name: "Zones_parameter_present_and_DynamicProvisioningScheduling_disabled",
|
Name: "Zones_parameter_present_and_VolumeScheduling_disabled",
|
||||||
ZonesPresent: true,
|
ZonesPresent: true,
|
||||||
Zones: "zoneX,zoneY",
|
Zones: "zoneX,zoneY",
|
||||||
DynamicProvisioningScheduling: false,
|
VolumeScheduling: false,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
ExpectedZones: "zoneX,zoneY",
|
ExpectedZones: "zoneX,zoneY",
|
||||||
},
|
},
|
||||||
|
|
||||||
// POSITIVE TESTS WITH DynamicProvisioningScheduling ENABLED
|
// POSITIVE TESTS WITH VolumeScheduling ENABLED
|
||||||
|
|
||||||
// Select zone from active zones [Pass]
|
// Select zone from active zones [Pass]
|
||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] no Zone parameter specified
|
// [2] no Zone parameter specified
|
||||||
// [3] no AllowedTopologies
|
// [3] no AllowedTopologies
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_No_Zone_Zones_parameter_and_no_Allowed_topologies_and_DynamicProvisioningScheduling_enabled",
|
Name: "Nil_Node_and_No_Zone_Zones_parameter_and_no_Allowed_topologies_and_VolumeScheduling_enabled",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
ZonesWithNodes: "zoneX,zoneY",
|
ZonesWithNodes: "zoneX,zoneY",
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
ExpectedZones: "zoneX,zoneY",
|
ExpectedZones: "zoneX,zoneY",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Select zone from single zone parameter [Pass]
|
// Select zone from single zone parameter [Pass]
|
||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] Zone parameter specified
|
// [2] Zone parameter specified
|
||||||
// [3] no AllowedTopology specified
|
// [3] no AllowedTopology specified
|
||||||
// [4] DynamicSchedulingEnabled enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Zone_parameter_present_and_DynamicProvisioningScheduling_enabled",
|
Name: "Nil_Node_and_Zone_parameter_present_and_VolumeScheduling_enabled",
|
||||||
ZonePresent: true,
|
ZonePresent: true,
|
||||||
Zone: "zoneX",
|
Zone: "zoneX",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
ExpectSpecificZone: true,
|
ExpectSpecificZone: true,
|
||||||
ExpectedZone: "zoneX",
|
ExpectedZone: "zoneX",
|
||||||
@ -1274,26 +1274,26 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] Zones parameter specified
|
// [2] Zones parameter specified
|
||||||
// [3] no AllowedTopology
|
// [3] no AllowedTopology
|
||||||
// [4] DynamicSchedulingEnabled enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Zones_parameter_present_and_DynamicProvisioningScheduling_enabled",
|
Name: "Nil_Node_and_Zones_parameter_present_and_VolumeScheduling_enabled",
|
||||||
ZonesPresent: true,
|
ZonesPresent: true,
|
||||||
Zones: "zoneX,zoneY",
|
Zones: "zoneX,zoneY",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
ExpectedZones: "zoneX,zoneY",
|
ExpectedZones: "zoneX,zoneY",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Select zone from node label [Pass]
|
// Select zone from node label [Pass]
|
||||||
// [1] Node with zone labels
|
// [1] Node with zone labels
|
||||||
// [2] no zone/zones parameters
|
// [2] no zone/zones parameters
|
||||||
// [3] no AllowedTopology
|
// [3] no AllowedTopology
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Node_with_Zone_labels_and_DynamicProvisioningScheduling_enabled",
|
Name: "Node_with_Zone_labels_and_VolumeScheduling_enabled",
|
||||||
Node: nodeWithZoneLabels,
|
Node: nodeWithZoneLabels,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
ExpectSpecificZone: true,
|
ExpectSpecificZone: true,
|
||||||
ExpectedZone: "zoneX",
|
ExpectedZone: "zoneX",
|
||||||
@ -1303,11 +1303,11 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] Node with zone labels
|
// [1] Node with zone labels
|
||||||
// [2] no Zone/Zones parameters
|
// [2] no Zone/Zones parameters
|
||||||
// [3] AllowedTopology with single term with multiple values specified (ignored)
|
// [3] AllowedTopology with single term with multiple values specified (ignored)
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Node_with_Zone_labels_and_Multiple_Allowed_Topology_values_and_DynamicProvisioningScheduling_enabled",
|
Name: "Node_with_Zone_labels_and_Multiple_Allowed_Topology_values_and_VolumeScheduling_enabled",
|
||||||
Node: nodeWithZoneLabels,
|
Node: nodeWithZoneLabels,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
||||||
@ -1327,11 +1327,11 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] no Zone/Zones parametes specified
|
// [2] no Zone/Zones parametes specified
|
||||||
// [3] AllowedTopologies with single term with multiple values specified
|
// [3] AllowedTopologies with single term with multiple values specified
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_with_Multiple_Allowed_Topology_values_and_DynamicProvisioningScheduling_enabled",
|
Name: "Nil_Node_with_Multiple_Allowed_Topology_values_and_VolumeScheduling_enabled",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
||||||
@ -1350,11 +1350,11 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] no Zone/Zones parametes specified
|
// [2] no Zone/Zones parametes specified
|
||||||
// [3] AllowedTopologies with multiple terms specified
|
// [3] AllowedTopologies with multiple terms specified
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Multiple_Allowed_Topology_terms_and_DynamicProvisioningScheduling_enabled",
|
Name: "Nil_Node_and_Multiple_Allowed_Topology_terms_and_VolumeScheduling_enabled",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
||||||
@ -1382,11 +1382,11 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
// [1] nil Node
|
// [1] nil Node
|
||||||
// [2] no Zone/Zones parametes specified
|
// [2] no Zone/Zones parametes specified
|
||||||
// [3] AllowedTopologies with single term and value specified
|
// [3] AllowedTopologies with single term and value specified
|
||||||
// [4] DynamicProvisioningScheduling enabled
|
// [4] VolumeScheduling enabled
|
||||||
{
|
{
|
||||||
Name: "Nil_Node_and_Single_Allowed_Topology_term_value_and_DynamicProvisioningScheduling_enabled",
|
Name: "Nil_Node_and_Single_Allowed_Topology_term_value_and_VolumeScheduling_enabled",
|
||||||
Node: nil,
|
Node: nil,
|
||||||
DynamicProvisioningScheduling: true,
|
VolumeScheduling: true,
|
||||||
AllowedTopologies: []v1.TopologySelectorTerm{
|
AllowedTopologies: []v1.TopologySelectorTerm{
|
||||||
{
|
{
|
||||||
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
|
||||||
@ -1404,9 +1404,9 @@ func TestSelectZoneForVolume(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=false")
|
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||||
if test.DynamicProvisioningScheduling {
|
if test.VolumeScheduling {
|
||||||
utilfeature.DefaultFeatureGate.Set("DynamicProvisioningScheduling=true")
|
utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||||
}
|
}
|
||||||
|
|
||||||
var zonesParameter, zonesWithNodes sets.String
|
var zonesParameter, zonesWithNodes sets.String
|
||||||
|
@ -1116,6 +1116,16 @@ items:
|
|||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- persistentvolumeclaims
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- patch
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
- aggregationRule:
|
- aggregationRule:
|
||||||
clusterRoleSelectors:
|
clusterRoleSelectors:
|
||||||
- matchLabels:
|
- matchLabels:
|
||||||
|
@ -264,9 +264,8 @@ func TestVolumeBinding(t *testing.T) {
|
|||||||
// TestVolumeBindingRescheduling tests scheduler will retry scheduling when needed.
|
// TestVolumeBindingRescheduling tests scheduler will retry scheduling when needed.
|
||||||
func TestVolumeBindingRescheduling(t *testing.T) {
|
func TestVolumeBindingRescheduling(t *testing.T) {
|
||||||
features := map[string]bool{
|
features := map[string]bool{
|
||||||
"VolumeScheduling": true,
|
"VolumeScheduling": true,
|
||||||
"PersistentLocalVolumes": true,
|
"PersistentLocalVolumes": true,
|
||||||
"DynamicProvisioningScheduling": true,
|
|
||||||
}
|
}
|
||||||
config := setupCluster(t, "volume-scheduling", 2, features, 0)
|
config := setupCluster(t, "volume-scheduling", 2, features, 0)
|
||||||
defer config.teardown()
|
defer config.teardown()
|
||||||
|
Loading…
Reference in New Issue
Block a user