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