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 fc
18
19import (
20	"fmt"
21	"os"
22	"strconv"
23	"strings"
24	"time"
25
26	v1 "k8s.io/api/core/v1"
27	"k8s.io/apimachinery/pkg/types"
28	"k8s.io/apimachinery/pkg/util/wait"
29	"k8s.io/klog/v2"
30	"k8s.io/kubernetes/pkg/volume"
31	volumeutil "k8s.io/kubernetes/pkg/volume/util"
32	"k8s.io/mount-utils"
33)
34
35type fcAttacher struct {
36	host    volume.VolumeHost
37	manager diskManager
38}
39
40var _ volume.Attacher = &fcAttacher{}
41
42var _ volume.DeviceMounter = &fcAttacher{}
43
44var _ volume.AttachableVolumePlugin = &fcPlugin{}
45
46var _ volume.DeviceMountableVolumePlugin = &fcPlugin{}
47
48func (plugin *fcPlugin) NewAttacher() (volume.Attacher, error) {
49	return &fcAttacher{
50		host:    plugin.host,
51		manager: &fcUtil{},
52	}, nil
53}
54
55func (plugin *fcPlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
56	return plugin.NewAttacher()
57}
58
59func (plugin *fcPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
60	mounter := plugin.host.GetMounter(plugin.GetPluginName())
61	return mounter.GetMountRefs(deviceMountPath)
62}
63
64func (attacher *fcAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
65	return "", nil
66}
67
68func (attacher *fcAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
69	volumesAttachedCheck := make(map[*volume.Spec]bool)
70	for _, spec := range specs {
71		volumesAttachedCheck[spec] = true
72	}
73
74	return volumesAttachedCheck, nil
75}
76
77func (attacher *fcAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
78	mounter, err := volumeSpecToMounter(spec, attacher.host)
79	if err != nil {
80		klog.Warningf("failed to get fc mounter: %v", err)
81		return "", err
82	}
83	return attacher.manager.AttachDisk(*mounter)
84}
85
86func (attacher *fcAttacher) GetDeviceMountPath(
87	spec *volume.Spec) (string, error) {
88	mounter, err := volumeSpecToMounter(spec, attacher.host)
89	if err != nil {
90		klog.Warningf("failed to get fc mounter: %v", err)
91		return "", err
92	}
93
94	return attacher.manager.MakeGlobalPDName(*mounter.fcDisk), nil
95}
96
97func (attacher *fcAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
98	mounter := attacher.host.GetMounter(fcPluginName)
99	notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
100	if err != nil {
101		if os.IsNotExist(err) {
102			if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
103				return err
104			}
105			notMnt = true
106		} else {
107			return err
108		}
109	}
110
111	volumeSource, readOnly, err := getVolumeSource(spec)
112	if err != nil {
113		return err
114	}
115
116	options := []string{}
117	if readOnly {
118		options = append(options, "ro")
119	}
120	if notMnt {
121		diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Exec: attacher.host.GetExec(fcPluginName)}
122		mountOptions := volumeutil.MountOptionFromSpec(spec, options...)
123		err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
124		if err != nil {
125			os.Remove(deviceMountPath)
126			return err
127		}
128	}
129	return nil
130}
131
132type fcDetacher struct {
133	mounter mount.Interface
134	manager diskManager
135	host    volume.VolumeHost
136}
137
138var _ volume.Detacher = &fcDetacher{}
139
140var _ volume.DeviceUnmounter = &fcDetacher{}
141
142func (plugin *fcPlugin) NewDetacher() (volume.Detacher, error) {
143	return &fcDetacher{
144		mounter: plugin.host.GetMounter(plugin.GetPluginName()),
145		manager: &fcUtil{},
146		host:    plugin.host,
147	}, nil
148}
149
150func (plugin *fcPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
151	return plugin.NewDetacher()
152}
153
154func (detacher *fcDetacher) Detach(volumeName string, nodeName types.NodeName) error {
155	return nil
156}
157
158func (detacher *fcDetacher) UnmountDevice(deviceMountPath string) error {
159	// Specify device name for DetachDisk later
160	devName, _, err := mount.GetDeviceNameFromMount(detacher.mounter, deviceMountPath)
161	if err != nil {
162		klog.Errorf("fc: failed to get device from mnt: %s\nError: %v", deviceMountPath, err)
163		return err
164	}
165	// Unmount for deviceMountPath(=globalPDPath)
166	err = mount.CleanupMountPoint(deviceMountPath, detacher.mounter, false)
167	if err != nil {
168		return fmt.Errorf("fc: failed to unmount: %s\nError: %v", deviceMountPath, err)
169	}
170	// GetDeviceNameFromMount from above returns an empty string if deviceMountPath is not a mount point
171	// There is no need to DetachDisk if this is the case (and DetachDisk will throw an error if we attempt)
172	if devName == "" {
173		return nil
174	}
175
176	unMounter := volumeSpecToUnmounter(detacher.mounter, detacher.host)
177	// The device is unmounted now. If UnmountDevice was retried, GetDeviceNameFromMount
178	// won't find any mount and won't return DetachDisk below.
179	// Therefore implement our own retry mechanism here.
180	// E.g. DetachDisk sometimes fails to flush a multipath device with "device is busy" when it was
181	// just unmounted.
182	// 2 minutes should be enough within 6 minute force detach timeout.
183	var detachError error
184	err = wait.PollImmediate(10*time.Second, 2*time.Minute, func() (bool, error) {
185		detachError = detacher.manager.DetachDisk(*unMounter, devName)
186		if detachError != nil {
187			klog.V(4).Infof("fc: failed to detach disk %s (%s): %v", devName, deviceMountPath, detachError)
188			return false, nil
189		}
190		return true, nil
191	})
192	if err != nil {
193		return fmt.Errorf("fc: failed to detach disk: %s: %v", devName, detachError)
194	}
195
196	klog.V(2).Infof("fc: successfully detached disk: %s", devName)
197	return nil
198}
199
200func (plugin *fcPlugin) CanAttach(spec *volume.Spec) (bool, error) {
201	return true, nil
202}
203
204func (plugin *fcPlugin) CanDeviceMount(spec *volume.Spec) (bool, error) {
205	return true, nil
206}
207
208func volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost) (*fcDiskMounter, error) {
209	fc, readOnly, err := getVolumeSource(spec)
210	if err != nil {
211		return nil, err
212	}
213	var lun string
214	var wwids []string
215	if fc.Lun != nil && len(fc.TargetWWNs) != 0 {
216		lun = strconv.Itoa(int(*fc.Lun))
217	} else if len(fc.WWIDs) != 0 {
218		for _, wwid := range fc.WWIDs {
219			wwids = append(wwids, strings.Replace(wwid, " ", "_", -1))
220		}
221	} else {
222		return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter")
223	}
224	fcDisk := &fcDisk{
225		plugin: &fcPlugin{
226			host: host,
227		},
228		wwns:  fc.TargetWWNs,
229		lun:   lun,
230		wwids: wwids,
231		io:    &osIOHandler{},
232	}
233
234	volumeMode, err := volumeutil.GetVolumeMode(spec)
235	if err != nil {
236		return nil, err
237	}
238
239	klog.V(5).Infof("fc: volumeSpecToMounter volumeMode %s", volumeMode)
240	return &fcDiskMounter{
241		fcDisk:       fcDisk,
242		fsType:       fc.FSType,
243		volumeMode:   volumeMode,
244		readOnly:     readOnly,
245		mounter:      volumeutil.NewSafeFormatAndMountFromHost(fcPluginName, host),
246		deviceUtil:   volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()),
247		mountOptions: volumeutil.MountOptionFromSpec(spec),
248	}, nil
249}
250
251func volumeSpecToUnmounter(mounter mount.Interface, host volume.VolumeHost) *fcDiskUnmounter {
252	return &fcDiskUnmounter{
253		fcDisk: &fcDisk{
254			io: &osIOHandler{},
255		},
256		mounter:    mounter,
257		deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()),
258		exec:       host.GetExec(fcPluginName),
259	}
260}
261