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 flexvolume
18
19import (
20	"fmt"
21	"path/filepath"
22	"runtime"
23	"strings"
24	"sync"
25
26	api "k8s.io/api/core/v1"
27	"k8s.io/apimachinery/pkg/types"
28	"k8s.io/klog/v2"
29	"k8s.io/kubernetes/pkg/volume"
30	"k8s.io/kubernetes/pkg/volume/util"
31	"k8s.io/mount-utils"
32	"k8s.io/utils/exec"
33	utilstrings "k8s.io/utils/strings"
34)
35
36const (
37	flexVolumePluginName = "kubernetes.io/flexvolume"
38)
39
40// FlexVolumePlugin object.
41type flexVolumePlugin struct {
42	driverName string
43	execPath   string
44	host       volume.VolumeHost
45	runner     exec.Interface
46
47	sync.Mutex
48	unsupportedCommands []string
49	capabilities        DriverCapabilities
50}
51
52type flexVolumeAttachablePlugin struct {
53	*flexVolumePlugin
54}
55
56var _ volume.AttachableVolumePlugin = &flexVolumeAttachablePlugin{}
57var _ volume.PersistentVolumePlugin = &flexVolumePlugin{}
58var _ volume.NodeExpandableVolumePlugin = &flexVolumePlugin{}
59var _ volume.ExpandableVolumePlugin = &flexVolumePlugin{}
60
61var _ volume.DeviceMountableVolumePlugin = &flexVolumeAttachablePlugin{}
62
63// PluginFactory create flex volume plugin
64type PluginFactory interface {
65	NewFlexVolumePlugin(pluginDir, driverName string, runner exec.Interface) (volume.VolumePlugin, error)
66}
67
68type pluginFactory struct{}
69
70func (pluginFactory) NewFlexVolumePlugin(pluginDir, name string, runner exec.Interface) (volume.VolumePlugin, error) {
71	execPath := filepath.Join(pluginDir, name)
72
73	driverName := utilstrings.UnescapeQualifiedName(name)
74
75	flexPlugin := &flexVolumePlugin{
76		driverName:          driverName,
77		execPath:            execPath,
78		runner:              runner,
79		unsupportedCommands: []string{},
80	}
81
82	// Initialize the plugin and probe the capabilities
83	call := flexPlugin.NewDriverCall(initCmd)
84	ds, err := call.Run()
85	if err != nil {
86		return nil, err
87	}
88	flexPlugin.capabilities = *ds.Capabilities
89
90	if flexPlugin.capabilities.Attach {
91		// Plugin supports attach/detach, so return flexVolumeAttachablePlugin
92		return &flexVolumeAttachablePlugin{flexVolumePlugin: flexPlugin}, nil
93	}
94	return flexPlugin, nil
95}
96
97// Init is part of the volume.VolumePlugin interface.
98func (plugin *flexVolumePlugin) Init(host volume.VolumeHost) error {
99	plugin.host = host
100	// Hardwired 'success' as any errors from calling init() will be caught by NewFlexVolumePlugin()
101	return nil
102}
103
104func (plugin *flexVolumePlugin) getExecutable() string {
105	parts := strings.Split(plugin.driverName, "/")
106	execName := parts[len(parts)-1]
107	execPath := filepath.Join(plugin.execPath, execName)
108	if runtime.GOOS == "windows" {
109		execPath = util.GetWindowsPath(execPath)
110	}
111	return execPath
112}
113
114// Name is part of the volume.VolumePlugin interface.
115func (plugin *flexVolumePlugin) GetPluginName() string {
116	return plugin.driverName
117}
118
119// GetVolumeName is part of the volume.VolumePlugin interface.
120func (plugin *flexVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
121	call := plugin.NewDriverCall(getVolumeNameCmd)
122	call.AppendSpec(spec, plugin.host, nil)
123
124	_, err := call.Run()
125	if isCmdNotSupportedErr(err) {
126		return (*pluginDefaults)(plugin).GetVolumeName(spec)
127	} else if err != nil {
128		return "", err
129	}
130
131	name, err := (*pluginDefaults)(plugin).GetVolumeName(spec)
132	if err != nil {
133		return "", err
134	}
135
136	klog.V(4).Info(logPrefix(plugin), "GetVolumeName is not supported yet. Defaulting to PV or volume name: ", name)
137
138	return name, nil
139}
140
141// CanSupport is part of the volume.VolumePlugin interface.
142func (plugin *flexVolumePlugin) CanSupport(spec *volume.Spec) bool {
143	sourceDriver, err := getDriver(spec)
144	if err != nil {
145		return false
146	}
147	return sourceDriver == plugin.driverName
148}
149
150// RequiresRemount is part of the volume.VolumePlugin interface.
151func (plugin *flexVolumePlugin) RequiresRemount(spec *volume.Spec) bool {
152	return false
153}
154
155// GetAccessModes gets the allowed access modes for this plugin.
156func (plugin *flexVolumePlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
157	return []api.PersistentVolumeAccessMode{
158		api.ReadWriteOnce,
159		api.ReadOnlyMany,
160	}
161}
162
163// NewMounter is part of the volume.VolumePlugin interface.
164func (plugin *flexVolumePlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
165	return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()), plugin.runner)
166}
167
168// newMounterInternal is the internal mounter routine to build the volume.
169func (plugin *flexVolumePlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface, runner exec.Interface) (volume.Mounter, error) {
170	sourceDriver, err := getDriver(spec)
171	if err != nil {
172		return nil, err
173	}
174
175	readOnly, err := getReadOnly(spec)
176	if err != nil {
177		return nil, err
178	}
179
180	var metricsProvider volume.MetricsProvider
181	if plugin.capabilities.SupportsMetrics {
182		metricsProvider = volume.NewMetricsStatFS(plugin.host.GetPodVolumeDir(
183			pod.UID, utilstrings.EscapeQualifiedName(sourceDriver), spec.Name()))
184	} else {
185		metricsProvider = &volume.MetricsNil{}
186	}
187
188	return &flexVolumeMounter{
189		flexVolume: &flexVolume{
190			driverName:            sourceDriver,
191			execPath:              plugin.getExecutable(),
192			mounter:               mounter,
193			plugin:                plugin,
194			podName:               pod.Name,
195			podUID:                pod.UID,
196			podNamespace:          pod.Namespace,
197			podServiceAccountName: pod.Spec.ServiceAccountName,
198			volName:               spec.Name(),
199			MetricsProvider:       metricsProvider,
200		},
201		runner:   runner,
202		spec:     spec,
203		readOnly: readOnly,
204	}, nil
205}
206
207// NewUnmounter is part of the volume.VolumePlugin interface.
208func (plugin *flexVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
209	return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()), plugin.runner)
210}
211
212// newUnmounterInternal is the internal unmounter routine to clean the volume.
213func (plugin *flexVolumePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface, runner exec.Interface) (volume.Unmounter, error) {
214	var metricsProvider volume.MetricsProvider
215	if plugin.capabilities.SupportsMetrics {
216		metricsProvider = volume.NewMetricsStatFS(plugin.host.GetPodVolumeDir(
217			podUID, utilstrings.EscapeQualifiedName(plugin.driverName), volName))
218	} else {
219		metricsProvider = &volume.MetricsNil{}
220	}
221
222	return &flexVolumeUnmounter{
223		flexVolume: &flexVolume{
224			driverName:      plugin.driverName,
225			execPath:        plugin.getExecutable(),
226			mounter:         mounter,
227			plugin:          plugin,
228			podUID:          podUID,
229			volName:         volName,
230			MetricsProvider: metricsProvider,
231		},
232		runner: runner,
233	}, nil
234}
235
236// NewAttacher is part of the volume.AttachableVolumePlugin interface.
237func (plugin *flexVolumeAttachablePlugin) NewAttacher() (volume.Attacher, error) {
238	return &flexVolumeAttacher{plugin}, nil
239}
240
241func (plugin *flexVolumeAttachablePlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
242	return plugin.NewAttacher()
243}
244
245// NewDetacher is part of the volume.AttachableVolumePlugin interface.
246func (plugin *flexVolumeAttachablePlugin) NewDetacher() (volume.Detacher, error) {
247	return &flexVolumeDetacher{plugin}, nil
248}
249
250func (plugin *flexVolumeAttachablePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
251	return plugin.NewDetacher()
252}
253
254func (plugin *flexVolumeAttachablePlugin) CanAttach(spec *volume.Spec) (bool, error) {
255	return true, nil
256}
257
258func (plugin *flexVolumeAttachablePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) {
259	return true, nil
260}
261
262// ConstructVolumeSpec is part of the volume.AttachableVolumePlugin interface.
263func (plugin *flexVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
264	flexVolume := &api.Volume{
265		Name: volumeName,
266		VolumeSource: api.VolumeSource{
267			FlexVolume: &api.FlexVolumeSource{
268				Driver: plugin.driverName,
269			},
270		},
271	}
272	return volume.NewSpecFromVolume(flexVolume), nil
273}
274
275func (plugin *flexVolumePlugin) SupportsMountOption() bool {
276	return false
277}
278
279// Mark the given commands as unsupported.
280func (plugin *flexVolumePlugin) unsupported(commands ...string) {
281	plugin.Lock()
282	defer plugin.Unlock()
283	plugin.unsupportedCommands = append(plugin.unsupportedCommands, commands...)
284}
285
286func (plugin *flexVolumePlugin) SupportsBulkVolumeVerification() bool {
287	return false
288}
289
290// Returns true iff the given command is known to be unsupported.
291func (plugin *flexVolumePlugin) isUnsupported(command string) bool {
292	plugin.Lock()
293	defer plugin.Unlock()
294	for _, unsupportedCommand := range plugin.unsupportedCommands {
295		if command == unsupportedCommand {
296			return true
297		}
298	}
299	return false
300}
301
302func (plugin *flexVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
303	mounter := plugin.host.GetMounter(plugin.GetPluginName())
304	return mounter.GetMountRefs(deviceMountPath)
305}
306
307func (plugin *flexVolumePlugin) getDeviceMountPath(spec *volume.Spec) (string, error) {
308	volumeName, err := plugin.GetVolumeName(spec)
309	if err != nil {
310		return "", fmt.Errorf("GetVolumeName failed from getDeviceMountPath: %s", err)
311	}
312
313	mountsDir := filepath.Join(plugin.host.GetPluginDir(flexVolumePluginName), plugin.driverName, "mounts")
314	return filepath.Join(mountsDir, volumeName), nil
315}
316
317func (plugin *flexVolumePlugin) RequiresFSResize() bool {
318	return plugin.capabilities.RequiresFSResize
319}
320