2017-11-20 03:41:06 +00:00
/ *
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 scheduler
// This file tests the VolumeScheduling feature.
import (
"fmt"
2018-02-22 23:13:56 +00:00
"strconv"
"strings"
2017-11-20 03:41:06 +00:00
"testing"
2018-02-22 23:13:56 +00:00
"time"
2017-11-20 03:41:06 +00:00
"github.com/golang/glog"
"k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-02-23 01:42:58 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2018-02-22 23:13:56 +00:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2017-11-20 03:41:06 +00:00
clientset "k8s.io/client-go/kubernetes"
2018-02-22 23:13:56 +00:00
"k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
2017-11-20 03:41:06 +00:00
)
type testConfig struct {
client clientset . Interface
ns string
stop <- chan struct { }
teardown func ( )
}
var (
// Delete API objects immediately
deletePeriod = int64 ( 0 )
deleteOption = & metav1 . DeleteOptions { GracePeriodSeconds : & deletePeriod }
modeWait = storagev1 . VolumeBindingWaitForFirstConsumer
modeImmediate = storagev1 . VolumeBindingImmediate
classWait = "wait"
classImmediate = "immediate"
)
const (
2018-02-23 01:42:58 +00:00
node1 = "node-1"
node2 = "node-2"
podLimit = 100
volsPerPod = 5
nodeAffinityLabelKey = "kubernetes.io/hostname"
2017-11-20 03:41:06 +00:00
)
func TestVolumeBinding ( t * testing . T ) {
2018-02-23 01:42:58 +00:00
config := setupCluster ( t , "volume-scheduling" , 2 )
2017-11-20 03:41:06 +00:00
defer config . teardown ( )
cases := map [ string ] struct {
pod * v1 . Pod
pvs [ ] * v1 . PersistentVolume
pvcs [ ] * v1 . PersistentVolumeClaim
2018-02-23 01:42:58 +00:00
// Create these, but they should not be bound in the end
unboundPvcs [ ] * v1 . PersistentVolumeClaim
unboundPvs [ ] * v1 . PersistentVolume
shouldFail bool
2017-11-20 03:41:06 +00:00
} {
"immediate can bind" : {
pod : makePod ( "pod-i-canbind" , config . ns , [ ] string { "pvc-i-canbind" } ) ,
2018-02-22 23:13:56 +00:00
pvs : [ ] * v1 . PersistentVolume { makePV ( t , "pv-i-canbind" , classImmediate , "" , "" , node1 ) } ,
2017-11-20 03:41:06 +00:00
pvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-i-canbind" , config . ns , & classImmediate , "" ) } ,
} ,
2018-02-23 01:42:58 +00:00
"immediate cannot bind" : {
pod : makePod ( "pod-i-cannotbind" , config . ns , [ ] string { "pvc-i-cannotbind" } ) ,
unboundPvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-i-cannotbind" , config . ns , & classImmediate , "" ) } ,
shouldFail : true ,
} ,
2017-11-20 03:41:06 +00:00
"immediate pvc prebound" : {
pod : makePod ( "pod-i-pvc-prebound" , config . ns , [ ] string { "pvc-i-prebound" } ) ,
2018-02-22 23:13:56 +00:00
pvs : [ ] * v1 . PersistentVolume { makePV ( t , "pv-i-pvc-prebound" , classImmediate , "" , "" , node1 ) } ,
2017-11-20 03:41:06 +00:00
pvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-i-prebound" , config . ns , & classImmediate , "pv-i-pvc-prebound" ) } ,
} ,
"immediate pv prebound" : {
pod : makePod ( "pod-i-pv-prebound" , config . ns , [ ] string { "pvc-i-pv-prebound" } ) ,
2018-02-22 23:13:56 +00:00
pvs : [ ] * v1 . PersistentVolume { makePV ( t , "pv-i-prebound" , classImmediate , "pvc-i-pv-prebound" , config . ns , node1 ) } ,
2017-11-20 03:41:06 +00:00
pvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-i-pv-prebound" , config . ns , & classImmediate , "" ) } ,
} ,
"wait can bind" : {
pod : makePod ( "pod-w-canbind" , config . ns , [ ] string { "pvc-w-canbind" } ) ,
2018-02-22 23:13:56 +00:00
pvs : [ ] * v1 . PersistentVolume { makePV ( t , "pv-w-canbind" , classWait , "" , "" , node1 ) } ,
2017-11-20 03:41:06 +00:00
pvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-w-canbind" , config . ns , & classWait , "" ) } ,
} ,
2018-02-23 01:42:58 +00:00
"wait cannot bind" : {
pod : makePod ( "pod-w-cannotbind" , config . ns , [ ] string { "pvc-w-cannotbind" } ) ,
unboundPvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-w-cannotbind" , config . ns , & classWait , "" ) } ,
shouldFail : true ,
} ,
2017-11-20 03:41:06 +00:00
"wait pvc prebound" : {
pod : makePod ( "pod-w-pvc-prebound" , config . ns , [ ] string { "pvc-w-prebound" } ) ,
2018-02-22 23:13:56 +00:00
pvs : [ ] * v1 . PersistentVolume { makePV ( t , "pv-w-pvc-prebound" , classWait , "" , "" , node1 ) } ,
2017-11-20 03:41:06 +00:00
pvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-w-prebound" , config . ns , & classWait , "pv-w-pvc-prebound" ) } ,
} ,
"wait pv prebound" : {
pod : makePod ( "pod-w-pv-prebound" , config . ns , [ ] string { "pvc-w-pv-prebound" } ) ,
2018-02-22 23:13:56 +00:00
pvs : [ ] * v1 . PersistentVolume { makePV ( t , "pv-w-prebound" , classWait , "pvc-w-pv-prebound" , config . ns , node1 ) } ,
2017-11-20 03:41:06 +00:00
pvcs : [ ] * v1 . PersistentVolumeClaim { makePVC ( "pvc-w-pv-prebound" , config . ns , & classWait , "" ) } ,
} ,
"wait can bind two" : {
pod : makePod ( "pod-w-canbind-2" , config . ns , [ ] string { "pvc-w-canbind-2" , "pvc-w-canbind-3" } ) ,
pvs : [ ] * v1 . PersistentVolume {
2018-02-23 01:42:58 +00:00
makePV ( t , "pv-w-canbind-2" , classWait , "" , "" , node2 ) ,
makePV ( t , "pv-w-canbind-3" , classWait , "" , "" , node2 ) ,
2017-11-20 03:41:06 +00:00
} ,
pvcs : [ ] * v1 . PersistentVolumeClaim {
makePVC ( "pvc-w-canbind-2" , config . ns , & classWait , "" ) ,
makePVC ( "pvc-w-canbind-3" , config . ns , & classWait , "" ) ,
} ,
2018-02-23 01:42:58 +00:00
unboundPvs : [ ] * v1 . PersistentVolume {
makePV ( t , "pv-w-canbind-5" , classWait , "" , "" , node1 ) ,
} ,
} ,
"wait cannot bind two" : {
pod : makePod ( "pod-w-cannotbind-2" , config . ns , [ ] string { "pvc-w-cannotbind-1" , "pvc-w-cannotbind-2" } ) ,
unboundPvcs : [ ] * v1 . PersistentVolumeClaim {
makePVC ( "pvc-w-cannotbind-1" , config . ns , & classWait , "" ) ,
makePVC ( "pvc-w-cannotbind-2" , config . ns , & classWait , "" ) ,
} ,
unboundPvs : [ ] * v1 . PersistentVolume {
makePV ( t , "pv-w-cannotbind-1" , classWait , "" , "" , node2 ) ,
makePV ( t , "pv-w-cannotbind-2" , classWait , "" , "" , node1 ) ,
} ,
shouldFail : true ,
2017-11-20 03:41:06 +00:00
} ,
"mix immediate and wait" : {
pod : makePod ( "pod-mix-bound" , config . ns , [ ] string { "pvc-w-canbind-4" , "pvc-i-canbind-2" } ) ,
pvs : [ ] * v1 . PersistentVolume {
2018-02-22 23:13:56 +00:00
makePV ( t , "pv-w-canbind-4" , classWait , "" , "" , node1 ) ,
makePV ( t , "pv-i-canbind-2" , classImmediate , "" , "" , node1 ) ,
2017-11-20 03:41:06 +00:00
} ,
pvcs : [ ] * v1 . PersistentVolumeClaim {
makePVC ( "pvc-w-canbind-4" , config . ns , & classWait , "" ) ,
makePVC ( "pvc-i-canbind-2" , config . ns , & classImmediate , "" ) ,
} ,
} ,
}
for name , test := range cases {
glog . Infof ( "Running test %v" , name )
// Create PVs
for _ , pv := range test . pvs {
if _ , err := config . client . CoreV1 ( ) . PersistentVolumes ( ) . Create ( pv ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolume %q: %v" , pv . Name , err )
}
}
2018-02-23 01:42:58 +00:00
for _ , pv := range test . unboundPvs {
if _ , err := config . client . CoreV1 ( ) . PersistentVolumes ( ) . Create ( pv ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolume %q: %v" , pv . Name , err )
}
}
2017-11-20 03:41:06 +00:00
// Create PVCs
for _ , pvc := range test . pvcs {
if _ , err := config . client . CoreV1 ( ) . PersistentVolumeClaims ( config . ns ) . Create ( pvc ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolumeClaim %q: %v" , pvc . Name , err )
}
}
2018-02-23 01:42:58 +00:00
for _ , pvc := range test . unboundPvcs {
if _ , err := config . client . CoreV1 ( ) . PersistentVolumeClaims ( config . ns ) . Create ( pvc ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolumeClaim %q: %v" , pvc . Name , err )
}
}
2017-11-20 03:41:06 +00:00
// Create Pod
if _ , err := config . client . CoreV1 ( ) . Pods ( config . ns ) . Create ( test . pod ) ; err != nil {
t . Fatalf ( "Failed to create Pod %q: %v" , test . pod . Name , err )
}
2018-02-23 01:42:58 +00:00
if test . shouldFail {
if err := waitForPodUnschedulable ( config . client , test . pod ) ; err != nil {
t . Errorf ( "Pod %q was not unschedulable: %v" , test . pod . Name , err )
}
} else {
if err := waitForPodToSchedule ( config . client , test . pod ) ; err != nil {
t . Errorf ( "Failed to schedule Pod %q: %v" , test . pod . Name , err )
}
2017-11-20 03:41:06 +00:00
}
// Validate PVC/PV binding
for _ , pvc := range test . pvcs {
validatePVCPhase ( t , config . client , pvc , v1 . ClaimBound )
}
2018-02-23 01:42:58 +00:00
for _ , pvc := range test . unboundPvcs {
validatePVCPhase ( t , config . client , pvc , v1 . ClaimPending )
}
2017-11-20 03:41:06 +00:00
for _ , pv := range test . pvs {
validatePVPhase ( t , config . client , pv , v1 . VolumeBound )
}
2018-02-23 01:42:58 +00:00
for _ , pv := range test . unboundPvs {
validatePVPhase ( t , config . client , pv , v1 . VolumeAvailable )
}
2017-11-20 03:41:06 +00:00
// TODO: validate events on Pods and PVCs
config . client . CoreV1 ( ) . Pods ( config . ns ) . DeleteCollection ( deleteOption , metav1 . ListOptions { } )
config . client . CoreV1 ( ) . PersistentVolumeClaims ( config . ns ) . DeleteCollection ( deleteOption , metav1 . ListOptions { } )
config . client . CoreV1 ( ) . PersistentVolumes ( ) . DeleteCollection ( deleteOption , metav1 . ListOptions { } )
}
}
// TestVolumeBindingStress creates <podLimit> pods, each with <volsPerPod> unbound PVCs.
func TestVolumeBindingStress ( t * testing . T ) {
2018-02-23 01:42:58 +00:00
config := setupCluster ( t , "volume-binding-stress" , 1 )
2017-11-20 03:41:06 +00:00
defer config . teardown ( )
// Create enough PVs and PVCs for all the pods
pvs := [ ] * v1 . PersistentVolume { }
pvcs := [ ] * v1 . PersistentVolumeClaim { }
for i := 0 ; i < podLimit * volsPerPod ; i ++ {
2018-02-22 23:13:56 +00:00
pv := makePV ( t , fmt . Sprintf ( "pv-stress-%v" , i ) , classWait , "" , "" , node1 )
2017-11-20 03:41:06 +00:00
pvc := makePVC ( fmt . Sprintf ( "pvc-stress-%v" , i ) , config . ns , & classWait , "" )
if pv , err := config . client . CoreV1 ( ) . PersistentVolumes ( ) . Create ( pv ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolume %q: %v" , pv . Name , err )
}
if pvc , err := config . client . CoreV1 ( ) . PersistentVolumeClaims ( config . ns ) . Create ( pvc ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolumeClaim %q: %v" , pvc . Name , err )
}
pvs = append ( pvs , pv )
pvcs = append ( pvcs , pvc )
}
pods := [ ] * v1 . Pod { }
for i := 0 ; i < podLimit ; i ++ {
// Generate string of all the PVCs for the pod
podPvcs := [ ] string { }
for j := i * volsPerPod ; j < ( i + 1 ) * volsPerPod ; j ++ {
podPvcs = append ( podPvcs , pvcs [ j ] . Name )
}
pod := makePod ( fmt . Sprintf ( "pod%v" , i ) , config . ns , podPvcs )
if pod , err := config . client . CoreV1 ( ) . Pods ( config . ns ) . Create ( pod ) ; err != nil {
t . Fatalf ( "Failed to create Pod %q: %v" , pod . Name , err )
}
pods = append ( pods , pod )
}
// Validate Pods scheduled
for _ , pod := range pods {
if err := waitForPodToSchedule ( config . client , pod ) ; err != nil {
t . Errorf ( "Failed to schedule Pod %q: %v" , pod . Name , err )
}
}
// Validate PVC/PV binding
for _ , pvc := range pvcs {
validatePVCPhase ( t , config . client , pvc , v1 . ClaimBound )
}
for _ , pv := range pvs {
validatePVPhase ( t , config . client , pv , v1 . VolumeBound )
}
// TODO: validate events on Pods and PVCs
}
2018-02-22 23:13:56 +00:00
func TestPVAffinityConflict ( t * testing . T ) {
2018-02-23 01:42:58 +00:00
config := setupCluster ( t , "volume-scheduling" , 3 )
2018-02-22 23:13:56 +00:00
defer config . teardown ( )
pv := makePV ( t , "local-pv" , classImmediate , "" , "" , node1 )
pvc := makePVC ( "local-pvc" , config . ns , & classImmediate , "" )
// Create PV
if _ , err := config . client . CoreV1 ( ) . PersistentVolumes ( ) . Create ( pv ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolume %q: %v" , pv . Name , err )
}
// Create PVC
if _ , err := config . client . CoreV1 ( ) . PersistentVolumeClaims ( config . ns ) . Create ( pvc ) ; err != nil {
t . Fatalf ( "Failed to create PersistentVolumeClaim %q: %v" , pvc . Name , err )
}
2018-02-23 01:42:58 +00:00
// Wait for PVC bound
if err := waitForPVCBound ( config . client , pvc ) ; err != nil {
t . Fatalf ( "PVC %q failed to bind: %v" , pvc . Name , err )
}
2018-02-22 23:13:56 +00:00
nodeMarkers := [ ] interface { } {
markNodeAffinity ,
markNodeSelector ,
}
for i := 0 ; i < len ( nodeMarkers ) ; i ++ {
podName := "local-pod-" + strconv . Itoa ( i + 1 )
pod := makePod ( podName , config . ns , [ ] string { "local-pvc" } )
nodeMarkers [ i ] . ( func ( * v1 . Pod , string ) ) ( pod , "node-2" )
// Create Pod
if _ , err := config . client . CoreV1 ( ) . Pods ( config . ns ) . Create ( pod ) ; err != nil {
t . Fatalf ( "Failed to create Pod %q: %v" , pod . Name , err )
}
// Give time to shceduler to attempt to schedule pod
2018-02-23 01:42:58 +00:00
if err := waitForPodUnschedulable ( config . client , pod ) ; err != nil {
t . Errorf ( "Failed as Pod %s was not unschedulable: %v" , pod . Name , err )
2018-02-22 23:13:56 +00:00
}
2018-02-23 01:42:58 +00:00
// Check pod conditions
2018-02-22 23:13:56 +00:00
p , err := config . client . CoreV1 ( ) . Pods ( config . ns ) . Get ( podName , metav1 . GetOptions { } )
if err != nil {
t . Fatalf ( "Failed to access Pod %s status: %v" , podName , err )
}
if strings . Compare ( string ( p . Status . Phase ) , "Pending" ) != 0 {
t . Fatalf ( "Failed as Pod %s was in: %s state and not in expected: Pending state" , podName , p . Status . Phase )
}
if strings . Compare ( p . Status . Conditions [ 0 ] . Reason , "Unschedulable" ) != 0 {
t . Fatalf ( "Failed as Pod %s reason was: %s but expected: Unschedulable" , podName , p . Status . Conditions [ 0 ] . Reason )
}
if ! strings . Contains ( p . Status . Conditions [ 0 ] . Message , "node(s) didn't match node selector" ) || ! strings . Contains ( p . Status . Conditions [ 0 ] . Message , "node(s) had volume node affinity conflict" ) {
2018-02-23 01:42:58 +00:00
t . Fatalf ( "Failed as Pod's %s failure message does not contain expected message: node(s) didn't match node selector, node(s) had volume node affinity conflict. Got message %q" , podName , p . Status . Conditions [ 0 ] . Message )
2018-02-22 23:13:56 +00:00
}
2018-02-23 01:42:58 +00:00
// Deleting test pod
2018-02-22 23:13:56 +00:00
if err := config . client . CoreV1 ( ) . Pods ( config . ns ) . Delete ( podName , & metav1 . DeleteOptions { } ) ; err != nil {
t . Fatalf ( "Failed to delete Pod %s: %v" , podName , err )
}
}
}
2018-02-23 01:42:58 +00:00
func setupCluster ( t * testing . T , nsName string , numberOfNodes int ) * testConfig {
2018-02-22 23:13:56 +00:00
// Enable feature gates
utilfeature . DefaultFeatureGate . Set ( "VolumeScheduling=true,PersistentLocalVolumes=true" )
2018-03-12 01:07:10 +00:00
controllerCh := make ( chan struct { } )
2018-02-22 23:13:56 +00:00
2018-03-12 01:07:10 +00:00
context := initTestScheduler ( t , initTestMaster ( t , nsName , nil ) , controllerCh , false , nil )
2018-02-22 23:13:56 +00:00
2018-03-12 01:07:10 +00:00
clientset := context . clientSet
ns := context . ns . Name
informers := context . informerFactory
2018-02-22 23:13:56 +00:00
// Start PV controller for volume binding.
params := persistentvolume . ControllerParameters {
KubeClient : clientset ,
SyncPeriod : time . Hour , // test shouldn't need to resync
VolumePlugins : nil , // TODO; need later for dynamic provisioning
Cloud : nil ,
ClusterName : "volume-test-cluster" ,
VolumeInformer : informers . Core ( ) . V1 ( ) . PersistentVolumes ( ) ,
ClaimInformer : informers . Core ( ) . V1 ( ) . PersistentVolumeClaims ( ) ,
ClassInformer : informers . Storage ( ) . V1 ( ) . StorageClasses ( ) ,
PodInformer : informers . Core ( ) . V1 ( ) . Pods ( ) ,
EventRecorder : nil , // TODO: add one so we can test PV events
EnableDynamicProvisioning : true ,
}
ctrl , err := persistentvolume . NewController ( params )
if err != nil {
t . Fatalf ( "Failed to create PV controller: %v" , err )
}
go ctrl . Run ( controllerCh )
// Create shared objects
// Create nodes
for i := 0 ; i < numberOfNodes ; i ++ {
testNode := & v1 . Node {
ObjectMeta : metav1 . ObjectMeta {
Name : fmt . Sprintf ( "node-%d" , i + 1 ) ,
2018-02-23 01:42:58 +00:00
Labels : map [ string ] string { nodeAffinityLabelKey : fmt . Sprintf ( "node-%d" , i + 1 ) } ,
2018-02-22 23:13:56 +00:00
} ,
Spec : v1 . NodeSpec { Unschedulable : false } ,
Status : v1 . NodeStatus {
Capacity : v1 . ResourceList {
v1 . ResourcePods : * resource . NewQuantity ( podLimit , resource . DecimalSI ) ,
} ,
Conditions : [ ] v1 . NodeCondition {
{
Type : v1 . NodeReady ,
Status : v1 . ConditionTrue ,
Reason : fmt . Sprintf ( "schedulable condition" ) ,
LastHeartbeatTime : metav1 . Time { Time : time . Now ( ) } ,
} ,
} ,
} ,
}
if _ , err := clientset . CoreV1 ( ) . Nodes ( ) . Create ( testNode ) ; err != nil {
t . Fatalf ( "Failed to create Node %q: %v" , testNode . Name , err )
}
}
// Create SCs
scs := [ ] * storagev1 . StorageClass {
makeStorageClass ( classImmediate , & modeImmediate ) ,
makeStorageClass ( classWait , & modeWait ) ,
}
for _ , sc := range scs {
if _ , err := clientset . StorageV1 ( ) . StorageClasses ( ) . Create ( sc ) ; err != nil {
t . Fatalf ( "Failed to create StorageClass %q: %v" , sc . Name , err )
}
}
return & testConfig {
client : clientset ,
ns : ns ,
stop : controllerCh ,
teardown : func ( ) {
clientset . CoreV1 ( ) . Pods ( ns ) . DeleteCollection ( nil , metav1 . ListOptions { } )
clientset . CoreV1 ( ) . PersistentVolumeClaims ( ns ) . DeleteCollection ( nil , metav1 . ListOptions { } )
clientset . CoreV1 ( ) . PersistentVolumes ( ) . DeleteCollection ( nil , metav1 . ListOptions { } )
clientset . StorageV1 ( ) . StorageClasses ( ) . DeleteCollection ( nil , metav1 . ListOptions { } )
2018-03-12 01:07:10 +00:00
cleanupTest ( t , context )
2018-02-22 23:13:56 +00:00
utilfeature . DefaultFeatureGate . Set ( "VolumeScheduling=false,LocalPersistentVolumes=false" )
} ,
}
}
2017-11-20 03:41:06 +00:00
func makeStorageClass ( name string , mode * storagev1 . VolumeBindingMode ) * storagev1 . StorageClass {
return & storagev1 . StorageClass {
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
} ,
Provisioner : "kubernetes.io/no-provisioner" ,
VolumeBindingMode : mode ,
}
}
2018-02-22 23:13:56 +00:00
func makePV ( t * testing . T , name , scName , pvcName , ns , node string ) * v1 . PersistentVolume {
2017-11-20 03:41:06 +00:00
pv := & v1 . PersistentVolume {
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
Annotations : map [ string ] string { } ,
} ,
Spec : v1 . PersistentVolumeSpec {
Capacity : v1 . ResourceList {
v1 . ResourceName ( v1 . ResourceStorage ) : resource . MustParse ( "5Gi" ) ,
} ,
AccessModes : [ ] v1 . PersistentVolumeAccessMode {
v1 . ReadWriteOnce ,
} ,
StorageClassName : scName ,
PersistentVolumeSource : v1 . PersistentVolumeSource {
Local : & v1 . LocalVolumeSource {
Path : "/test-path" ,
} ,
} ,
2018-01-30 23:41:57 +00:00
NodeAffinity : & v1 . VolumeNodeAffinity {
Required : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
2017-11-20 03:41:06 +00:00
{
2018-01-30 23:41:57 +00:00
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
2018-02-23 01:42:58 +00:00
Key : nodeAffinityLabelKey ,
2018-01-30 23:41:57 +00:00
Operator : v1 . NodeSelectorOpIn ,
2018-02-22 23:13:56 +00:00
Values : [ ] string { node } ,
2018-01-30 23:41:57 +00:00
} ,
} ,
2017-11-20 03:41:06 +00:00
} ,
} ,
} ,
} ,
} ,
}
2018-01-30 23:41:57 +00:00
if pvcName != "" {
pv . Spec . ClaimRef = & v1 . ObjectReference { Name : pvcName , Namespace : ns }
2017-11-20 03:41:06 +00:00
}
2018-01-30 23:41:57 +00:00
2017-11-20 03:41:06 +00:00
return pv
}
func makePVC ( name , ns string , scName * string , volumeName string ) * v1 . PersistentVolumeClaim {
return & v1 . PersistentVolumeClaim {
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
Namespace : ns ,
} ,
Spec : v1 . PersistentVolumeClaimSpec {
AccessModes : [ ] v1 . PersistentVolumeAccessMode {
v1 . ReadWriteOnce ,
} ,
Resources : v1 . ResourceRequirements {
Requests : v1 . ResourceList {
v1 . ResourceName ( v1 . ResourceStorage ) : resource . MustParse ( "5Gi" ) ,
} ,
} ,
StorageClassName : scName ,
VolumeName : volumeName ,
} ,
}
}
func makePod ( name , ns string , pvcs [ ] string ) * v1 . Pod {
volumes := [ ] v1 . Volume { }
for i , pvc := range pvcs {
volumes = append ( volumes , v1 . Volume {
Name : fmt . Sprintf ( "vol%v" , i ) ,
VolumeSource : v1 . VolumeSource {
PersistentVolumeClaim : & v1 . PersistentVolumeClaimVolumeSource {
ClaimName : pvc ,
} ,
} ,
} )
}
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : name ,
Namespace : ns ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "write-pod" ,
Switch to k8s.gcr.io vanity domain
This is the 2nd attempt. The previous was reverted while we figured out
the regional mirrors (oops).
New plan: k8s.gcr.io is a read-only facade that auto-detects your source
region (us, eu, or asia for now) and pulls from the closest. To publish
an image, push k8s-staging.gcr.io and it will be synced to the regionals
automatically (similar to today). For now the staging is an alias to
gcr.io/google_containers (the legacy URL).
When we move off of google-owned projects (working on it), then we just
do a one-time sync, and change the google-internal config, and nobody
outside should notice.
We can, in parallel, change the auto-sync into a manual sync - send a PR
to "promote" something from staging, and a bot activates it. Nice and
visible, easy to keep track of.
2018-01-17 19:36:53 +00:00
Image : "k8s.gcr.io/busybox:1.24" ,
2017-11-20 03:41:06 +00:00
Command : [ ] string { "/bin/sh" } ,
Args : [ ] string { "-c" , "while true; do sleep 1; done" } ,
} ,
} ,
Volumes : volumes ,
} ,
}
}
func validatePVCPhase ( t * testing . T , client clientset . Interface , pvc * v1 . PersistentVolumeClaim , phase v1 . PersistentVolumeClaimPhase ) {
claim , err := client . CoreV1 ( ) . PersistentVolumeClaims ( pvc . Namespace ) . Get ( pvc . Name , metav1 . GetOptions { } )
if err != nil {
t . Errorf ( "Failed to get PVC %v/%v: %v" , pvc . Namespace , pvc . Name , err )
}
if claim . Status . Phase != phase {
t . Errorf ( "PVC %v/%v phase not %v, got %v" , pvc . Namespace , pvc . Name , phase , claim . Status . Phase )
}
}
func validatePVPhase ( t * testing . T , client clientset . Interface , pv * v1 . PersistentVolume , phase v1 . PersistentVolumePhase ) {
pv , err := client . CoreV1 ( ) . PersistentVolumes ( ) . Get ( pv . Name , metav1 . GetOptions { } )
if err != nil {
t . Errorf ( "Failed to get PV %v: %v" , pv . Name , err )
}
if pv . Status . Phase != phase {
t . Errorf ( "PV %v phase not %v, got %v" , pv . Name , phase , pv . Status . Phase )
}
}
2018-02-22 23:13:56 +00:00
2018-02-23 01:42:58 +00:00
func waitForPVCBound ( client clientset . Interface , pvc * v1 . PersistentVolumeClaim ) error {
return wait . Poll ( time . Second , 30 * time . Second , func ( ) ( bool , error ) {
claim , err := client . CoreV1 ( ) . PersistentVolumeClaims ( pvc . Namespace ) . Get ( pvc . Name , metav1 . GetOptions { } )
if err != nil {
return false , err
}
if claim . Status . Phase == v1 . ClaimBound {
return true , nil
}
return false , nil
} )
}
2018-02-22 23:13:56 +00:00
func markNodeAffinity ( pod * v1 . Pod , node string ) {
affinity := & v1 . Affinity {
NodeAffinity : & v1 . NodeAffinity {
RequiredDuringSchedulingIgnoredDuringExecution : & v1 . NodeSelector {
NodeSelectorTerms : [ ] v1 . NodeSelectorTerm {
{
MatchExpressions : [ ] v1 . NodeSelectorRequirement {
{
2018-02-23 01:42:58 +00:00
Key : nodeAffinityLabelKey ,
2018-02-22 23:13:56 +00:00
Operator : v1 . NodeSelectorOpIn ,
Values : [ ] string { node } ,
} ,
} ,
} ,
} ,
} ,
} ,
}
pod . Spec . Affinity = affinity
}
func markNodeSelector ( pod * v1 . Pod , node string ) {
ns := map [ string ] string {
2018-02-23 01:42:58 +00:00
nodeAffinityLabelKey : node ,
2018-02-22 23:13:56 +00:00
}
pod . Spec . NodeSelector = ns
}