1// +build !providerless
2
3/*
4Copyright 2016 The Kubernetes Authors.
5
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17*/
18
19package vsphere_volume
20
21import (
22	"errors"
23	"fmt"
24	"testing"
25
26	v1 "k8s.io/api/core/v1"
27	"k8s.io/apimachinery/pkg/types"
28	"k8s.io/kubernetes/pkg/volume"
29	volumetest "k8s.io/kubernetes/pkg/volume/testing"
30	"k8s.io/legacy-cloud-providers/vsphere/vclib"
31
32	"k8s.io/klog/v2"
33)
34
35var (
36	// diskNameErr is the error when disk name is wrong.
37	diskNameErr = errors.New("wrong diskName")
38	// nodeNameErr is the error when node name is wrong.
39	nodeNameErr = errors.New("wrong nodeName")
40)
41
42func TestGetDeviceName_Volume(t *testing.T) {
43	plugin := newPlugin(t)
44	volPath := "[local] volumes/test"
45	spec := createVolSpec(volPath)
46
47	deviceName, err := plugin.GetVolumeName(spec)
48	if err != nil {
49		t.Errorf("GetDeviceName error: %v", err)
50	}
51	if deviceName != volPath {
52		t.Errorf("GetDeviceName error: expected %s, got %s", volPath, deviceName)
53	}
54}
55
56func TestGetDeviceName_PersistentVolume(t *testing.T) {
57	plugin := newPlugin(t)
58	volPath := "[local] volumes/test"
59	spec := createPVSpec(volPath)
60
61	deviceName, err := plugin.GetVolumeName(spec)
62	if err != nil {
63		t.Errorf("GetDeviceName error: %v", err)
64	}
65	if deviceName != volPath {
66		t.Errorf("GetDeviceName error: expected %s, got %s", volPath, deviceName)
67	}
68}
69
70// One testcase for TestAttachDetach table test below
71type testcase struct {
72	name string
73	// For fake vSphere:
74	attach         attachCall
75	detach         detachCall
76	diskIsAttached diskIsAttachedCall
77	t              *testing.T
78
79	// Actual test to run
80	test func(test *testcase) (string, error)
81	// Expected return of the test
82	expectedDevice string
83	expectedError  error
84}
85
86func TestAttachDetach(t *testing.T) {
87	uuid := "00000000000000"
88	diskName := "[local] volumes/test"
89	nodeName := types.NodeName("host")
90	spec := createVolSpec(diskName)
91	attachError := errors.New("fake attach error")
92	detachError := errors.New("fake detach error")
93	diskCheckError := errors.New("fake DiskIsAttached error")
94	tests := []testcase{
95		// Successful Attach call
96		{
97			name:   "Attach_Positive",
98			attach: attachCall{diskName, nodeName, uuid, nil},
99			test: func(testcase *testcase) (string, error) {
100				attacher := newAttacher(testcase)
101				return attacher.Attach(spec, nodeName)
102			},
103			expectedDevice: "/dev/disk/by-id/wwn-0x" + uuid,
104		},
105
106		// Attach call fails
107		{
108			name:   "Attach_Negative",
109			attach: attachCall{diskName, nodeName, "", attachError},
110			test: func(testcase *testcase) (string, error) {
111				attacher := newAttacher(testcase)
112				return attacher.Attach(spec, nodeName)
113			},
114			expectedError: attachError,
115		},
116
117		// Detach succeeds
118		{
119			name:           "Detach_Positive",
120			diskIsAttached: diskIsAttachedCall{diskName, nodeName, true, nil},
121			detach:         detachCall{diskName, nodeName, nil},
122			test: func(testcase *testcase) (string, error) {
123				detacher := newDetacher(testcase)
124				return "", detacher.Detach(diskName, nodeName)
125			},
126		},
127
128		// Disk is already detached
129		{
130			name:           "Detach_Positive_AlreadyDetached",
131			diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, nil},
132			test: func(testcase *testcase) (string, error) {
133				detacher := newDetacher(testcase)
134				return "", detacher.Detach(diskName, nodeName)
135			},
136		},
137
138		// Detach succeeds when DiskIsAttached fails
139		{
140			name:           "Detach_Positive_CheckFails",
141			diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError},
142			detach:         detachCall{diskName, nodeName, nil},
143			test: func(testcase *testcase) (string, error) {
144				detacher := newDetacher(testcase)
145				return "", detacher.Detach(diskName, nodeName)
146			},
147		},
148
149		// Detach fails
150		{
151			name:           "Detach_Negative",
152			diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError},
153			detach:         detachCall{diskName, nodeName, detachError},
154			test: func(testcase *testcase) (string, error) {
155				detacher := newDetacher(testcase)
156				return "", detacher.Detach(diskName, nodeName)
157			},
158			expectedError: detachError,
159		},
160	}
161
162	for _, testcase := range tests {
163		testcase.t = t
164		device, err := testcase.test(&testcase)
165		if err != testcase.expectedError {
166			t.Errorf("%s failed: expected err=%q, got %q", testcase.name, testcase.expectedError.Error(), err.Error())
167		}
168		if device != testcase.expectedDevice {
169			t.Errorf("%s failed: expected device=%q, got %q", testcase.name, testcase.expectedDevice, device)
170		}
171		t.Logf("Test %q succeeded", testcase.name)
172	}
173}
174
175// newPlugin creates a new vsphereVolumePlugin with fake cloud, NewAttacher
176// and NewDetacher won't work.
177func newPlugin(t *testing.T) *vsphereVolumePlugin {
178	host := volumetest.NewFakeVolumeHost(t, "/tmp", nil, nil)
179	plugins := ProbeVolumePlugins()
180	plugin := plugins[0]
181	plugin.Init(host)
182	return plugin.(*vsphereVolumePlugin)
183}
184
185func newAttacher(testcase *testcase) *vsphereVMDKAttacher {
186	return &vsphereVMDKAttacher{
187		host:           nil,
188		vsphereVolumes: testcase,
189	}
190}
191
192func newDetacher(testcase *testcase) *vsphereVMDKDetacher {
193	return &vsphereVMDKDetacher{
194		vsphereVolumes: testcase,
195	}
196}
197
198func createVolSpec(name string) *volume.Spec {
199	return &volume.Spec{
200		Volume: &v1.Volume{
201			VolumeSource: v1.VolumeSource{
202				VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
203					VolumePath: name,
204				},
205			},
206		},
207	}
208}
209
210func createPVSpec(name string) *volume.Spec {
211	return &volume.Spec{
212		PersistentVolume: &v1.PersistentVolume{
213			Spec: v1.PersistentVolumeSpec{
214				PersistentVolumeSource: v1.PersistentVolumeSource{
215					VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
216						VolumePath: name,
217					},
218				},
219			},
220		},
221	}
222}
223
224// Fake vSphere implementation
225
226type attachCall struct {
227	diskName      string
228	nodeName      types.NodeName
229	retDeviceUUID string
230	ret           error
231}
232
233type detachCall struct {
234	diskName string
235	nodeName types.NodeName
236	ret      error
237}
238
239type diskIsAttachedCall struct {
240	diskName   string
241	nodeName   types.NodeName
242	isAttached bool
243	ret        error
244}
245
246func (testcase *testcase) AttachDisk(diskName string, storagePolicyName string, nodeName types.NodeName) (string, error) {
247	expected := &testcase.attach
248
249	if expected.diskName == "" && expected.nodeName == "" {
250		// testcase.attach looks uninitialized, test did not expect to call
251		// AttachDisk
252		testcase.t.Errorf("Unexpected AttachDisk call!")
253		return "", errors.New("unexpected AttachDisk call")
254	}
255
256	if expected.diskName != diskName {
257		testcase.t.Errorf("Unexpected AttachDisk call: expected diskName %s, got %s", expected.diskName, diskName)
258		return "", fmt.Errorf(`unexpected AttachDisk call: %w`, diskNameErr)
259	}
260
261	if expected.nodeName != nodeName {
262		testcase.t.Errorf("Unexpected AttachDisk call: expected nodeName %s, got %s", expected.nodeName, nodeName)
263		return "", fmt.Errorf(`unexpected AttachDisk call: %w`, nodeNameErr)
264	}
265
266	klog.V(4).Infof("AttachDisk call: %s, %s, returning %q, %v", diskName, nodeName, expected.retDeviceUUID, expected.ret)
267
268	return expected.retDeviceUUID, expected.ret
269}
270
271func (testcase *testcase) DetachDisk(diskName string, nodeName types.NodeName) error {
272	expected := &testcase.detach
273
274	if expected.diskName == "" && expected.nodeName == "" {
275		// testcase.detach looks uninitialized, test did not expect to call
276		// DetachDisk
277		testcase.t.Errorf("Unexpected DetachDisk call!")
278		return errors.New("unexpected DetachDisk call")
279	}
280
281	if expected.diskName != diskName {
282		testcase.t.Errorf("Unexpected DetachDisk call: expected diskName %s, got %s", expected.diskName, diskName)
283		return fmt.Errorf(`unexpected DetachDisk call: %w`, diskNameErr)
284	}
285
286	if expected.nodeName != nodeName {
287		testcase.t.Errorf("Unexpected DetachDisk call: expected nodeName %s, got %s", expected.nodeName, nodeName)
288		return fmt.Errorf(`unexpected DetachDisk call: %w`, nodeNameErr)
289	}
290
291	klog.V(4).Infof("DetachDisk call: %s, %s, returning %v", diskName, nodeName, expected.ret)
292
293	return expected.ret
294}
295
296func (testcase *testcase) DiskIsAttached(diskName string, nodeName types.NodeName) (bool, string, error) {
297	expected := &testcase.diskIsAttached
298
299	if expected.diskName == "" && expected.nodeName == "" {
300		// testcase.diskIsAttached looks uninitialized, test did not expect to
301		// call DiskIsAttached
302		testcase.t.Errorf("Unexpected DiskIsAttached call!")
303		return false, diskName, errors.New("unexpected DiskIsAttached call")
304	}
305
306	if expected.diskName != diskName {
307		testcase.t.Errorf("Unexpected DiskIsAttached call: expected diskName %s, got %s", expected.diskName, diskName)
308		return false, diskName, fmt.Errorf(`unexpected DiskIsAttached call: %w`, diskNameErr)
309	}
310
311	if expected.nodeName != nodeName {
312		testcase.t.Errorf("Unexpected DiskIsAttached call: expected nodeName %s, got %s", expected.nodeName, nodeName)
313		return false, diskName, fmt.Errorf(`unexpected DiskIsAttached call: %w`, nodeNameErr)
314	}
315
316	klog.V(4).Infof("DiskIsAttached call: %s, %s, returning %v, %v", diskName, nodeName, expected.isAttached, expected.ret)
317
318	return expected.isAttached, diskName, expected.ret
319}
320
321func (testcase *testcase) DisksAreAttached(nodeVolumes map[types.NodeName][]string) (map[types.NodeName]map[string]bool, error) {
322	return nil, errors.New("not implemented")
323}
324
325func (testcase *testcase) CreateVolume(volumeOptions *vclib.VolumeOptions) (volumePath string, err error) {
326	return "", errors.New("not implemented")
327}
328
329func (testcase *testcase) DeleteVolume(vmDiskPath string) error {
330	return errors.New("not implemented")
331}
332