2016-02-29 13:08:05 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-02-29 13:08:05 +00:00
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 .
* /
2017-03-19 19:25:40 +00:00
package storage
2016-02-29 13:08:05 +00:00
import (
2017-03-13 14:41:53 +00:00
"fmt"
2017-04-05 09:49:49 +00:00
"strings"
2016-02-29 13:08:05 +00:00
"time"
2017-03-13 14:41:53 +00:00
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
2017-04-05 09:49:49 +00:00
2016-11-14 14:29:36 +00:00
"github.com/aws/aws-sdk-go/aws"
2017-04-05 09:49:49 +00:00
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
2017-03-13 14:41:53 +00:00
apierrs "k8s.io/apimachinery/pkg/api/errors"
2017-01-25 13:13:07 +00:00
"k8s.io/apimachinery/pkg/api/resource"
2018-02-27 11:26:26 +00:00
"k8s.io/apimachinery/pkg/types"
2017-04-05 09:49:49 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
storage "k8s.io/api/storage/v1"
storagebeta "k8s.io/api/storage/v1beta1"
2017-01-11 14:09:48 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-02-09 19:11:26 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2017-03-13 14:41:53 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2017-02-09 19:11:26 +00:00
"k8s.io/apiserver/pkg/authentication/serviceaccount"
2017-06-23 20:56:37 +00:00
clientset "k8s.io/client-go/kubernetes"
2017-03-17 13:08:39 +00:00
storageutil "k8s.io/kubernetes/pkg/apis/storage/v1/util"
2018-08-10 23:33:46 +00:00
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
2018-08-28 19:35:43 +00:00
volumeutil "k8s.io/kubernetes/pkg/volume/util"
2016-04-07 17:21:31 +00:00
"k8s.io/kubernetes/test/e2e/framework"
2018-08-01 10:46:59 +00:00
"k8s.io/kubernetes/test/e2e/framework/providers/gce"
2018-10-22 16:04:43 +00:00
"k8s.io/kubernetes/test/e2e/storage/testsuites"
2017-11-29 22:08:28 +00:00
"k8s.io/kubernetes/test/e2e/storage/utils"
2016-02-29 13:08:05 +00:00
)
2016-10-17 17:32:27 +00:00
const (
2017-02-09 19:11:26 +00:00
// Plugin name of the external provisioner
externalPluginName = "example.com/nfs"
2016-10-17 17:32:27 +00:00
)
2016-02-29 13:08:05 +00:00
2018-08-10 23:33:46 +00:00
func testBindingWaitForFirstConsumer ( client clientset . Interface , claim * v1 . PersistentVolumeClaim , class * storage . StorageClass ) ( * v1 . PersistentVolume , * v1 . Node ) {
var err error
By ( "creating a storage class " + class . Name )
class , err = client . StorageV1 ( ) . StorageClasses ( ) . Create ( class )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer deleteStorageClass ( client , class . Name )
By ( "creating a claim" )
claim , err = client . CoreV1 ( ) . PersistentVolumeClaims ( claim . Namespace ) . Create ( claim )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer func ( ) {
framework . ExpectNoError ( framework . DeletePersistentVolumeClaim ( client , claim . Name , claim . Namespace ) , "Failed to delete PVC " , claim . Name )
} ( )
// Wait for ClaimProvisionTimeout and make sure the phase did not become Bound i.e. the Wait errors out
err = framework . WaitForPersistentVolumeClaimPhase ( v1 . ClaimBound , client , claim . Namespace , claim . Name , 2 * time . Second , framework . ClaimProvisionShortTimeout )
Expect ( err ) . To ( HaveOccurred ( ) )
By ( "checking the claim is in pending state" )
// Get new copy of the claim
claim , err = client . CoreV1 ( ) . PersistentVolumeClaims ( claim . Namespace ) . Get ( claim . Name , metav1 . GetOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
Expect ( claim . Status . Phase ) . To ( Equal ( v1 . ClaimPending ) )
By ( "creating a pod referring to the claim" )
// Create a pod referring to the claim and wait for it to get to running
pod , err := framework . CreateClientPod ( client , claim . Namespace , claim )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer func ( ) {
framework . DeletePodOrFail ( client , pod . Namespace , pod . Name )
} ( )
By ( "re-checking the claim to see it binded" )
// Get new copy of the claim
claim , err = client . CoreV1 ( ) . PersistentVolumeClaims ( claim . Namespace ) . Get ( claim . Name , metav1 . GetOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
// make sure claim did bind
err = framework . WaitForPersistentVolumeClaimPhase ( v1 . ClaimBound , client , claim . Namespace , claim . Name , framework . Poll , framework . ClaimProvisionTimeout )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
// collect node and pv details
node , err := client . CoreV1 ( ) . Nodes ( ) . Get ( pod . Spec . NodeName , metav1 . GetOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
pv , err := client . CoreV1 ( ) . PersistentVolumes ( ) . Get ( claim . Spec . VolumeName , metav1 . GetOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
return pv , node
}
2018-08-28 19:35:43 +00:00
func checkZoneFromLabelAndAffinity ( pv * v1 . PersistentVolume , zone string , matchZone bool ) {
checkZonesFromLabelAndAffinity ( pv , sets . NewString ( zone ) , matchZone )
}
2018-08-10 23:33:46 +00:00
// checkZoneLabelAndAffinity checks the LabelZoneFailureDomain label of PV and terms
2018-08-28 19:35:43 +00:00
// with key LabelZoneFailureDomain in PV's node affinity contains zone
// matchZones is used to indicate if zones should match perfectly
func checkZonesFromLabelAndAffinity ( pv * v1 . PersistentVolume , zones sets . String , matchZones bool ) {
2018-08-10 23:33:46 +00:00
By ( "checking PV's zone label and node affinity terms match expected zone" )
if pv == nil {
framework . Failf ( "nil pv passed" )
}
pvLabel , ok := pv . Labels [ kubeletapis . LabelZoneFailureDomain ]
if ! ok {
framework . Failf ( "label %s not found on PV" , kubeletapis . LabelZoneFailureDomain )
}
2018-08-28 19:35:43 +00:00
zonesFromLabel , err := volumeutil . LabelZonesToSet ( pvLabel )
if err != nil {
framework . Failf ( "unable to parse zone labels %s: %v" , pvLabel , err )
}
if matchZones && ! zonesFromLabel . Equal ( zones ) {
framework . Failf ( "value[s] of %s label for PV: %v does not match expected zone[s]: %v" , kubeletapis . LabelZoneFailureDomain , zonesFromLabel , zones )
}
if ! matchZones && ! zonesFromLabel . IsSuperset ( zones ) {
framework . Failf ( "value[s] of %s label for PV: %v does not contain expected zone[s]: %v" , kubeletapis . LabelZoneFailureDomain , zonesFromLabel , zones )
2018-08-10 23:33:46 +00:00
}
if pv . Spec . NodeAffinity == nil {
framework . Failf ( "node affinity not found in PV spec %v" , pv . Spec )
}
if len ( pv . Spec . NodeAffinity . Required . NodeSelectorTerms ) == 0 {
framework . Failf ( "node selector terms not found in PV spec %v" , pv . Spec )
}
for _ , term := range pv . Spec . NodeAffinity . Required . NodeSelectorTerms {
keyFound := false
for _ , r := range term . MatchExpressions {
2018-08-28 19:35:43 +00:00
if r . Key != kubeletapis . LabelZoneFailureDomain {
continue
2018-08-10 23:33:46 +00:00
}
2018-08-28 19:35:43 +00:00
keyFound = true
zonesFromNodeAffinity := sets . NewString ( r . Values ... )
if matchZones && ! zonesFromNodeAffinity . Equal ( zones ) {
framework . Failf ( "zones from NodeAffinity of PV: %v does not equal expected zone[s]: %v" , zonesFromNodeAffinity , zones )
}
if ! matchZones && ! zonesFromNodeAffinity . IsSuperset ( zones ) {
framework . Failf ( "zones from NodeAffinity of PV: %v does not contain expected zone[s]: %v" , zonesFromNodeAffinity , zones )
}
break
2018-08-10 23:33:46 +00:00
}
if ! keyFound {
framework . Failf ( "label %s not found in term %v" , kubeletapis . LabelZoneFailureDomain , term )
}
}
}
2017-04-05 09:49:49 +00:00
// checkAWSEBS checks properties of an AWS EBS. Test framework does not
// instantiate full AWS provider, therefore we need use ec2 API directly.
func checkAWSEBS ( volume * v1 . PersistentVolume , volumeType string , encrypted bool ) error {
diskName := volume . Spec . AWSElasticBlockStore . VolumeID
2016-11-14 14:29:36 +00:00
var client * ec2 . EC2
2017-04-05 09:49:49 +00:00
tokens := strings . Split ( diskName , "/" )
volumeID := tokens [ len ( tokens ) - 1 ]
2016-11-14 14:29:36 +00:00
zone := framework . TestContext . CloudConfig . Zone
if len ( zone ) > 0 {
region := zone [ : len ( zone ) - 1 ]
cfg := aws . Config { Region : & region }
framework . Logf ( "using region %s" , region )
client = ec2 . New ( session . New ( ) , & cfg )
} else {
framework . Logf ( "no region configured" )
client = ec2 . New ( session . New ( ) )
}
2017-04-05 09:49:49 +00:00
request := & ec2 . DescribeVolumesInput {
VolumeIds : [ ] * string { & volumeID } ,
}
info , err := client . DescribeVolumes ( request )
if err != nil {
return fmt . Errorf ( "error querying ec2 for volume %q: %v" , volumeID , err )
}
if len ( info . Volumes ) == 0 {
return fmt . Errorf ( "no volumes found for volume %q" , volumeID )
}
if len ( info . Volumes ) > 1 {
return fmt . Errorf ( "multiple volumes found for volume %q" , volumeID )
}
awsVolume := info . Volumes [ 0 ]
if awsVolume . VolumeType == nil {
return fmt . Errorf ( "expected volume type %q, got nil" , volumeType )
}
if * awsVolume . VolumeType != volumeType {
return fmt . Errorf ( "expected volume type %q, got %q" , volumeType , * awsVolume . VolumeType )
}
if encrypted && awsVolume . Encrypted == nil {
return fmt . Errorf ( "expected encrypted volume, got no encryption" )
}
if encrypted && ! * awsVolume . Encrypted {
return fmt . Errorf ( "expected encrypted volume, got %v" , * awsVolume . Encrypted )
}
return nil
}
func checkGCEPD ( volume * v1 . PersistentVolume , volumeType string ) error {
2018-08-01 10:46:59 +00:00
cloud , err := gce . GetGCECloud ( )
2017-04-05 09:49:49 +00:00
if err != nil {
return err
}
diskName := volume . Spec . GCEPersistentDisk . PDName
disk , err := cloud . GetDiskByNameUnknownZone ( diskName )
if err != nil {
return err
}
if ! strings . HasSuffix ( disk . Type , volumeType ) {
return fmt . Errorf ( "unexpected disk type %q, expected suffix %q" , disk . Type , volumeType )
}
return nil
}
2017-11-29 22:08:28 +00:00
var _ = utils . SIGDescribe ( "Dynamic Provisioning" , func ( ) {
2016-04-07 17:21:31 +00:00
f := framework . NewDefaultFramework ( "volume-provisioning" )
2016-02-29 13:08:05 +00:00
// filled in BeforeEach
2016-10-18 13:00:38 +00:00
var c clientset . Interface
2016-02-29 13:08:05 +00:00
var ns string
BeforeEach ( func ( ) {
2016-10-18 13:00:38 +00:00
c = f . ClientSet
2016-04-07 17:21:31 +00:00
ns = f . Namespace . Name
2016-02-29 13:08:05 +00:00
} )
2018-09-20 22:29:00 +00:00
Describe ( "DynamicProvisioner [Slow]" , func ( ) {
2018-06-13 11:09:25 +00:00
It ( "should provision storage with different parameters" , func ( ) {
2017-04-07 11:17:47 +00:00
cloudZone := getRandomCloudZone ( c )
// This test checks that dynamic provisioning can provision a volume
// that can be used to persist data among pods.
2018-10-22 16:04:43 +00:00
tests := [ ] testsuites . StorageClassTest {
2018-04-10 07:29:08 +00:00
// GCE/GKE
2017-04-07 11:17:47 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "SSD PD on GCE/GKE" ,
CloudProviders : [ ] string { "gce" , "gke" } ,
Provisioner : "kubernetes.io/gce-pd" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"type" : "pd-ssd" ,
"zone" : cloudZone ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "1.5Gi" ,
ExpectedSize : "2Gi" ,
PvCheck : func ( volume * v1 . PersistentVolume ) error {
2017-04-07 11:17:47 +00:00
return checkGCEPD ( volume , "pd-ssd" )
} ,
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "HDD PD on GCE/GKE" ,
CloudProviders : [ ] string { "gce" , "gke" } ,
Provisioner : "kubernetes.io/gce-pd" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"type" : "pd-standard" ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "1.5Gi" ,
ExpectedSize : "2Gi" ,
PvCheck : func ( volume * v1 . PersistentVolume ) error {
2017-04-07 11:17:47 +00:00
return checkGCEPD ( volume , "pd-standard" )
} ,
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
// AWS
{
2018-10-22 16:04:43 +00:00
Name : "gp2 EBS on AWS" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"type" : "gp2" ,
"zone" : cloudZone ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "1.5Gi" ,
ExpectedSize : "2Gi" ,
PvCheck : func ( volume * v1 . PersistentVolume ) error {
2017-04-07 11:17:47 +00:00
return checkAWSEBS ( volume , "gp2" , false )
} ,
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "io1 EBS on AWS" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"type" : "io1" ,
"iopsPerGB" : "50" ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "3.5Gi" ,
ExpectedSize : "4Gi" , // 4 GiB is minimum for io1
PvCheck : func ( volume * v1 . PersistentVolume ) error {
2017-04-07 11:17:47 +00:00
return checkAWSEBS ( volume , "io1" , false )
} ,
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "sc1 EBS on AWS" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"type" : "sc1" ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "500Gi" , // minimum for sc1
ExpectedSize : "500Gi" ,
PvCheck : func ( volume * v1 . PersistentVolume ) error {
2017-04-07 11:17:47 +00:00
return checkAWSEBS ( volume , "sc1" , false )
} ,
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "st1 EBS on AWS" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"type" : "st1" ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "500Gi" , // minimum for st1
ExpectedSize : "500Gi" ,
PvCheck : func ( volume * v1 . PersistentVolume ) error {
2017-04-07 11:17:47 +00:00
return checkAWSEBS ( volume , "st1" , false )
} ,
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "encrypted EBS on AWS" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"encrypted" : "true" ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "1Gi" ,
ExpectedSize : "1Gi" ,
PvCheck : func ( volume * v1 . PersistentVolume ) error {
2017-04-07 11:17:47 +00:00
return checkAWSEBS ( volume , "gp2" , true )
} ,
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
// OpenStack generic tests (works on all OpenStack deployments)
{
2018-10-22 16:04:43 +00:00
Name : "generic Cinder volume on OpenStack" ,
CloudProviders : [ ] string { "openstack" } ,
Provisioner : "kubernetes.io/cinder" ,
Parameters : map [ string ] string { } ,
ClaimSize : "1.5Gi" ,
ExpectedSize : "2Gi" ,
PvCheck : nil , // there is currently nothing to check on OpenStack
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "Cinder volume with empty volume type and zone on OpenStack" ,
CloudProviders : [ ] string { "openstack" } ,
Provisioner : "kubernetes.io/cinder" ,
Parameters : map [ string ] string {
2017-04-07 11:17:47 +00:00
"type" : "" ,
"availability" : "" ,
} ,
2018-10-22 16:04:43 +00:00
ClaimSize : "1.5Gi" ,
ExpectedSize : "2Gi" ,
PvCheck : nil , // there is currently nothing to check on OpenStack
2017-04-05 09:49:49 +00:00
} ,
2017-04-07 11:17:47 +00:00
// vSphere generic test
{
2018-10-22 16:04:43 +00:00
Name : "generic vSphere volume" ,
CloudProviders : [ ] string { "vsphere" } ,
Provisioner : "kubernetes.io/vsphere-volume" ,
Parameters : map [ string ] string { } ,
ClaimSize : "1.5Gi" ,
ExpectedSize : "1.5Gi" ,
PvCheck : nil ,
2017-04-05 09:49:49 +00:00
} ,
2018-04-10 07:29:08 +00:00
// Azure
2017-04-28 12:37:50 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "Azure disk volume with empty sku and location" ,
CloudProviders : [ ] string { "azure" } ,
Provisioner : "kubernetes.io/azure-disk" ,
Parameters : map [ string ] string { } ,
ClaimSize : "1Gi" ,
ExpectedSize : "1Gi" ,
PvCheck : nil ,
2017-04-28 12:37:50 +00:00
} ,
2017-04-07 11:17:47 +00:00
}
2017-03-17 13:08:39 +00:00
2018-10-22 16:04:43 +00:00
var betaTest * testsuites . StorageClassTest
2017-04-07 11:17:47 +00:00
for i , t := range tests {
// Beware of clojure, use local variables instead of those from
// outer scope
test := t
2018-10-22 16:04:43 +00:00
if ! framework . ProviderIs ( test . CloudProviders ... ) {
framework . Logf ( "Skipping %q: cloud providers is not %v" , test . Name , test . CloudProviders )
2017-04-07 11:17:47 +00:00
continue
2017-04-05 09:49:49 +00:00
}
2017-03-02 09:23:57 +00:00
2017-04-05 09:49:49 +00:00
// Remember the last supported test for subsequent test of beta API
betaTest = & test
2017-03-02 09:23:57 +00:00
2018-10-22 16:04:43 +00:00
By ( "Testing " + test . Name )
2017-04-07 11:17:47 +00:00
suffix := fmt . Sprintf ( "%d" , i )
2017-04-05 09:49:49 +00:00
class := newStorageClass ( test , ns , suffix )
claim := newClaim ( test , ns , suffix )
claim . Spec . StorageClassName = & class . Name
2018-10-22 16:04:43 +00:00
testsuites . TestDynamicProvisioning ( test , c , claim , class )
2017-04-07 11:17:47 +00:00
}
2017-03-02 09:23:57 +00:00
2018-04-10 07:29:08 +00:00
// Run the last test with storage.k8s.io/v1beta1 on pvc
2017-04-07 11:17:47 +00:00
if betaTest != nil {
2018-10-22 16:04:43 +00:00
By ( "Testing " + betaTest . Name + " with beta volume provisioning" )
2017-04-05 09:49:49 +00:00
class := newBetaStorageClass ( * betaTest , "beta" )
// we need to create the class manually, testDynamicProvisioning does not accept beta class
class , err := c . StorageV1beta1 ( ) . StorageClasses ( ) . Create ( class )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-04-07 11:17:47 +00:00
defer deleteStorageClass ( c , class . Name )
2017-03-02 09:23:57 +00:00
2017-04-05 09:49:49 +00:00
claim := newClaim ( * betaTest , ns , "beta" )
2018-04-10 07:29:08 +00:00
claim . Spec . StorageClassName = & ( class . Name )
2018-10-22 16:04:43 +00:00
testsuites . TestDynamicProvisioning ( * betaTest , c , claim , nil )
2017-04-07 11:17:47 +00:00
}
} )
2017-02-14 21:46:03 +00:00
2018-10-24 19:21:28 +00:00
It ( "should provision storage with non-default reclaim policy Retain" , func ( ) {
framework . SkipUnlessProviderIs ( "gce" , "gke" )
test := testsuites . StorageClassTest {
Name : "HDD PD on GCE/GKE" ,
CloudProviders : [ ] string { "gce" , "gke" } ,
Provisioner : "kubernetes.io/gce-pd" ,
Parameters : map [ string ] string {
"type" : "pd-standard" ,
} ,
ClaimSize : "1Gi" ,
ExpectedSize : "1Gi" ,
PvCheck : func ( volume * v1 . PersistentVolume ) error {
return checkGCEPD ( volume , "pd-standard" )
} ,
}
class := newStorageClass ( test , ns , "reclaimpolicy" )
retain := v1 . PersistentVolumeReclaimRetain
class . ReclaimPolicy = & retain
claim := newClaim ( test , ns , "reclaimpolicy" )
claim . Spec . StorageClassName = & class . Name
pv := testsuites . TestDynamicProvisioning ( test , c , claim , class )
By ( fmt . Sprintf ( "waiting for the provisioned PV %q to enter phase %s" , pv . Name , v1 . VolumeReleased ) )
framework . ExpectNoError ( framework . WaitForPersistentVolumePhase ( v1 . VolumeReleased , c , pv . Name , 1 * time . Second , 30 * time . Second ) )
By ( fmt . Sprintf ( "deleting the storage asset backing the PV %q" , pv . Name ) )
framework . ExpectNoError ( framework . DeletePDWithRetry ( pv . Spec . GCEPersistentDisk . PDName ) )
By ( fmt . Sprintf ( "deleting the PV %q" , pv . Name ) )
framework . ExpectNoError ( framework . DeletePersistentVolume ( c , pv . Name ) , "Failed to delete PV " , pv . Name )
framework . ExpectNoError ( framework . WaitForPersistentVolumeDeleted ( c , pv . Name , 1 * time . Second , 30 * time . Second ) )
} )
2018-04-16 08:32:48 +00:00
It ( "should not provision a volume in an unmanaged GCE zone." , func ( ) {
2017-02-14 21:46:03 +00:00
framework . SkipUnlessProviderIs ( "gce" , "gke" )
var suffix string = "unmananged"
By ( "Discovering an unmanaged zone" )
allZones := sets . NewString ( ) // all zones in the project
managedZones := sets . NewString ( ) // subset of allZones
2018-08-01 10:46:59 +00:00
gceCloud , err := gce . GetGCECloud ( )
2017-02-14 21:46:03 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-09-12 03:20:29 +00:00
// Get all k8s managed zones (same as zones with nodes in them for test)
managedZones , err = gceCloud . GetAllZonesFromCloudProvider ( )
2017-02-14 21:46:03 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
// Get a list of all zones in the project
2017-12-22 19:20:57 +00:00
zones , err := gceCloud . ComputeServices ( ) . GA . Zones . List ( framework . TestContext . CloudConfig . ProjectID ) . Do ( )
2017-04-01 04:42:52 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-02-14 21:46:03 +00:00
for _ , z := range zones . Items {
allZones . Insert ( z . Name )
}
// Get the subset of zones not managed by k8s
var unmanagedZone string
var popped bool
unmanagedZones := allZones . Difference ( managedZones )
// And select one of them at random.
if unmanagedZone , popped = unmanagedZones . PopAny ( ) ; ! popped {
framework . Skipf ( "No unmanaged zones found." )
}
By ( "Creating a StorageClass for the unmanaged zone" )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "unmanaged_zone" ,
Provisioner : "kubernetes.io/gce-pd" ,
Parameters : map [ string ] string { "zone" : unmanagedZone } ,
ClaimSize : "1Gi" ,
2017-04-05 09:49:49 +00:00
}
sc := newStorageClass ( test , ns , suffix )
sc , err = c . StorageV1 ( ) . StorageClasses ( ) . Create ( sc )
2017-02-14 21:46:03 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-03-13 14:41:53 +00:00
defer deleteStorageClass ( c , sc . Name )
2017-02-14 21:46:03 +00:00
By ( "Creating a claim and expecting it to timeout" )
2017-04-05 09:49:49 +00:00
pvc := newClaim ( test , ns , suffix )
2017-03-17 13:08:39 +00:00
pvc . Spec . StorageClassName = & sc . Name
2017-04-07 11:17:47 +00:00
pvc , err = c . CoreV1 ( ) . PersistentVolumeClaims ( ns ) . Create ( pvc )
2017-02-14 21:46:03 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-04-01 04:42:52 +00:00
defer func ( ) {
framework . ExpectNoError ( framework . DeletePersistentVolumeClaim ( c , pvc . Name , ns ) , "Failed to delete PVC " , pvc . Name )
} ( )
2017-02-14 21:46:03 +00:00
// The claim should timeout phase:Pending
2018-04-16 08:32:48 +00:00
err = framework . WaitForPersistentVolumeClaimPhase ( v1 . ClaimBound , c , ns , pvc . Name , 2 * time . Second , framework . ClaimProvisionShortTimeout )
2017-02-14 21:46:03 +00:00
Expect ( err ) . To ( HaveOccurred ( ) )
framework . Logf ( err . Error ( ) )
} )
2017-03-13 14:41:53 +00:00
2017-07-12 01:38:01 +00:00
It ( "should test that deleting a claim before the volume is provisioned deletes the volume." , func ( ) {
2017-03-13 14:41:53 +00:00
// This case tests for the regressions of a bug fixed by PR #21268
// REGRESSION: Deleting the PVC before the PV is provisioned can result in the PV
// not being deleted.
// NOTE: Polls until no PVs are detected, times out at 5 minutes.
2017-04-28 12:37:50 +00:00
framework . SkipUnlessProviderIs ( "openstack" , "gce" , "aws" , "gke" , "vsphere" , "azure" )
2017-03-13 14:41:53 +00:00
const raceAttempts int = 100
var residualPVs [ ] * v1 . PersistentVolume
2017-04-05 09:49:49 +00:00
By ( fmt . Sprintf ( "Creating and deleting PersistentVolumeClaims %d times" , raceAttempts ) )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "deletion race" ,
Provisioner : "" , // Use a native one based on current cloud provider
ClaimSize : "1Gi" ,
2017-04-05 09:49:49 +00:00
}
class := newStorageClass ( test , ns , "race" )
2017-04-07 11:17:47 +00:00
class , err := c . StorageV1 ( ) . StorageClasses ( ) . Create ( class )
2017-03-13 14:41:53 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer deleteStorageClass ( c , class . Name )
// To increase chance of detection, attempt multiple iterations
for i := 0 ; i < raceAttempts ; i ++ {
2017-04-05 09:49:49 +00:00
suffix := fmt . Sprintf ( "race-%d" , i )
claim := newClaim ( test , ns , suffix )
claim . Spec . StorageClassName = & class . Name
2017-04-01 04:42:52 +00:00
tmpClaim , err := framework . CreatePVC ( c , ns , claim )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
framework . ExpectNoError ( framework . DeletePersistentVolumeClaim ( c , tmpClaim . Name , ns ) )
2017-03-13 14:41:53 +00:00
}
By ( fmt . Sprintf ( "Checking for residual PersistentVolumes associated with StorageClass %s" , class . Name ) )
residualPVs , err = waitForProvisionedVolumesDeleted ( c , class . Name )
2017-04-01 04:42:52 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
// Cleanup the test resources before breaking
defer deleteProvisionedVolumesAndDisks ( c , residualPVs )
2017-03-13 14:41:53 +00:00
// Report indicators of regression
if len ( residualPVs ) > 0 {
framework . Logf ( "Remaining PersistentVolumes:" )
for i , pv := range residualPVs {
2017-04-05 09:49:49 +00:00
framework . Logf ( "\t%d) %s" , i + 1 , pv . Name )
2017-03-13 14:41:53 +00:00
}
framework . Failf ( "Expected 0 PersistentVolumes remaining. Found %d" , len ( residualPVs ) )
}
framework . Logf ( "0 PersistentVolumes remain." )
} )
2018-02-27 11:26:26 +00:00
It ( "deletion should be idempotent" , func ( ) {
// This test ensures that deletion of a volume is idempotent.
// It creates a PV with Retain policy, deletes underlying AWS / GCE
// volume and changes the reclaim policy to Delete.
// PV controller should delete the PV even though the underlying volume
// is already deleted.
framework . SkipUnlessProviderIs ( "gce" , "gke" , "aws" )
By ( "creating PD" )
diskName , err := framework . CreatePDWithRetry ( )
framework . ExpectNoError ( err )
By ( "creating PV" )
pv := & v1 . PersistentVolume {
ObjectMeta : metav1 . ObjectMeta {
GenerateName : "volume-idempotent-delete-" ,
} ,
Spec : v1 . PersistentVolumeSpec {
// Use Retain to keep the PV, the test will change it to Delete
// when the time comes.
PersistentVolumeReclaimPolicy : v1 . PersistentVolumeReclaimRetain ,
AccessModes : [ ] v1 . PersistentVolumeAccessMode {
v1 . ReadWriteOnce ,
} ,
Capacity : v1 . ResourceList {
v1 . ResourceName ( v1 . ResourceStorage ) : resource . MustParse ( "1Gi" ) ,
} ,
// PV is bound to non-existing PVC, so it's reclaim policy is
// executed immediately
ClaimRef : & v1 . ObjectReference {
Kind : "PersistentVolumeClaim" ,
APIVersion : "v1" ,
UID : types . UID ( "01234567890" ) ,
Namespace : ns ,
Name : "dummy-claim-name" ,
} ,
} ,
}
switch framework . TestContext . Provider {
case "aws" :
pv . Spec . PersistentVolumeSource = v1 . PersistentVolumeSource {
AWSElasticBlockStore : & v1 . AWSElasticBlockStoreVolumeSource {
VolumeID : diskName ,
} ,
}
case "gce" , "gke" :
pv . Spec . PersistentVolumeSource = v1 . PersistentVolumeSource {
GCEPersistentDisk : & v1 . GCEPersistentDiskVolumeSource {
PDName : diskName ,
} ,
}
}
pv , err = c . CoreV1 ( ) . PersistentVolumes ( ) . Create ( pv )
framework . ExpectNoError ( err )
By ( "waiting for the PV to get Released" )
err = framework . WaitForPersistentVolumePhase ( v1 . VolumeReleased , c , pv . Name , 2 * time . Second , framework . PVReclaimingTimeout )
framework . ExpectNoError ( err )
By ( "deleting the PD" )
err = framework . DeletePVSource ( & pv . Spec . PersistentVolumeSource )
framework . ExpectNoError ( err )
By ( "changing the PV reclaim policy" )
pv , err = c . CoreV1 ( ) . PersistentVolumes ( ) . Get ( pv . Name , metav1 . GetOptions { } )
framework . ExpectNoError ( err )
pv . Spec . PersistentVolumeReclaimPolicy = v1 . PersistentVolumeReclaimDelete
pv , err = c . CoreV1 ( ) . PersistentVolumes ( ) . Update ( pv )
framework . ExpectNoError ( err )
By ( "waiting for the PV to get deleted" )
err = framework . WaitForPersistentVolumeDeleted ( c , pv . Name , 5 * time . Second , framework . PVDeletingTimeout )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} )
2016-08-18 08:36:50 +00:00
} )
2016-02-29 13:08:05 +00:00
2017-07-18 23:34:47 +00:00
Describe ( "DynamicProvisioner External" , func ( ) {
2017-07-12 01:38:01 +00:00
It ( "should let an external dynamic provisioner create and delete persistent volumes [Slow]" , func ( ) {
2017-02-09 19:11:26 +00:00
// external dynamic provisioner pods need additional permissions provided by the
2018-08-31 13:44:42 +00:00
// persistent-volume-provisioner clusterrole and a leader-locking role
serviceAccountName := "default"
subject := rbacv1beta1 . Subject {
Kind : rbacv1beta1 . ServiceAccountKind ,
Namespace : ns ,
Name : serviceAccountName ,
}
framework . BindClusterRole ( c . RbacV1beta1 ( ) , "system:persistent-volume-provisioner" , ns , subject )
roleName := "leader-locking-nfs-provisioner"
_ , err := f . ClientSet . RbacV1beta1 ( ) . Roles ( ns ) . Create ( & rbacv1beta1 . Role {
ObjectMeta : metav1 . ObjectMeta {
Name : roleName ,
} ,
Rules : [ ] rbacv1beta1 . PolicyRule { {
APIGroups : [ ] string { "" } ,
Resources : [ ] string { "endpoints" } ,
Verbs : [ ] string { "get" , "list" , "watch" , "create" , "update" , "patch" } ,
} } ,
} )
framework . ExpectNoError ( err , "Failed to create leader-locking role" )
2017-02-09 19:11:26 +00:00
2018-08-31 13:44:42 +00:00
framework . BindRoleInNamespace ( c . RbacV1beta1 ( ) , roleName , ns , subject )
err = framework . WaitForAuthorizationUpdate ( c . AuthorizationV1beta1 ( ) ,
serviceaccount . MakeUsername ( ns , serviceAccountName ) ,
2017-02-09 19:11:26 +00:00
"" , "get" , schema . GroupResource { Group : "storage.k8s.io" , Resource : "storageclasses" } , true )
2018-08-31 13:44:42 +00:00
framework . ExpectNoError ( err , "Failed to update authorization" )
2017-02-09 19:11:26 +00:00
By ( "creating an external dynamic provisioner pod" )
2018-08-31 13:44:42 +00:00
pod := utils . StartExternalProvisioner ( c , ns , externalPluginName )
2017-03-13 14:41:53 +00:00
defer framework . DeletePodOrFail ( c , ns , pod . Name )
2017-02-09 19:11:26 +00:00
By ( "creating a StorageClass" )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "external provisioner test" ,
Provisioner : externalPluginName ,
ClaimSize : "1500Mi" ,
ExpectedSize : "1500Mi" ,
2017-04-05 09:49:49 +00:00
}
class := newStorageClass ( test , ns , "external" )
claim := newClaim ( test , ns , "external" )
2018-04-10 07:29:08 +00:00
claim . Spec . StorageClassName = & ( class . Name )
2017-02-09 19:11:26 +00:00
2017-04-05 09:49:49 +00:00
By ( "creating a claim with a external provisioning annotation" )
2018-10-22 16:04:43 +00:00
testsuites . TestDynamicProvisioning ( test , c , claim , class )
2016-02-29 13:08:05 +00:00
} )
} )
2017-03-01 01:09:21 +00:00
2017-07-18 23:34:47 +00:00
Describe ( "DynamicProvisioner Default" , func ( ) {
2017-07-12 01:38:01 +00:00
It ( "should create and delete default persistent volumes [Slow]" , func ( ) {
2017-03-01 01:09:21 +00:00
framework . SkipUnlessProviderIs ( "openstack" , "gce" , "aws" , "gke" , "vsphere" , "azure" )
By ( "creating a claim with no annotation" )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "default" ,
ClaimSize : "2Gi" ,
ExpectedSize : "2Gi" ,
2017-04-05 09:49:49 +00:00
}
2017-11-30 01:28:13 +00:00
2017-04-05 09:49:49 +00:00
claim := newClaim ( test , ns , "default" )
2018-10-22 16:04:43 +00:00
testsuites . TestDynamicProvisioning ( test , c , claim , nil )
2017-03-01 01:09:21 +00:00
} )
// Modifying the default storage class can be disruptive to other tests that depend on it
2018-04-16 08:32:48 +00:00
It ( "should be disabled by changing the default annotation [Serial] [Disruptive]" , func ( ) {
2017-04-28 12:37:50 +00:00
framework . SkipUnlessProviderIs ( "openstack" , "gce" , "aws" , "gke" , "vsphere" , "azure" )
2017-03-17 09:41:11 +00:00
scName := getDefaultStorageClassName ( c )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "default" ,
ClaimSize : "2Gi" ,
2017-04-05 09:49:49 +00:00
}
2017-03-01 01:09:21 +00:00
By ( "setting the is-default StorageClass annotation to false" )
2017-03-17 09:41:11 +00:00
verifyDefaultStorageClass ( c , scName , true )
defer updateDefaultStorageClass ( c , scName , "true" )
updateDefaultStorageClass ( c , scName , "false" )
2017-03-01 01:09:21 +00:00
By ( "creating a claim with default storageclass and expecting it to timeout" )
2017-04-05 09:49:49 +00:00
claim := newClaim ( test , ns , "default" )
2017-04-07 11:17:47 +00:00
claim , err := c . CoreV1 ( ) . PersistentVolumeClaims ( ns ) . Create ( claim )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-03-13 14:41:53 +00:00
defer func ( ) {
2017-04-01 04:42:52 +00:00
framework . ExpectNoError ( framework . DeletePersistentVolumeClaim ( c , claim . Name , ns ) )
2017-03-13 14:41:53 +00:00
} ( )
2017-03-01 01:09:21 +00:00
// The claim should timeout phase:Pending
2018-04-16 08:32:48 +00:00
err = framework . WaitForPersistentVolumeClaimPhase ( v1 . ClaimBound , c , ns , claim . Name , 2 * time . Second , framework . ClaimProvisionShortTimeout )
2017-03-01 01:09:21 +00:00
Expect ( err ) . To ( HaveOccurred ( ) )
framework . Logf ( err . Error ( ) )
2017-04-07 11:17:47 +00:00
claim , err = c . CoreV1 ( ) . PersistentVolumeClaims ( ns ) . Get ( claim . Name , metav1 . GetOptions { } )
2017-03-01 01:09:21 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
Expect ( claim . Status . Phase ) . To ( Equal ( v1 . ClaimPending ) )
} )
// Modifying the default storage class can be disruptive to other tests that depend on it
2018-04-16 08:32:48 +00:00
It ( "should be disabled by removing the default annotation [Serial] [Disruptive]" , func ( ) {
2017-04-28 12:37:50 +00:00
framework . SkipUnlessProviderIs ( "openstack" , "gce" , "aws" , "gke" , "vsphere" , "azure" )
2017-03-17 09:41:11 +00:00
scName := getDefaultStorageClassName ( c )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "default" ,
ClaimSize : "2Gi" ,
2017-04-05 09:49:49 +00:00
}
2017-03-01 01:09:21 +00:00
By ( "removing the is-default StorageClass annotation" )
2017-03-17 09:41:11 +00:00
verifyDefaultStorageClass ( c , scName , true )
defer updateDefaultStorageClass ( c , scName , "true" )
updateDefaultStorageClass ( c , scName , "" )
2017-03-01 01:09:21 +00:00
By ( "creating a claim with default storageclass and expecting it to timeout" )
2017-04-05 09:49:49 +00:00
claim := newClaim ( test , ns , "default" )
2017-04-07 11:17:47 +00:00
claim , err := c . CoreV1 ( ) . PersistentVolumeClaims ( ns ) . Create ( claim )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2017-03-13 14:41:53 +00:00
defer func ( ) {
2017-04-01 04:42:52 +00:00
framework . ExpectNoError ( framework . DeletePersistentVolumeClaim ( c , claim . Name , ns ) )
2017-03-13 14:41:53 +00:00
} ( )
2017-03-01 01:09:21 +00:00
// The claim should timeout phase:Pending
2018-04-16 08:32:48 +00:00
err = framework . WaitForPersistentVolumeClaimPhase ( v1 . ClaimBound , c , ns , claim . Name , 2 * time . Second , framework . ClaimProvisionShortTimeout )
2017-03-01 01:09:21 +00:00
Expect ( err ) . To ( HaveOccurred ( ) )
framework . Logf ( err . Error ( ) )
2017-04-07 11:17:47 +00:00
claim , err = c . CoreV1 ( ) . PersistentVolumeClaims ( ns ) . Get ( claim . Name , metav1 . GetOptions { } )
2017-03-01 01:09:21 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
Expect ( claim . Status . Phase ) . To ( Equal ( v1 . ClaimPending ) )
} )
} )
2018-04-19 20:14:13 +00:00
framework . KubeDescribe ( "GlusterDynamicProvisioner" , func ( ) {
It ( "should create and delete persistent volumes [fast]" , func ( ) {
By ( "creating a Gluster DP server Pod" )
pod := startGlusterDpServerPod ( c , ns )
serverUrl := "https://" + pod . Status . PodIP + ":8081"
By ( "creating a StorageClass" )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "Gluster Dynamic provisioner test" ,
Provisioner : "kubernetes.io/glusterfs" ,
ClaimSize : "2Gi" ,
ExpectedSize : "2Gi" ,
Parameters : map [ string ] string { "resturl" : serverUrl } ,
SkipWriteReadCheck : true ,
2018-04-19 20:14:13 +00:00
}
suffix := fmt . Sprintf ( "glusterdptest" )
class := newStorageClass ( test , ns , suffix )
By ( "creating a claim object with a suffix for gluster dynamic provisioner" )
claim := newClaim ( test , ns , suffix )
2018-10-22 16:04:43 +00:00
testsuites . TestDynamicProvisioning ( test , c , claim , class )
2018-04-19 20:14:13 +00:00
} )
} )
2018-05-29 14:24:18 +00:00
2018-06-19 08:11:47 +00:00
Describe ( "Invalid AWS KMS key" , func ( ) {
It ( "should report an error and create no PV" , func ( ) {
framework . SkipUnlessProviderIs ( "aws" )
2018-10-22 16:04:43 +00:00
test := testsuites . StorageClassTest {
Name : "AWS EBS with invalid KMS key" ,
Provisioner : "kubernetes.io/aws-ebs" ,
ClaimSize : "2Gi" ,
Parameters : map [ string ] string { "kmsKeyId" : "arn:aws:kms:us-east-1:123456789012:key/55555555-5555-5555-5555-555555555555" } ,
2018-06-19 08:11:47 +00:00
}
By ( "creating a StorageClass" )
suffix := fmt . Sprintf ( "invalid-aws" )
class := newStorageClass ( test , ns , suffix )
class , err := c . StorageV1 ( ) . StorageClasses ( ) . Create ( class )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer func ( ) {
framework . Logf ( "deleting storage class %s" , class . Name )
framework . ExpectNoError ( c . StorageV1 ( ) . StorageClasses ( ) . Delete ( class . Name , nil ) )
} ( )
By ( "creating a claim object with a suffix for gluster dynamic provisioner" )
claim := newClaim ( test , ns , suffix )
claim . Spec . StorageClassName = & class . Name
claim , err = c . CoreV1 ( ) . PersistentVolumeClaims ( claim . Namespace ) . Create ( claim )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer func ( ) {
framework . Logf ( "deleting claim %q/%q" , claim . Namespace , claim . Name )
err = c . CoreV1 ( ) . PersistentVolumeClaims ( claim . Namespace ) . Delete ( claim . Name , nil )
if err != nil && ! apierrs . IsNotFound ( err ) {
framework . Failf ( "Error deleting claim %q. Error: %v" , claim . Name , err )
}
} ( )
// Watch events until the message about invalid key appears
err = wait . Poll ( time . Second , framework . ClaimProvisionTimeout , func ( ) ( bool , error ) {
events , err := c . CoreV1 ( ) . Events ( claim . Namespace ) . List ( metav1 . ListOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
for _ , event := range events . Items {
if strings . Contains ( event . Message , "failed to create encrypted volume: the volume disappeared after creation, most likely due to inaccessible KMS encryption key" ) {
return true , nil
}
}
return false , nil
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} )
} )
2018-08-29 20:43:53 +00:00
Describe ( "DynamicProvisioner delayed binding [Slow]" , func ( ) {
2018-08-28 19:35:43 +00:00
It ( "should create a persistent volume in the same zone as node after a pod mounting the claim is started" , func ( ) {
2018-10-22 16:04:43 +00:00
tests := [ ] testsuites . StorageClassTest {
2018-08-28 19:35:43 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "Delayed binding EBS storage class test" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
ClaimSize : "2Gi" ,
DelayBinding : true ,
2018-08-28 19:35:43 +00:00
} ,
{
2018-10-22 16:04:43 +00:00
Name : "Delayed binding GCE PD storage class test" ,
CloudProviders : [ ] string { "gce" , "gke" } ,
Provisioner : "kubernetes.io/gce-pd" ,
ClaimSize : "2Gi" ,
DelayBinding : true ,
2018-08-28 19:35:43 +00:00
} ,
2018-08-10 23:33:46 +00:00
}
2018-08-28 19:35:43 +00:00
for _ , test := range tests {
2018-10-22 16:04:43 +00:00
if ! framework . ProviderIs ( test . CloudProviders ... ) {
framework . Logf ( "Skipping %q: cloud providers is not %v" , test . Name , test . CloudProviders )
2018-08-28 19:35:43 +00:00
continue
}
By ( "creating a claim with class with waitForFirstConsumer" )
suffix := "delayed"
class := newStorageClass ( test , ns , suffix )
claim := newClaim ( test , ns , suffix )
claim . Spec . StorageClassName = & class . Name
pv , node := testBindingWaitForFirstConsumer ( c , claim , class )
if node == nil {
framework . Failf ( "unexpected nil node found" )
}
zone , ok := node . Labels [ kubeletapis . LabelZoneFailureDomain ]
if ! ok {
framework . Failf ( "label %s not found on Node" , kubeletapis . LabelZoneFailureDomain )
}
checkZoneFromLabelAndAffinity ( pv , zone , true )
2018-08-10 23:33:46 +00:00
}
} )
} )
2018-08-29 20:43:53 +00:00
Describe ( "DynamicProvisioner allowedTopologies" , func ( ) {
2018-08-28 19:35:43 +00:00
It ( "should create persistent volume in the zone specified in allowedTopologies of storageclass" , func ( ) {
2018-10-22 16:04:43 +00:00
tests := [ ] testsuites . StorageClassTest {
2018-08-28 19:35:43 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "AllowedTopologies EBS storage class test" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
ClaimSize : "2Gi" ,
ExpectedSize : "2Gi" ,
2018-08-28 19:35:43 +00:00
} ,
{
2018-10-22 16:04:43 +00:00
Name : "AllowedTopologies GCE PD storage class test" ,
CloudProviders : [ ] string { "gce" , "gke" } ,
Provisioner : "kubernetes.io/gce-pd" ,
ClaimSize : "2Gi" ,
ExpectedSize : "2Gi" ,
2018-08-28 19:35:43 +00:00
} ,
}
for _ , test := range tests {
2018-10-22 16:04:43 +00:00
if ! framework . ProviderIs ( test . CloudProviders ... ) {
framework . Logf ( "Skipping %q: cloud providers is not %v" , test . Name , test . CloudProviders )
2018-08-28 19:35:43 +00:00
continue
}
By ( "creating a claim with class with allowedTopologies set" )
suffix := "topology"
class := newStorageClass ( test , ns , suffix )
zone := getRandomCloudZone ( c )
addSingleZoneAllowedTopologyToStorageClass ( c , class , zone )
claim := newClaim ( test , ns , suffix )
claim . Spec . StorageClassName = & class . Name
2018-10-22 16:04:43 +00:00
pv := testsuites . TestDynamicProvisioning ( test , c , claim , class )
2018-08-28 19:35:43 +00:00
checkZoneFromLabelAndAffinity ( pv , zone , true )
2018-08-10 23:33:46 +00:00
}
} )
} )
2018-08-29 20:43:53 +00:00
Describe ( "DynamicProvisioner delayed binding with allowedTopologies [Slow]" , func ( ) {
2018-08-28 19:35:43 +00:00
It ( "should create persistent volume in the same zone as specified in allowedTopologies after a pod mounting the claim is started" , func ( ) {
2018-10-22 16:04:43 +00:00
tests := [ ] testsuites . StorageClassTest {
2018-08-28 19:35:43 +00:00
{
2018-10-22 16:04:43 +00:00
Name : "AllowedTopologies and delayed binding EBS storage class test" ,
CloudProviders : [ ] string { "aws" } ,
Provisioner : "kubernetes.io/aws-ebs" ,
ClaimSize : "2Gi" ,
DelayBinding : true ,
2018-08-28 19:35:43 +00:00
} ,
{
2018-10-22 16:04:43 +00:00
Name : "AllowedTopologies and delayed binding GCE PD storage class test" ,
CloudProviders : [ ] string { "gce" , "gke" } ,
Provisioner : "kubernetes.io/gce-pd" ,
ClaimSize : "2Gi" ,
DelayBinding : true ,
2018-08-28 19:35:43 +00:00
} ,
2018-08-10 23:33:46 +00:00
}
2018-08-28 19:35:43 +00:00
for _ , test := range tests {
2018-10-22 16:04:43 +00:00
if ! framework . ProviderIs ( test . CloudProviders ... ) {
framework . Logf ( "Skipping %q: cloud providers is not %v" , test . Name , test . CloudProviders )
2018-08-28 19:35:43 +00:00
continue
}
By ( "creating a claim with class with WaitForFirstConsumer and allowedTopologies" )
suffix := "delayed-topo"
class := newStorageClass ( test , ns , suffix )
topoZone := getRandomCloudZone ( c )
addSingleZoneAllowedTopologyToStorageClass ( c , class , topoZone )
claim := newClaim ( test , ns , suffix )
claim . Spec . StorageClassName = & class . Name
pv , node := testBindingWaitForFirstConsumer ( c , claim , class )
if node == nil {
framework . Failf ( "unexpected nil node found" )
}
nodeZone , ok := node . Labels [ kubeletapis . LabelZoneFailureDomain ]
if ! ok {
framework . Failf ( "label %s not found on Node" , kubeletapis . LabelZoneFailureDomain )
}
if topoZone != nodeZone {
framework . Failf ( "zone specified in allowedTopologies: %s does not match zone of node where PV got provisioned: %s" , topoZone , nodeZone )
}
checkZoneFromLabelAndAffinity ( pv , topoZone , true )
2018-08-10 23:33:46 +00:00
}
} )
} )
2016-02-29 13:08:05 +00:00
} )
2017-03-17 09:41:11 +00:00
func getDefaultStorageClassName ( c clientset . Interface ) string {
list , err := c . StorageV1 ( ) . StorageClasses ( ) . List ( metav1 . ListOptions { } )
if err != nil {
framework . Failf ( "Error listing storage classes: %v" , err )
}
var scName string
for _ , sc := range list . Items {
if storageutil . IsDefaultAnnotation ( sc . ObjectMeta ) {
if len ( scName ) != 0 {
framework . Failf ( "Multiple default storage classes found: %q and %q" , scName , sc . Name )
}
scName = sc . Name
}
}
if len ( scName ) == 0 {
framework . Failf ( "No default storage class found" )
}
framework . Logf ( "Default storage class: %q" , scName )
return scName
}
func verifyDefaultStorageClass ( c clientset . Interface , scName string , expectedDefault bool ) {
sc , err := c . StorageV1 ( ) . StorageClasses ( ) . Get ( scName , metav1 . GetOptions { } )
2017-03-01 01:09:21 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
Expect ( storageutil . IsDefaultAnnotation ( sc . ObjectMeta ) ) . To ( Equal ( expectedDefault ) )
}
2017-03-17 09:41:11 +00:00
func updateDefaultStorageClass ( c clientset . Interface , scName string , defaultStr string ) {
sc , err := c . StorageV1 ( ) . StorageClasses ( ) . Get ( scName , metav1 . GetOptions { } )
2017-03-01 01:09:21 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if defaultStr == "" {
2017-03-15 18:44:14 +00:00
delete ( sc . Annotations , storageutil . BetaIsDefaultStorageClassAnnotation )
2017-03-17 13:08:39 +00:00
delete ( sc . Annotations , storageutil . IsDefaultStorageClassAnnotation )
2017-03-01 01:09:21 +00:00
} else {
if sc . Annotations == nil {
sc . Annotations = make ( map [ string ] string )
}
2017-03-15 18:44:14 +00:00
sc . Annotations [ storageutil . BetaIsDefaultStorageClassAnnotation ] = defaultStr
2017-03-17 13:08:39 +00:00
sc . Annotations [ storageutil . IsDefaultStorageClassAnnotation ] = defaultStr
2017-03-01 01:09:21 +00:00
}
sc , err = c . StorageV1 ( ) . StorageClasses ( ) . Update ( sc )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
expectedDefault := false
if defaultStr == "true" {
expectedDefault = true
}
2017-03-17 09:41:11 +00:00
verifyDefaultStorageClass ( c , scName , expectedDefault )
2017-03-01 01:09:21 +00:00
}
2018-08-29 14:39:01 +00:00
func getClaim ( claimSize string , ns string ) * v1 . PersistentVolumeClaim {
2016-11-18 20:55:17 +00:00
claim := v1 . PersistentVolumeClaim {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-02-29 13:08:05 +00:00
GenerateName : "pvc-" ,
Namespace : ns ,
} ,
2016-11-18 20:55:17 +00:00
Spec : v1 . PersistentVolumeClaimSpec {
AccessModes : [ ] v1 . PersistentVolumeAccessMode {
v1 . ReadWriteOnce ,
2016-02-29 13:08:05 +00:00
} ,
2016-11-18 20:55:17 +00:00
Resources : v1 . ResourceRequirements {
Requests : v1 . ResourceList {
2018-08-29 14:39:01 +00:00
v1 . ResourceName ( v1 . ResourceStorage ) : resource . MustParse ( claimSize ) ,
2016-02-29 13:08:05 +00:00
} ,
} ,
} ,
}
2016-08-18 08:36:50 +00:00
return & claim
2016-02-29 13:08:05 +00:00
}
2018-10-22 16:04:43 +00:00
func newClaim ( t testsuites . StorageClassTest , ns , suffix string ) * v1 . PersistentVolumeClaim {
return getClaim ( t . ClaimSize , ns )
2016-02-29 13:08:05 +00:00
}
2016-08-18 08:36:49 +00:00
2017-03-17 13:08:39 +00:00
func getDefaultPluginName ( ) string {
switch {
case framework . ProviderIs ( "gke" ) , framework . ProviderIs ( "gce" ) :
return "kubernetes.io/gce-pd"
case framework . ProviderIs ( "aws" ) :
return "kubernetes.io/aws-ebs"
case framework . ProviderIs ( "openstack" ) :
return "kubernetes.io/cinder"
case framework . ProviderIs ( "vsphere" ) :
return "kubernetes.io/vsphere-volume"
2017-04-28 12:37:50 +00:00
case framework . ProviderIs ( "azure" ) :
return "kubernetes.io/azure-disk"
2017-03-17 13:08:39 +00:00
}
return ""
}
2018-08-10 23:33:46 +00:00
func addSingleZoneAllowedTopologyToStorageClass ( c clientset . Interface , sc * storage . StorageClass , zone string ) {
term := v1 . TopologySelectorTerm {
MatchLabelExpressions : [ ] v1 . TopologySelectorLabelRequirement {
{
Key : kubeletapis . LabelZoneFailureDomain ,
Values : [ ] string { zone } ,
} ,
} ,
}
sc . AllowedTopologies = append ( sc . AllowedTopologies , term )
}
2018-10-22 16:04:43 +00:00
func newStorageClass ( t testsuites . StorageClassTest , ns string , suffix string ) * storage . StorageClass {
pluginName := t . Provisioner
2017-02-09 19:11:26 +00:00
if pluginName == "" {
2017-03-17 13:08:39 +00:00
pluginName = getDefaultPluginName ( )
2016-10-17 17:32:27 +00:00
}
2017-03-13 14:41:53 +00:00
if suffix == "" {
suffix = "sc"
}
2018-08-10 23:33:46 +00:00
bindingMode := storage . VolumeBindingImmediate
2018-10-22 16:04:43 +00:00
if t . DelayBinding {
2018-08-10 23:33:46 +00:00
bindingMode = storage . VolumeBindingWaitForFirstConsumer
}
2018-10-22 16:04:43 +00:00
return getStorageClass ( pluginName , t . Parameters , & bindingMode , ns , suffix )
2018-08-29 14:39:01 +00:00
}
func getStorageClass (
provisioner string ,
parameters map [ string ] string ,
bindingMode * storage . VolumeBindingMode ,
ns string ,
suffix string ,
) * storage . StorageClass {
if bindingMode == nil {
defaultBindingMode := storage . VolumeBindingImmediate
bindingMode = & defaultBindingMode
}
2016-09-01 15:29:26 +00:00
return & storage . StorageClass {
2016-12-03 18:57:26 +00:00
TypeMeta : metav1 . TypeMeta {
2016-08-18 08:36:49 +00:00
Kind : "StorageClass" ,
} ,
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2017-04-05 09:49:49 +00:00
// Name must be unique, so let's base it on namespace name
Name : ns + "-" + suffix ,
2017-03-17 13:08:39 +00:00
} ,
2018-08-29 14:39:01 +00:00
Provisioner : provisioner ,
Parameters : parameters ,
VolumeBindingMode : bindingMode ,
2017-03-17 13:08:39 +00:00
}
}
2018-04-10 07:29:08 +00:00
// TODO: remove when storage.k8s.io/v1beta1 is removed.
2018-10-22 16:04:43 +00:00
func newBetaStorageClass ( t testsuites . StorageClassTest , suffix string ) * storagebeta . StorageClass {
pluginName := t . Provisioner
2017-04-05 09:49:49 +00:00
2017-03-17 13:08:39 +00:00
if pluginName == "" {
pluginName = getDefaultPluginName ( )
}
2017-03-13 14:41:53 +00:00
if suffix == "" {
suffix = "default"
}
2017-03-17 13:08:39 +00:00
return & storagebeta . StorageClass {
TypeMeta : metav1 . TypeMeta {
Kind : "StorageClass" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
2017-03-13 14:41:53 +00:00
GenerateName : suffix + "-" ,
2016-08-18 08:36:49 +00:00
} ,
2016-10-17 17:32:27 +00:00
Provisioner : pluginName ,
2018-10-22 16:04:43 +00:00
Parameters : t . Parameters ,
2016-08-18 08:36:49 +00:00
}
}
2017-02-09 19:11:26 +00:00
2018-04-19 20:14:13 +00:00
func startGlusterDpServerPod ( c clientset . Interface , ns string ) * v1 . Pod {
podClient := c . CoreV1 ( ) . Pods ( ns )
provisionerPod := & v1 . Pod {
TypeMeta : metav1 . TypeMeta {
Kind : "Pod" ,
APIVersion : "v1" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
GenerateName : "glusterdynamic-provisioner-" ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "glusterdynamic-provisioner" ,
Image : "docker.io/humblec/glusterdynamic-provisioner:v1.0" ,
Args : [ ] string {
"-config=" + "/etc/heketi/heketi.json" ,
} ,
Ports : [ ] v1 . ContainerPort {
{ Name : "heketi" , ContainerPort : 8081 } ,
} ,
Env : [ ] v1 . EnvVar {
{
Name : "POD_IP" ,
ValueFrom : & v1 . EnvVarSource {
FieldRef : & v1 . ObjectFieldSelector {
FieldPath : "status.podIP" ,
} ,
} ,
} ,
} ,
ImagePullPolicy : v1 . PullIfNotPresent ,
} ,
} ,
} ,
}
provisionerPod , err := podClient . Create ( provisionerPod )
framework . ExpectNoError ( err , "Failed to create %s pod: %v" , provisionerPod . Name , err )
framework . ExpectNoError ( framework . WaitForPodRunningInNamespace ( c , provisionerPod ) )
By ( "locating the provisioner pod" )
pod , err := podClient . Get ( provisionerPod . Name , metav1 . GetOptions { } )
framework . ExpectNoError ( err , "Cannot locate the provisioner pod %v: %v" , provisionerPod . Name , err )
return pod
}
2017-03-13 14:41:53 +00:00
// waitForProvisionedVolumesDelete is a polling wrapper to scan all PersistentVolumes for any associated to the test's
// StorageClass. Returns either an error and nil values or the remaining PVs and their count.
func waitForProvisionedVolumesDeleted ( c clientset . Interface , scName string ) ( [ ] * v1 . PersistentVolume , error ) {
var remainingPVs [ ] * v1 . PersistentVolume
err := wait . Poll ( 10 * time . Second , 300 * time . Second , func ( ) ( bool , error ) {
remainingPVs = [ ] * v1 . PersistentVolume { }
2017-04-07 11:17:47 +00:00
allPVs , err := c . CoreV1 ( ) . PersistentVolumes ( ) . List ( metav1 . ListOptions { } )
2017-03-13 14:41:53 +00:00
if err != nil {
return true , err
}
for _ , pv := range allPVs . Items {
2018-04-10 07:29:08 +00:00
if pv . Spec . StorageClassName == scName {
2017-03-13 14:41:53 +00:00
remainingPVs = append ( remainingPVs , & pv )
}
}
if len ( remainingPVs ) > 0 {
return false , nil // Poll until no PVs remain
} else {
return true , nil // No PVs remain
}
} )
return remainingPVs , err
}
// deleteStorageClass deletes the passed in StorageClass and catches errors other than "Not Found"
func deleteStorageClass ( c clientset . Interface , className string ) {
2017-04-07 11:17:47 +00:00
err := c . StorageV1 ( ) . StorageClasses ( ) . Delete ( className , nil )
2017-03-13 14:41:53 +00:00
if err != nil && ! apierrs . IsNotFound ( err ) {
Expect ( err ) . NotTo ( HaveOccurred ( ) )
}
}
// deleteProvisionedVolumes [gce||gke only] iteratively deletes persistent volumes and attached GCE PDs.
func deleteProvisionedVolumesAndDisks ( c clientset . Interface , pvs [ ] * v1 . PersistentVolume ) {
for _ , pv := range pvs {
2017-04-01 04:42:52 +00:00
framework . ExpectNoError ( framework . DeletePDWithRetry ( pv . Spec . PersistentVolumeSource . GCEPersistentDisk . PDName ) )
framework . ExpectNoError ( framework . DeletePersistentVolume ( c , pv . Name ) )
2017-03-13 14:41:53 +00:00
}
}
2017-04-07 11:17:47 +00:00
func getRandomCloudZone ( c clientset . Interface ) string {
2018-02-16 19:35:11 +00:00
zones , err := framework . GetClusterZones ( c )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
2017-04-07 11:17:47 +00:00
// return "" in case that no node has zone label
zone , _ := zones . PopAny ( )
return zone
}