1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package storageos 18 19import ( 20 "context" 21 "fmt" 22 "os" 23 "path/filepath" 24 "testing" 25 26 "k8s.io/mount-utils" 27 "k8s.io/utils/exec/testing" 28 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/client-go/kubernetes/fake" 33 utiltesting "k8s.io/client-go/util/testing" 34 "k8s.io/kubernetes/pkg/volume" 35 volumetest "k8s.io/kubernetes/pkg/volume/testing" 36) 37 38func TestCanSupport(t *testing.T) { 39 tmpDir, err := utiltesting.MkTmpdir("storageos_test") 40 if err != nil { 41 t.Fatalf("error creating temp dir: %v", err) 42 } 43 defer os.RemoveAll(tmpDir) 44 plugMgr := volume.VolumePluginMgr{} 45 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) 46 47 plug, err := plugMgr.FindPluginByName("kubernetes.io/storageos") 48 if err != nil { 49 t.Fatal("Can't find the plugin by name") 50 } 51 if plug.GetPluginName() != "kubernetes.io/storageos" { 52 t.Errorf("Wrong name: %s", plug.GetPluginName()) 53 } 54 if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{StorageOS: &v1.StorageOSVolumeSource{}}}}) { 55 t.Errorf("Expected true") 56 } 57 if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{StorageOS: &v1.StorageOSPersistentVolumeSource{}}}}}) { 58 t.Errorf("Expected true") 59 } 60} 61 62func TestGetAccessModes(t *testing.T) { 63 tmpDir, err := utiltesting.MkTmpdir("storageos_test") 64 if err != nil { 65 t.Fatalf("error creating temp dir: %v", err) 66 } 67 defer os.RemoveAll(tmpDir) 68 plugMgr := volume.VolumePluginMgr{} 69 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) 70 71 plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/storageos") 72 if err != nil { 73 t.Errorf("Can't find the plugin by name") 74 } 75 if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) { 76 t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany) 77 } 78} 79 80type fakePDManager struct { 81 api apiImplementer 82 attachCalled bool 83 attachDeviceCalled bool 84 detachCalled bool 85 mountCalled bool 86 unmountCalled bool 87 createCalled bool 88 deleteCalled bool 89} 90 91func (fake *fakePDManager) NewAPI(apiCfg *storageosAPIConfig) error { 92 fake.api = fakeAPI{} 93 return nil 94} 95 96func (fake *fakePDManager) CreateVolume(p *storageosProvisioner) (*storageosVolume, error) { 97 fake.createCalled = true 98 labels := make(map[string]string) 99 labels["fakepdmanager"] = "yes" 100 return &storageosVolume{ 101 Name: "test-storageos-name", 102 Namespace: "test-storageos-namespace", 103 Pool: "test-storageos-pool", 104 SizeGB: 100, 105 Labels: labels, 106 FSType: "ext2", 107 }, nil 108} 109 110func (fake *fakePDManager) AttachVolume(b *storageosMounter) (string, error) { 111 fake.attachCalled = true 112 return "", nil 113} 114 115func (fake *fakePDManager) AttachDevice(b *storageosMounter, dir string) error { 116 fake.attachDeviceCalled = true 117 return nil 118} 119 120func (fake *fakePDManager) DetachVolume(b *storageosUnmounter, loopDevice string) error { 121 fake.detachCalled = true 122 return nil 123} 124 125func (fake *fakePDManager) MountVolume(b *storageosMounter, mntDevice, deviceMountPath string) error { 126 fake.mountCalled = true 127 return nil 128} 129 130func (fake *fakePDManager) UnmountVolume(b *storageosUnmounter) error { 131 fake.unmountCalled = true 132 return nil 133} 134 135func (fake *fakePDManager) DeleteVolume(d *storageosDeleter) error { 136 fake.deleteCalled = true 137 if d.volName != "test-storageos-name" { 138 return fmt.Errorf("Deleter got unexpected volume name: %s", d.volName) 139 } 140 return nil 141} 142 143func (fake *fakePDManager) DeviceDir(mounter *storageosMounter) string { 144 return defaultDeviceDir 145} 146 147func TestPlugin(t *testing.T) { 148 tmpDir, err := utiltesting.MkTmpdir("storageos_test") 149 if err != nil { 150 t.Fatalf("can't make a temp dir: %v", err) 151 } 152 defer os.RemoveAll(tmpDir) 153 plugMgr := volume.VolumePluginMgr{} 154 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, nil, nil)) 155 156 plug, err := plugMgr.FindPluginByName("kubernetes.io/storageos") 157 if err != nil { 158 t.Errorf("Can't find the plugin by name") 159 } 160 secretName := "very-secret" 161 spec := &v1.Volume{ 162 Name: "vol1-pvname", 163 VolumeSource: v1.VolumeSource{ 164 StorageOS: &v1.StorageOSVolumeSource{ 165 VolumeName: "vol1", 166 VolumeNamespace: "ns1", 167 FSType: "ext3", 168 SecretRef: &v1.LocalObjectReference{ 169 Name: secretName, 170 }, 171 }, 172 }, 173 } 174 175 client := fake.NewSimpleClientset() 176 177 client.CoreV1().Secrets("default").Create(context.TODO(), &v1.Secret{ 178 ObjectMeta: metav1.ObjectMeta{ 179 Name: secretName, 180 Namespace: "default", 181 }, 182 Type: "kubernetes.io/storageos", 183 Data: map[string][]byte{ 184 "apiUsername": []byte("storageos"), 185 "apiPassword": []byte("storageos"), 186 "apiAddr": []byte("tcp://localhost:5705"), 187 }}, metav1.CreateOptions{}) 188 189 plug.(*storageosPlugin).host = volumetest.NewFakeVolumeHost(t, tmpDir, client, nil) 190 191 // Test Mounter 192 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid"), Namespace: "default"}} 193 fakeManager := &fakePDManager{} 194 195 apiCfg, err := parsePodSecret(pod, secretName, plug.(*storageosPlugin).host.GetKubeClient()) 196 if err != nil { 197 t.Errorf("Couldn't get secret from %v/%v", pod.Namespace, secretName) 198 } 199 200 mounter, err := plug.(*storageosPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), pod, apiCfg, fakeManager, mount.NewFakeMounter(nil), &testingexec.FakeExec{}) 201 if err != nil { 202 t.Fatalf("Failed to make a new Mounter: %v", err) 203 } 204 if mounter == nil { 205 t.Fatalf("Got a nil Mounter") 206 } 207 208 expectedPath := filepath.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~storageos/vol1-pvname.ns1.vol1") 209 volPath := mounter.GetPath() 210 if volPath != expectedPath { 211 t.Errorf("Expected path: '%s' got: '%s'", expectedPath, volPath) 212 } 213 214 if err := mounter.SetUp(volume.MounterArgs{}); err != nil { 215 t.Errorf("Expected success, got: %v", err) 216 } 217 if _, err := os.Stat(volPath); err != nil { 218 if os.IsNotExist(err) { 219 t.Errorf("SetUp() failed, volume path not created: %s", volPath) 220 } else { 221 t.Errorf("SetUp() failed: %v", err) 222 } 223 } 224 225 if !fakeManager.attachDeviceCalled { 226 t.Errorf("AttachDevice not called") 227 } 228 if !fakeManager.attachCalled { 229 t.Errorf("Attach not called") 230 } 231 if !fakeManager.mountCalled { 232 t.Errorf("Mount not called") 233 } 234 235 // Test Unmounter 236 fakeManager = &fakePDManager{} 237 unmounter, err := plug.(*storageosPlugin).newUnmounterInternal("vol1-pvname", types.UID("poduid"), fakeManager, mount.NewFakeMounter(nil), &testingexec.FakeExec{}) 238 if err != nil { 239 t.Errorf("Failed to make a new Unmounter: %v", err) 240 } 241 if unmounter == nil { 242 t.Errorf("Got a nil Unmounter") 243 } 244 245 volPath = unmounter.GetPath() 246 if volPath != expectedPath { 247 t.Errorf("Expected path: '%s' got: '%s'", expectedPath, volPath) 248 } 249 250 if err := unmounter.TearDown(); err != nil { 251 t.Errorf("Expected success, got: %v", err) 252 } 253 if _, err := os.Stat(volPath); err == nil { 254 t.Errorf("TearDown() failed, volume path still exists: %s", volPath) 255 } else if !os.IsNotExist(err) { 256 t.Errorf("TearDown() failed: %v", err) 257 } 258 259 if !fakeManager.unmountCalled { 260 t.Errorf("Unmount not called") 261 } 262 if !fakeManager.detachCalled { 263 t.Errorf("Detach not called") 264 } 265 266 // Test Provisioner 267 fakeManager = &fakePDManager{} 268 mountOptions := []string{"sync", "noatime"} 269 options := volume.VolumeOptions{ 270 PVC: volumetest.CreateTestPVC("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}), 271 // PVName: "test-volume-name", 272 PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete, 273 Parameters: map[string]string{ 274 "VolumeNamespace": "test-volume-namespace", 275 "adminSecretName": secretName, 276 "adminsecretnamespace": "default", 277 }, 278 MountOptions: mountOptions, 279 } 280 provisioner, err := plug.(*storageosPlugin).newProvisionerInternal(options, fakeManager) 281 if err != nil { 282 t.Errorf("newProvisionerInternal() failed: %v", err) 283 } 284 285 persistentSpec, err := provisioner.Provision(nil, nil) 286 if err != nil { 287 t.Fatalf("Provision() failed: %v", err) 288 } 289 290 if persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeName != "test-storageos-name" { 291 t.Errorf("Provision() returned unexpected volume Name: %s, expected test-storageos-name", persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeName) 292 } 293 if persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeNamespace != "test-storageos-namespace" { 294 t.Errorf("Provision() returned unexpected volume Namespace: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeNamespace) 295 } 296 cap := persistentSpec.Spec.Capacity[v1.ResourceStorage] 297 size := cap.Value() 298 if size != 100*1024*1024*1024 { 299 t.Errorf("Provision() returned unexpected volume size: %v", size) 300 } 301 if persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType != "ext2" { 302 t.Errorf("Provision() returned unexpected volume FSType: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType) 303 } 304 if len(persistentSpec.Spec.MountOptions) != 2 { 305 t.Errorf("Provision() returned unexpected volume mount options: %v", persistentSpec.Spec.MountOptions) 306 } 307 if persistentSpec.Labels["fakepdmanager"] != "yes" { 308 t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels) 309 } 310 if !fakeManager.createCalled { 311 t.Errorf("Create not called") 312 } 313 314 // Test Deleter 315 fakeManager = &fakePDManager{} 316 volSpec := &volume.Spec{ 317 PersistentVolume: persistentSpec, 318 } 319 deleter, err := plug.(*storageosPlugin).newDeleterInternal(volSpec, apiCfg, fakeManager) 320 if err != nil { 321 t.Errorf("newDeleterInternal() failed: %v", err) 322 } 323 324 err = deleter.Delete() 325 if err != nil { 326 t.Errorf("Deleter() failed: %v", err) 327 } 328 if !fakeManager.deleteCalled { 329 t.Errorf("Delete not called") 330 } 331} 332 333func TestPersistentClaimReadOnlyFlag(t *testing.T) { 334 tmpDir, err := utiltesting.MkTmpdir("storageos_test") 335 if err != nil { 336 t.Fatalf("error creating temp dir: %v", err) 337 } 338 defer os.RemoveAll(tmpDir) 339 340 pv := &v1.PersistentVolume{ 341 ObjectMeta: metav1.ObjectMeta{ 342 Name: "pvA", 343 }, 344 Spec: v1.PersistentVolumeSpec{ 345 PersistentVolumeSource: v1.PersistentVolumeSource{ 346 StorageOS: &v1.StorageOSPersistentVolumeSource{VolumeName: "pvA", VolumeNamespace: "vnsA", ReadOnly: false}, 347 }, 348 ClaimRef: &v1.ObjectReference{ 349 Name: "claimA", 350 }, 351 }, 352 } 353 354 claim := &v1.PersistentVolumeClaim{ 355 ObjectMeta: metav1.ObjectMeta{ 356 Name: "claimA", 357 Namespace: "nsA", 358 }, 359 Spec: v1.PersistentVolumeClaimSpec{ 360 VolumeName: "pvA", 361 }, 362 Status: v1.PersistentVolumeClaimStatus{ 363 Phase: v1.ClaimBound, 364 }, 365 } 366 367 client := fake.NewSimpleClientset(pv, claim) 368 plugMgr := volume.VolumePluginMgr{} 369 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, client, nil)) 370 plug, _ := plugMgr.FindPluginByName(storageosPluginName) 371 372 // readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes 373 spec := volume.NewSpecFromPersistentVolume(pv, true) 374 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", UID: types.UID("poduid")}} 375 fakeManager := &fakePDManager{} 376 apiCfg := GetAPIConfig() 377 mounter, err := plug.(*storageosPlugin).newMounterInternal(spec, pod, apiCfg, fakeManager, mount.NewFakeMounter(nil), &testingexec.FakeExec{}) 378 if err != nil { 379 t.Fatalf("error creating a new internal mounter:%v", err) 380 } 381 if !mounter.GetAttributes().ReadOnly { 382 t.Errorf("Expected true for mounter.IsReadOnly") 383 } 384} 385