mirror of https://github.com/k3s-io/k3s
Add support for attacher/detacher interface in Flex volume
parent
46b20acba2
commit
0d2af70e95
|
@ -19,10 +19,13 @@
|
||||||
usage() {
|
usage() {
|
||||||
err "Invalid usage. Usage: "
|
err "Invalid usage. Usage: "
|
||||||
err "\t$0 init"
|
err "\t$0 init"
|
||||||
err "\t$0 attach <json params>"
|
err "\t$0 attach <json params> <nodename>"
|
||||||
err "\t$0 detach <mount device>"
|
err "\t$0 detach <mount device> <nodename>"
|
||||||
err "\t$0 mount <mount dir> <mount device> <json params>"
|
err "\t$0 waitforattach <mount device> <json params>"
|
||||||
err "\t$0 unmount <mount dir>"
|
err "\t$0 mountdevice <mount dir> <mount device> <json params>"
|
||||||
|
err "\t$0 unmountdevice <mount dir>"
|
||||||
|
err "\t$0 getvolumename <json params>"
|
||||||
|
err "\t$0 isattached <json params> <nodename>"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,16 +46,23 @@ ismounted() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
attach() {
|
getdevice() {
|
||||||
VOLUMEID=$(echo $1 | jq -r '.volumeID')
|
VOLUMEID=$(echo ${JSON_PARAMS} | jq -r '.volumeID')
|
||||||
SIZE=$(echo $1 | jq -r '.size')
|
VG=$(echo ${JSON_PARAMS}|jq -r '.volumegroup')
|
||||||
VG=$(echo $1|jq -r '.volumegroup')
|
|
||||||
|
|
||||||
# LVM substitutes - with --
|
# LVM substitutes - with --
|
||||||
VOLUMEID=`echo $VOLUMEID|sed s/-/--/g`
|
VOLUMEID=`echo $VOLUMEID|sed s/-/--/g`
|
||||||
VG=`echo $VG|sed s/-/--/g`
|
VG=`echo $VG|sed s/-/--/g`
|
||||||
|
|
||||||
DMDEV="/dev/mapper/${VG}-${VOLUMEID}"
|
DMDEV="/dev/mapper/${VG}-${VOLUMEID}"
|
||||||
|
echo ${DMDEV}
|
||||||
|
}
|
||||||
|
|
||||||
|
attach() {
|
||||||
|
JSON_PARAMS=$1
|
||||||
|
SIZE=$(echo $1 | jq -r '.size')
|
||||||
|
|
||||||
|
DMDEV=$(getdevice)
|
||||||
if [ ! -b "${DMDEV}" ]; then
|
if [ ! -b "${DMDEV}" ]; then
|
||||||
err "{\"status\": \"Failure\", \"message\": \"Volume ${VOLUMEID} does not exist\"}"
|
err "{\"status\": \"Failure\", \"message\": \"Volume ${VOLUMEID} does not exist\"}"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -66,7 +76,12 @@ detach() {
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
domount() {
|
waitforattach() {
|
||||||
|
shift
|
||||||
|
attach $*
|
||||||
|
}
|
||||||
|
|
||||||
|
domountdevice() {
|
||||||
MNTPATH=$1
|
MNTPATH=$1
|
||||||
DMDEV=$2
|
DMDEV=$2
|
||||||
FSTYPE=$(echo $3|jq -r '.["kubernetes.io/fsType"]')
|
FSTYPE=$(echo $3|jq -r '.["kubernetes.io/fsType"]')
|
||||||
|
@ -101,8 +116,13 @@ domount() {
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
unmount() {
|
unmountdevice() {
|
||||||
MNTPATH=$1
|
MNTPATH=$1
|
||||||
|
if [ ! -d ${MNTPATH} ]; then
|
||||||
|
log "{\"status\": \"Success\"}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $(ismounted) -eq 0 ] ; then
|
if [ $(ismounted) -eq 0 ] ; then
|
||||||
log "{\"status\": \"Success\"}"
|
log "{\"status\": \"Success\"}"
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -113,12 +133,27 @@ unmount() {
|
||||||
err "{ \"status\": \"Failed\", \"message\": \"Failed to unmount volume at ${MNTPATH}\"}"
|
err "{ \"status\": \"Failed\", \"message\": \"Failed to unmount volume at ${MNTPATH}\"}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
rmdir ${MNTPATH} &> /dev/null
|
|
||||||
|
|
||||||
log "{\"status\": \"Success\"}"
|
log "{\"status\": \"Success\"}"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getvolumename() {
|
||||||
|
JSON_PARAMS=$1
|
||||||
|
DMDEV=$(getdevice)
|
||||||
|
|
||||||
|
# get lvm device UUID
|
||||||
|
UUID=`lvs -o lv_uuid --noheadings ${DMDEV} 2>/dev/null|tr -d " "`
|
||||||
|
|
||||||
|
log "{\"status\": \"Success\", \"volumeName\":\"${UUID}\"}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
isattached() {
|
||||||
|
log "{\"status\": \"Success\", \"attached\":true}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
op=$1
|
op=$1
|
||||||
|
|
||||||
if [ "$op" = "init" ]; then
|
if [ "$op" = "init" ]; then
|
||||||
|
@ -139,14 +174,24 @@ case "$op" in
|
||||||
detach)
|
detach)
|
||||||
detach $*
|
detach $*
|
||||||
;;
|
;;
|
||||||
mount)
|
waitforattach)
|
||||||
domount $*
|
waitforattach $*
|
||||||
;;
|
;;
|
||||||
unmount)
|
mountdevice)
|
||||||
unmount $*
|
domountdevice $*
|
||||||
;;
|
;;
|
||||||
|
unmountdevice)
|
||||||
|
unmountdevice $*
|
||||||
|
;;
|
||||||
|
getvolumename)
|
||||||
|
getvolumename $*
|
||||||
|
;;
|
||||||
|
isattached)
|
||||||
|
isattached $*
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
usage
|
err "{ \"status\": \"Not supported\" }"
|
||||||
|
exit 1
|
||||||
esac
|
esac
|
||||||
|
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2015 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Notes:
|
||||||
|
# - Please install "jq" package before using this driver.
|
||||||
|
usage() {
|
||||||
|
err "Invalid usage. Usage: "
|
||||||
|
err "\t$0 init"
|
||||||
|
err "\t$0 mount <mount dir> <json params>"
|
||||||
|
err "\t$0 unmount <mount dir>"
|
||||||
|
err "\t$0 getvolumename <json params>"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
err() {
|
||||||
|
echo -ne $* 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo -ne $* >&1
|
||||||
|
}
|
||||||
|
|
||||||
|
ismounted() {
|
||||||
|
MOUNT=`findmnt -n ${MNTPATH} 2>/dev/null | cut -d' ' -f1`
|
||||||
|
if [ "${MOUNT}" == "${MNTPATH}" ]; then
|
||||||
|
echo "1"
|
||||||
|
else
|
||||||
|
echo "0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
domount() {
|
||||||
|
MNTPATH=$1
|
||||||
|
|
||||||
|
NFS_SERVER=$(echo $2 | jq -r '.server')
|
||||||
|
SHARE=$(echo $2 | jq -r '.share')
|
||||||
|
|
||||||
|
if [ $(ismounted) -eq 1 ] ; then
|
||||||
|
log "{\"status\": \"Success\"}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p ${MNTPATH} &> /dev/null
|
||||||
|
|
||||||
|
mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
err "{ \"status\": \"Failure\", \"message\": \"Failed to mount ${NFS_SERVER}:${SHARE} at ${MNTPATH}\"}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "{\"status\": \"Success\"}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
unmount() {
|
||||||
|
MNTPATH=$1
|
||||||
|
if [ $(ismounted) -eq 0 ] ; then
|
||||||
|
log "{\"status\": \"Success\"}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
umount ${MNTPATH} &> /dev/null
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
err "{ \"status\": \"Failed\", \"message\": \"Failed to unmount volume at ${MNTPATH}\"}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "{\"status\": \"Success\"}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getvolumename() {
|
||||||
|
NFS_SERVER=$(echo $1 | jq -r '.server')
|
||||||
|
SHARE=$(echo $1 | jq -r '.share')
|
||||||
|
|
||||||
|
log "{\"status\": \"Success\", \"volumeName\": \"${NFS_SERVER}/${SHARE}\"}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
op=$1
|
||||||
|
|
||||||
|
if [ "$op" = "init" ]; then
|
||||||
|
log "{\"status\": \"Success\"}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -lt 2 ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$op" in
|
||||||
|
mount)
|
||||||
|
domount $*
|
||||||
|
;;
|
||||||
|
unmount)
|
||||||
|
unmount $*
|
||||||
|
;;
|
||||||
|
getvolumename)
|
||||||
|
getvolumename $*
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
err "{ \"status\": \"Not supported\" }"
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 1
|
|
@ -0,0 +1,22 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: nginx-nfs
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx-nfs
|
||||||
|
image: nginx
|
||||||
|
volumeMounts:
|
||||||
|
- name: test
|
||||||
|
mountPath: /data
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
volumes:
|
||||||
|
- name: test
|
||||||
|
flexVolume:
|
||||||
|
driver: "k8s/nfs"
|
||||||
|
fsType: "nfs"
|
||||||
|
options:
|
||||||
|
server: "172.16.0.25"
|
||||||
|
share: "dws_nas_scratch"
|
|
@ -2,6 +2,7 @@ apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx
|
name: nginx
|
||||||
|
namespace: default
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: nginx
|
- name: nginx
|
||||||
|
@ -20,4 +21,3 @@ spec:
|
||||||
volumeID: "vol1"
|
volumeID: "vol1"
|
||||||
size: "1000m"
|
size: "1000m"
|
||||||
volumegroup: "kube_vg"
|
volumegroup: "kube_vg"
|
||||||
|
|
||||||
|
|
|
@ -251,3 +251,7 @@ func (eic execInContainer) SetStdin(in io.Reader) {
|
||||||
func (eic execInContainer) SetStdout(out io.Writer) {
|
func (eic execInContainer) SetStdout(out io.Writer) {
|
||||||
//unimplemented
|
//unimplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (eic execInContainer) Stop() {
|
||||||
|
//unimplemented
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ func (f *FakeCmd) SetStdin(in io.Reader) {}
|
||||||
|
|
||||||
func (f *FakeCmd) SetStdout(out io.Writer) {}
|
func (f *FakeCmd) SetStdout(out io.Writer) {}
|
||||||
|
|
||||||
|
func (f *FakeCmd) Stop() {}
|
||||||
|
|
||||||
type fakeExitError struct {
|
type fakeExitError struct {
|
||||||
exited bool
|
exited bool
|
||||||
statusCode int
|
statusCode int
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
osexec "os/exec"
|
osexec "os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrExecutableNotFound is returned if the executable is not found.
|
// ErrExecutableNotFound is returned if the executable is not found.
|
||||||
|
@ -48,6 +49,11 @@ type Cmd interface {
|
||||||
SetDir(dir string)
|
SetDir(dir string)
|
||||||
SetStdin(in io.Reader)
|
SetStdin(in io.Reader)
|
||||||
SetStdout(out io.Writer)
|
SetStdout(out io.Writer)
|
||||||
|
// Stops the command by sending SIGTERM. It is not guaranteed the
|
||||||
|
// process will stop before this function returns. If the process is not
|
||||||
|
// responding, an internal timer function will send a SIGKILL to force
|
||||||
|
// terminate after 10 seconds.
|
||||||
|
Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExitError is an interface that presents an API similar to os.ProcessState, which is
|
// ExitError is an interface that presents an API similar to os.ProcessState, which is
|
||||||
|
@ -110,6 +116,21 @@ func (cmd *cmdWrapper) Output() ([]byte, error) {
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop is part of the Cmd interface.
|
||||||
|
func (cmd *cmdWrapper) Stop() {
|
||||||
|
c := (*osexec.Cmd)(cmd)
|
||||||
|
if c.ProcessState.Exited() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Process.Signal(syscall.SIGTERM)
|
||||||
|
time.AfterFunc(10*time.Second, func() {
|
||||||
|
if c.ProcessState.Exited() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Process.Signal(syscall.SIGKILL)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func handleError(err error) error {
|
func handleError(err error) error {
|
||||||
if ee, ok := err.(*osexec.ExitError); ok {
|
if ee, ok := err.(*osexec.ExitError); ok {
|
||||||
// Force a compile fail if exitErrorWrapper can't convert to ExitError.
|
// Force a compile fail if exitErrorWrapper can't convert to ExitError.
|
||||||
|
|
|
@ -90,6 +90,10 @@ func (fake *FakeCmd) Output() ([]byte, error) {
|
||||||
return nil, fmt.Errorf("unimplemented")
|
return nil, fmt.Errorf("unimplemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fake *FakeCmd) Stop() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
// A simple fake ExitError type.
|
// A simple fake ExitError type.
|
||||||
type FakeExitError struct {
|
type FakeExitError struct {
|
||||||
Status int
|
Status int
|
||||||
|
|
|
@ -11,8 +11,20 @@ load(
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"flexvolume.go",
|
"attacher.go",
|
||||||
"flexvolume_util.go",
|
"attacher-defaults.go",
|
||||||
|
"detacher.go",
|
||||||
|
"detacher-defaults.go",
|
||||||
|
"driver-call.go",
|
||||||
|
"mounter.go",
|
||||||
|
"mounter-defaults.go",
|
||||||
|
"plugin.go",
|
||||||
|
"plugin-defaults.go",
|
||||||
|
"probe.go",
|
||||||
|
"unmounter.go",
|
||||||
|
"unmounter-defaults.go",
|
||||||
|
"util.go",
|
||||||
|
"volume.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
|
@ -23,14 +35,21 @@ go_library(
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
"//pkg/volume/util:go_default_library",
|
"//pkg/volume/util:go_default_library",
|
||||||
"//vendor:github.com/golang/glog",
|
"//vendor:github.com/golang/glog",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
|
||||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["flexvolume_test.go"],
|
srcs = [
|
||||||
|
"attacher_test.go",
|
||||||
|
"common_test.go",
|
||||||
|
"detacher_test.go",
|
||||||
|
"flexvolume_test.go",
|
||||||
|
"mounter_test.go",
|
||||||
|
"plugin_test.go",
|
||||||
|
"unmounter_test.go",
|
||||||
|
],
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
approvers:
|
approvers:
|
||||||
- chakri-nelluri
|
- chakri-nelluri
|
||||||
- saad-ali
|
- saad-ali
|
||||||
|
- MikaelCluseau
|
||||||
reviewers:
|
reviewers:
|
||||||
- ivan4th
|
- ivan4th
|
||||||
- rata
|
- rata
|
||||||
|
@ -20,3 +21,4 @@ reviewers:
|
||||||
- rkouj
|
- rkouj
|
||||||
- msau42
|
- msau42
|
||||||
- chakri-nelluri
|
- chakri-nelluri
|
||||||
|
- MikaelCluseau
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attacherDefaults flexVolumeAttacher
|
||||||
|
|
||||||
|
// Attach is part of the volume.Attacher interface
|
||||||
|
func (a *attacherDefaults) Attach(spec *volume.Spec, hostName types.NodeName) (string, error) {
|
||||||
|
glog.Warning(logPrefix(a.plugin), "using default Attach for volume ", spec.Name, ", host ", hostName)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForAttach is part of the volume.Attacher interface
|
||||||
|
func (a *attacherDefaults) WaitForAttach(spec *volume.Spec, devicePath string, timeout time.Duration) (string, error) {
|
||||||
|
glog.Warning(logPrefix(a.plugin), "using default WaitForAttach for volume ", spec.Name, ", device ", devicePath)
|
||||||
|
return devicePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceMountPath is part of the volume.Attacher interface
|
||||||
|
func (a *attacherDefaults) GetDeviceMountPath(spec *volume.Spec, mountsDir string) (string, error) {
|
||||||
|
glog.Warning(logPrefix(a.plugin), "using default GetDeviceMountPath for volume ", spec.Name, ", mountsDir ", mountsDir)
|
||||||
|
volumeName, err := a.plugin.GetVolumeName(spec)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("GetVolumeName failed from GetDeviceMountPath: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(mountsDir, volumeName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MountDevice is part of the volume.Attacher interface
|
||||||
|
func (a *attacherDefaults) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
|
||||||
|
glog.Warning(logPrefix(a.plugin), "using default MountDevice for volume ", spec.Name, ", device ", devicePath, ", deviceMountPath ", deviceMountPath)
|
||||||
|
volSource, readOnly := getVolumeSource(spec)
|
||||||
|
|
||||||
|
options := make([]string, 0)
|
||||||
|
|
||||||
|
if readOnly {
|
||||||
|
options = append(options, "ro")
|
||||||
|
} else {
|
||||||
|
options = append(options, "rw")
|
||||||
|
}
|
||||||
|
|
||||||
|
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
|
||||||
|
|
||||||
|
return diskMounter.FormatAndMount(devicePath, deviceMountPath, volSource.FSType, options)
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
type flexVolumeAttacher struct {
|
||||||
|
plugin *flexVolumePlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ volume.Attacher = &flexVolumeAttacher{}
|
||||||
|
|
||||||
|
// Attach is part of the volume.Attacher interface
|
||||||
|
func (a *flexVolumeAttacher) Attach(spec *volume.Spec, hostName types.NodeName) (string, error) {
|
||||||
|
call := a.plugin.NewDriverCall(attachCmd)
|
||||||
|
call.AppendSpec(spec, a.plugin.host, nil)
|
||||||
|
call.Append(string(hostName))
|
||||||
|
|
||||||
|
status, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
return (*attacherDefaults)(a).Attach(spec, hostName)
|
||||||
|
} else if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return status.DevicePath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForAttach is part of the volume.Attacher interface
|
||||||
|
func (a *flexVolumeAttacher) WaitForAttach(spec *volume.Spec, devicePath string, timeout time.Duration) (string, error) {
|
||||||
|
call := a.plugin.NewDriverCallWithTimeout(waitForAttachCmd, timeout)
|
||||||
|
call.Append(devicePath)
|
||||||
|
call.AppendSpec(spec, a.plugin.host, nil)
|
||||||
|
|
||||||
|
status, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
return (*attacherDefaults)(a).WaitForAttach(spec, devicePath, timeout)
|
||||||
|
} else if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return status.DevicePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceMountPath is part of the volume.Attacher interface
|
||||||
|
func (a *flexVolumeAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) {
|
||||||
|
mountsDir := path.Join(a.plugin.host.GetPluginDir(flexVolumePluginName), a.plugin.driverName, "mounts")
|
||||||
|
|
||||||
|
return (*attacherDefaults)(a).GetDeviceMountPath(spec, mountsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MountDevice is part of the volume.Attacher interface
|
||||||
|
func (a *flexVolumeAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
|
||||||
|
// Mount only once.
|
||||||
|
alreadyMounted, err := prepareForMount(a.plugin.host.GetMounter(), deviceMountPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if alreadyMounted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
call := a.plugin.NewDriverCall(mountDeviceCmd)
|
||||||
|
call.Append(deviceMountPath)
|
||||||
|
call.Append(devicePath)
|
||||||
|
call.AppendSpec(spec, a.plugin.host, nil)
|
||||||
|
|
||||||
|
_, err = call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
// Devicepath is empty if the plugin does not support attach calls. Ignore mountDevice calls if the
|
||||||
|
// plugin does not implement attach interface.
|
||||||
|
if devicePath != "" {
|
||||||
|
return (*attacherDefaults)(a).MountDevice(spec, devicePath, deviceMountPath, a.plugin.host.GetMounter())
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *flexVolumeAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
|
||||||
|
volumesAttachedCheck := make(map[*volume.Spec]bool)
|
||||||
|
for _, spec := range specs {
|
||||||
|
volumesAttachedCheck[spec] = true
|
||||||
|
|
||||||
|
call := a.plugin.NewDriverCall(isAttached)
|
||||||
|
call.AppendSpec(spec, a.plugin.host, nil)
|
||||||
|
call.Append(string(nodeName))
|
||||||
|
|
||||||
|
status, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
return nil, nil
|
||||||
|
} else if err == nil {
|
||||||
|
if !status.Attached {
|
||||||
|
volumesAttachedCheck[spec] = false
|
||||||
|
glog.V(2).Infof("VolumesAreAttached: check volume (%q) is no longer attached", spec.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return volumesAttachedCheck, nil
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAttach(t *testing.T) {
|
||||||
|
spec := fakeVolumeSpec()
|
||||||
|
|
||||||
|
plugin, _ := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, notSupportedOutput(), attachCmd,
|
||||||
|
specJson(plugin, spec, nil), "localhost"),
|
||||||
|
)
|
||||||
|
|
||||||
|
a, _ := plugin.NewAttacher()
|
||||||
|
a.Attach(spec, "localhost")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWaitForAttach(t *testing.T) {
|
||||||
|
spec := fakeVolumeSpec()
|
||||||
|
|
||||||
|
plugin, _ := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, notSupportedOutput(), waitForAttachCmd, "/dev/sdx",
|
||||||
|
specJson(plugin, spec, nil)),
|
||||||
|
)
|
||||||
|
|
||||||
|
a, _ := plugin.NewAttacher()
|
||||||
|
a.WaitForAttach(spec, "/dev/sdx", 1*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMountDevice(t *testing.T) {
|
||||||
|
spec := fakeVolumeSpec()
|
||||||
|
|
||||||
|
plugin, rootDir := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, notSupportedOutput(), mountDeviceCmd, rootDir+"/mount-dir", "/dev/sdx",
|
||||||
|
specJson(plugin, spec, nil)),
|
||||||
|
)
|
||||||
|
|
||||||
|
a, _ := plugin.NewAttacher()
|
||||||
|
a.MountDevice(spec, "/dev/sdx", rootDir+"/mount-dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsVolumeAttached(t *testing.T) {
|
||||||
|
spec := fakeVolumeSpec()
|
||||||
|
|
||||||
|
plugin, _ := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, notSupportedOutput(), isAttached, specJson(plugin, spec, nil), "localhost"),
|
||||||
|
)
|
||||||
|
a, _ := plugin.NewAttacher()
|
||||||
|
specs := []*volume.Spec{spec}
|
||||||
|
a.VolumesAreAttached(specs, "localhost")
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utiltesting "k8s.io/client-go/util/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testPlugin() (*flexVolumePlugin, string) {
|
||||||
|
rootDir, err := utiltesting.MkTmpdir("flexvolume_test")
|
||||||
|
if err != nil {
|
||||||
|
panic("error creating temp dir: " + err.Error())
|
||||||
|
}
|
||||||
|
return &flexVolumePlugin{
|
||||||
|
driverName: "test",
|
||||||
|
execPath: "/plugin",
|
||||||
|
host: volumetesting.NewFakeVolumeHost(rootDir, nil, nil),
|
||||||
|
unsupportedCommands: []string{},
|
||||||
|
}, rootDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertDriverCall(t *testing.T, output exec.FakeCombinedOutputAction, expectedCommand string, expectedArgs ...string) exec.FakeCommandAction {
|
||||||
|
return func(cmd string, args ...string) exec.Cmd {
|
||||||
|
if cmd != "/plugin/test" {
|
||||||
|
t.Errorf("Wrong executable called: got %v, expected %v", cmd, "/plugin/test")
|
||||||
|
}
|
||||||
|
if args[0] != expectedCommand {
|
||||||
|
t.Errorf("Wrong command called: got %v, expected %v", args[0], expectedCommand)
|
||||||
|
}
|
||||||
|
cmdArgs := args[1:]
|
||||||
|
if !sameArgs(cmdArgs, expectedArgs) {
|
||||||
|
t.Errorf("Wrong args for %s: got %v, expected %v", args[0], cmdArgs, expectedArgs)
|
||||||
|
}
|
||||||
|
return &exec.FakeCmd{
|
||||||
|
Argv: args,
|
||||||
|
CombinedOutputScript: []exec.FakeCombinedOutputAction{output},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeRunner(fakeCommands ...exec.FakeCommandAction) exec.Interface {
|
||||||
|
return &exec.FakeExec{
|
||||||
|
CommandScript: fakeCommands,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeResultOutput(result interface{}) exec.FakeCombinedOutputAction {
|
||||||
|
return func() ([]byte, error) {
|
||||||
|
bytes, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
panic("Unable to marshal result: " + err.Error())
|
||||||
|
}
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func successOutput() exec.FakeCombinedOutputAction {
|
||||||
|
return fakeResultOutput(&DriverStatus{StatusSuccess, "", "", "", true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func notSupportedOutput() exec.FakeCombinedOutputAction {
|
||||||
|
return fakeResultOutput(&DriverStatus{StatusNotSupported, "", "", "", false})
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameArgs(args, expectedArgs []string) bool {
|
||||||
|
if len(args) != len(expectedArgs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range args {
|
||||||
|
if v != expectedArgs[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeVolumeSpec() *volume.Spec {
|
||||||
|
vol := &v1.Volume{
|
||||||
|
Name: "vol1",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
FlexVolume: &v1.FlexVolumeSource{
|
||||||
|
Driver: "kubernetes.io/fakeAttacher",
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return volume.NewSpecFromVolume(vol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakePersistentVolumeSpec() *volume.Spec {
|
||||||
|
vol := &v1.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "vol1",
|
||||||
|
},
|
||||||
|
Spec: v1.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||||
|
FlexVolume: &v1.FlexVolumeSource{
|
||||||
|
Driver: "kubernetes.io/fakeAttacher",
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return volume.NewSpecFromPersistentVolume(vol, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func specJson(plugin *flexVolumePlugin, spec *volume.Spec, extraOptions map[string]string) string {
|
||||||
|
o, err := NewOptionsForDriver(spec, plugin.host, extraOptions)
|
||||||
|
if err != nil {
|
||||||
|
panic("Failed to convert spec: " + err.Error())
|
||||||
|
}
|
||||||
|
bytes, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
panic("Unable to marshal result: " + err.Error())
|
||||||
|
}
|
||||||
|
return string(bytes)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type detacherDefaults flexVolumeDetacher
|
||||||
|
|
||||||
|
// Detach is part of the volume.Detacher interface.
|
||||||
|
func (d *detacherDefaults) Detach(deviceName string, hostName types.NodeName) error {
|
||||||
|
glog.Warning(logPrefix(d.plugin), "using default Detach for device ", deviceName, ", host ", hostName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForDetach is part of the volume.Detacher interface.
|
||||||
|
func (d *detacherDefaults) WaitForDetach(devicePath string, timeout time.Duration) error {
|
||||||
|
glog.Warning(logPrefix(d.plugin), "using default WaitForDetach for device ", devicePath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmountDevice is part of the volume.Detacher interface.
|
||||||
|
func (d *detacherDefaults) UnmountDevice(deviceMountPath string) error {
|
||||||
|
glog.Warning(logPrefix(d.plugin), "using default UnmountDevice for device mount path ", deviceMountPath)
|
||||||
|
return util.UnmountPath(deviceMountPath, d.plugin.host.GetMounter())
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type flexVolumeDetacher struct {
|
||||||
|
plugin *flexVolumePlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ volume.Detacher = &flexVolumeDetacher{}
|
||||||
|
|
||||||
|
// Detach is part of the volume.Detacher interface.
|
||||||
|
func (d *flexVolumeDetacher) Detach(deviceName string, hostName types.NodeName) error {
|
||||||
|
call := d.plugin.NewDriverCall(detachCmd)
|
||||||
|
call.Append(deviceName)
|
||||||
|
call.Append(string(hostName))
|
||||||
|
|
||||||
|
_, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
return (*detacherDefaults)(d).Detach(deviceName, hostName)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForDetach is part of the volume.Detacher interface.
|
||||||
|
func (d *flexVolumeDetacher) WaitForDetach(devicePath string, timeout time.Duration) error {
|
||||||
|
call := d.plugin.NewDriverCallWithTimeout(waitForDetachCmd, timeout)
|
||||||
|
call.Append(devicePath)
|
||||||
|
|
||||||
|
_, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
return (*detacherDefaults)(d).WaitForDetach(devicePath, timeout)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmountDevice is part of the volume.Detacher interface.
|
||||||
|
func (d *flexVolumeDetacher) UnmountDevice(deviceMountPath string) error {
|
||||||
|
|
||||||
|
if pathExists, pathErr := util.PathExists(deviceMountPath); pathErr != nil {
|
||||||
|
return fmt.Errorf("Error checking if path exists: %v", pathErr)
|
||||||
|
} else if !pathExists {
|
||||||
|
glog.Warningf("Warning: Unmount skipped because path does not exist: %v", deviceMountPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
notmnt, err := isNotMounted(d.plugin.host.GetMounter(), deviceMountPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if notmnt {
|
||||||
|
glog.Warningf("Warning: Path: %v already unmounted", deviceMountPath)
|
||||||
|
} else {
|
||||||
|
call := d.plugin.NewDriverCall(unmountDeviceCmd)
|
||||||
|
call.Append(deviceMountPath)
|
||||||
|
|
||||||
|
_, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
err = (*detacherDefaults)(d).UnmountDevice(deviceMountPath)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flexvolume driver may remove the directory. Ignore if it does.
|
||||||
|
if pathExists, pathErr := util.PathExists(deviceMountPath); pathErr != nil {
|
||||||
|
return fmt.Errorf("Error checking if path exists: %v", pathErr)
|
||||||
|
} else if !pathExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return os.Remove(deviceMountPath)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetach(t *testing.T) {
|
||||||
|
plugin, _ := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, notSupportedOutput(), detachCmd,
|
||||||
|
"sdx", "localhost"),
|
||||||
|
)
|
||||||
|
|
||||||
|
d, _ := plugin.NewDetacher()
|
||||||
|
d.Detach("sdx", "localhost")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmountDevice(t *testing.T) {
|
||||||
|
plugin, rootDir := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, notSupportedOutput(), unmountDeviceCmd,
|
||||||
|
rootDir+"/mount-dir"),
|
||||||
|
)
|
||||||
|
|
||||||
|
d, _ := plugin.NewDetacher()
|
||||||
|
d.UnmountDevice(rootDir + "/mount-dir")
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Driver calls
|
||||||
|
initCmd = "init"
|
||||||
|
getVolumeNameCmd = "getvolumename"
|
||||||
|
|
||||||
|
isAttached = "isattached"
|
||||||
|
|
||||||
|
attachCmd = "attach"
|
||||||
|
waitForAttachCmd = "waitforattach"
|
||||||
|
mountDeviceCmd = "mountdevice"
|
||||||
|
|
||||||
|
detachCmd = "detach"
|
||||||
|
waitForDetachCmd = "waitfordetach"
|
||||||
|
unmountDeviceCmd = "unmountdevice"
|
||||||
|
|
||||||
|
mountCmd = "mount"
|
||||||
|
unmountCmd = "unmount"
|
||||||
|
|
||||||
|
// Option keys
|
||||||
|
optionFSType = "kubernetes.io/fsType"
|
||||||
|
optionReadWrite = "kubernetes.io/readwrite"
|
||||||
|
optionKeySecret = "kubernetes.io/secret"
|
||||||
|
optionFSGroup = "kubernetes.io/fsGroup"
|
||||||
|
optionMountsDir = "kubernetes.io/mountsDir"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StatusSuccess represents the successful completion of command.
|
||||||
|
StatusSuccess = "Success"
|
||||||
|
// StatusFailed represents that the command failed.
|
||||||
|
StatusFailure = "Failed"
|
||||||
|
// StatusNotSupported represents that the command is not supported.
|
||||||
|
StatusNotSupported = "Not supported"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TimeoutError = fmt.Errorf("Timeout")
|
||||||
|
)
|
||||||
|
|
||||||
|
// DriverCall implements the basic contract between FlexVolume and its driver.
|
||||||
|
// The caller is responsible for providing the required args.
|
||||||
|
type DriverCall struct {
|
||||||
|
Command string
|
||||||
|
Timeout time.Duration
|
||||||
|
plugin *flexVolumePlugin
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *flexVolumePlugin) NewDriverCall(command string) *DriverCall {
|
||||||
|
return plugin.NewDriverCallWithTimeout(command, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *flexVolumePlugin) NewDriverCallWithTimeout(command string, timeout time.Duration) *DriverCall {
|
||||||
|
return &DriverCall{
|
||||||
|
Command: command,
|
||||||
|
Timeout: timeout,
|
||||||
|
plugin: plugin,
|
||||||
|
args: []string{command},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DriverCall) Append(arg string) {
|
||||||
|
dc.args = append(dc.args, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DriverCall) AppendSpec(spec *volume.Spec, host volume.VolumeHost, extraOptions map[string]string) error {
|
||||||
|
optionsForDriver, err := NewOptionsForDriver(spec, host, extraOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(optionsForDriver)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to marshal spec, error: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
dc.Append(string(jsonBytes))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DriverCall) Run() (*DriverStatus, error) {
|
||||||
|
if dc.plugin.isUnsupported(dc.Command) {
|
||||||
|
return nil, errors.New(StatusNotSupported)
|
||||||
|
}
|
||||||
|
execPath := dc.plugin.getExecutable()
|
||||||
|
|
||||||
|
cmd := dc.plugin.runner.Command(execPath, dc.args...)
|
||||||
|
|
||||||
|
timeout := false
|
||||||
|
if dc.Timeout > 0 {
|
||||||
|
timer := time.AfterFunc(dc.Timeout, func() {
|
||||||
|
timeout = true
|
||||||
|
cmd.Stop()
|
||||||
|
})
|
||||||
|
defer timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
output, execErr := cmd.CombinedOutput()
|
||||||
|
if execErr != nil {
|
||||||
|
if timeout {
|
||||||
|
return nil, TimeoutError
|
||||||
|
}
|
||||||
|
_, err := handleCmdResponse(dc.Command, output)
|
||||||
|
if err == nil {
|
||||||
|
glog.Errorf("FlexVolume: driver bug: %s: exec error (%s) but no error in response.", execPath, execErr)
|
||||||
|
return nil, execErr
|
||||||
|
}
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
dc.plugin.unsupported(dc.Command)
|
||||||
|
} else {
|
||||||
|
glog.Warningf("FlexVolume: driver call failed: executable: %s, args: %s, error: %s, output: %s", execPath, dc.args, execErr.Error(), output)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := handleCmdResponse(dc.Command, output)
|
||||||
|
if err != nil {
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
dc.plugin.unsupported(dc.Command)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionsForDriver represents the spec given to the driver.
|
||||||
|
type OptionsForDriver map[string]string
|
||||||
|
|
||||||
|
func NewOptionsForDriver(spec *volume.Spec, host volume.VolumeHost, extraOptions map[string]string) (OptionsForDriver, error) {
|
||||||
|
volSource, readOnly := getVolumeSource(spec)
|
||||||
|
options := map[string]string{}
|
||||||
|
|
||||||
|
options[optionFSType] = volSource.FSType
|
||||||
|
|
||||||
|
if readOnly {
|
||||||
|
options[optionReadWrite] = "ro"
|
||||||
|
} else {
|
||||||
|
options[optionReadWrite] = "rw"
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range extraOptions {
|
||||||
|
options[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range volSource.Options {
|
||||||
|
options[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return OptionsForDriver(options), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverStatus represents the return value of the driver callout.
|
||||||
|
type DriverStatus struct {
|
||||||
|
// Status of the callout. One of "Success", "Failure" or "Not supported".
|
||||||
|
Status string `json:"status"`
|
||||||
|
// Reason for success/failure.
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
// Path to the device attached. This field is valid only for attach calls.
|
||||||
|
// ie: /dev/sdx
|
||||||
|
DevicePath string `json:"device,omitempty"`
|
||||||
|
// Cluster wide unique name of the volume.
|
||||||
|
VolumeName string `json:"volumeName,omitempty"`
|
||||||
|
// Represents volume is attached on the node
|
||||||
|
Attached bool `json:"attached,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCmdNotSupportedErr checks if the error corresponds to command not supported by
|
||||||
|
// driver.
|
||||||
|
func isCmdNotSupportedErr(err error) bool {
|
||||||
|
if err != nil && err.Error() == StatusNotSupported {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCmdResponse processes the command output and returns the appropriate
|
||||||
|
// error code or message.
|
||||||
|
func handleCmdResponse(cmd string, output []byte) (*DriverStatus, error) {
|
||||||
|
var status DriverStatus
|
||||||
|
if err := json.Unmarshal(output, &status); err != nil {
|
||||||
|
glog.Errorf("Failed to unmarshal output for command: %s, output: %s, error: %s", cmd, string(output), err.Error())
|
||||||
|
return nil, err
|
||||||
|
} else if status.Status == StatusNotSupported {
|
||||||
|
glog.V(5).Infof("%s command is not supported by the driver", cmd)
|
||||||
|
return nil, errors.New(status.Status)
|
||||||
|
} else if status.Status != StatusSuccess {
|
||||||
|
errMsg := fmt.Sprintf("%s command failed, status: %s, reason: %s", cmd, status.Status, status.Message)
|
||||||
|
glog.Errorf(errMsg)
|
||||||
|
return nil, fmt.Errorf("%s", errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
|
@ -1,440 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package flexvolume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/util/exec"
|
|
||||||
"k8s.io/kubernetes/pkg/util/mount"
|
|
||||||
utilstrings "k8s.io/kubernetes/pkg/util/strings"
|
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
|
||||||
"k8s.io/kubernetes/pkg/volume/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is the primary entrypoint for volume plugins.
|
|
||||||
func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
|
|
||||||
plugins := []volume.VolumePlugin{}
|
|
||||||
|
|
||||||
files, _ := ioutil.ReadDir(pluginDir)
|
|
||||||
for _, f := range files {
|
|
||||||
// only directories are counted as plugins
|
|
||||||
// and pluginDir/dirname/dirname should be an executable
|
|
||||||
// unless dirname contains '~' for escaping namespace
|
|
||||||
// e.g. dirname = vendor~cifs
|
|
||||||
// then, executable will be pluginDir/dirname/cifs
|
|
||||||
if f.IsDir() {
|
|
||||||
execPath := path.Join(pluginDir, f.Name())
|
|
||||||
plugins = append(plugins, &flexVolumePlugin{driverName: utilstrings.UnescapePluginName(f.Name()), execPath: execPath})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return plugins
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlexVolumePlugin object.
|
|
||||||
type flexVolumePlugin struct {
|
|
||||||
driverName string
|
|
||||||
execPath string
|
|
||||||
host volume.VolumeHost
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the plugin.
|
|
||||||
func (plugin *flexVolumePlugin) Init(host volume.VolumeHost) error {
|
|
||||||
plugin.host = host
|
|
||||||
// call the init script
|
|
||||||
u := &flexVolumeUtil{}
|
|
||||||
return u.init(plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (plugin *flexVolumePlugin) getExecutable() string {
|
|
||||||
parts := strings.Split(plugin.driverName, "/")
|
|
||||||
execName := parts[len(parts)-1]
|
|
||||||
return path.Join(plugin.execPath, execName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (plugin *flexVolumePlugin) GetPluginName() string {
|
|
||||||
return plugin.driverName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (plugin *flexVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
|
||||||
volumeSource, _, err := getVolumeSource(spec)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return volumeSource.Driver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanSupport checks whether the plugin can support the input volume spec.
|
|
||||||
func (plugin *flexVolumePlugin) CanSupport(spec *volume.Spec) bool {
|
|
||||||
source, _, _ := getVolumeSource(spec)
|
|
||||||
return (source != nil) && (source.Driver == plugin.driverName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (plugin *flexVolumePlugin) RequiresRemount() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccessModes gets the allowed access modes for this plugin.
|
|
||||||
func (plugin *flexVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
|
|
||||||
return []v1.PersistentVolumeAccessMode{
|
|
||||||
v1.ReadWriteOnce,
|
|
||||||
v1.ReadOnlyMany,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMounter is the mounter routine to build the volume.
|
|
||||||
func (plugin *flexVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
|
||||||
fv, _, err := getVolumeSource(spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
secrets := make(map[string]string)
|
|
||||||
if fv.SecretRef != nil {
|
|
||||||
kubeClient := plugin.host.GetKubeClient()
|
|
||||||
if kubeClient == nil {
|
|
||||||
return nil, fmt.Errorf("Cannot get kube client")
|
|
||||||
}
|
|
||||||
|
|
||||||
secretName, err := kubeClient.Core().Secrets(pod.Namespace).Get(fv.SecretRef.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Couldn't get secret %v/%v err: %v", pod.Namespace, fv.SecretRef, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for name, data := range secretName.Data {
|
|
||||||
secrets[name] = base64.StdEncoding.EncodeToString(data)
|
|
||||||
glog.V(1).Infof("found flex volume secret info: %s", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return plugin.newMounterInternal(spec, pod, &flexVolumeUtil{}, plugin.host.GetMounter(), exec.New(), secrets)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMounterInternal is the internal mounter routine to build the volume.
|
|
||||||
func (plugin *flexVolumePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, manager flexVolumeManager, mounter mount.Interface, runner exec.Interface, secrets map[string]string) (volume.Mounter, error) {
|
|
||||||
source, _, err := getVolumeSource(spec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &flexVolumeMounter{
|
|
||||||
flexVolumeDisk: &flexVolumeDisk{
|
|
||||||
podUID: pod.UID,
|
|
||||||
podNamespace: pod.Namespace,
|
|
||||||
podName: pod.Name,
|
|
||||||
volName: spec.Name(),
|
|
||||||
driverName: source.Driver,
|
|
||||||
execPath: plugin.getExecutable(),
|
|
||||||
mounter: mounter,
|
|
||||||
plugin: plugin,
|
|
||||||
secrets: secrets,
|
|
||||||
},
|
|
||||||
fsType: source.FSType,
|
|
||||||
readOnly: source.ReadOnly,
|
|
||||||
options: source.Options,
|
|
||||||
runner: runner,
|
|
||||||
manager: manager,
|
|
||||||
blockDeviceMounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: runner},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUnmounter is the unmounter routine to clean the volume.
|
|
||||||
func (plugin *flexVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
|
||||||
return plugin.newUnmounterInternal(volName, podUID, &flexVolumeUtil{}, plugin.host.GetMounter(), exec.New())
|
|
||||||
}
|
|
||||||
|
|
||||||
// newUnmounterInternal is the internal unmounter routine to clean the volume.
|
|
||||||
func (plugin *flexVolumePlugin) newUnmounterInternal(volName string, podUID types.UID, manager flexVolumeManager, mounter mount.Interface, runner exec.Interface) (volume.Unmounter, error) {
|
|
||||||
return &flexVolumeUnmounter{
|
|
||||||
flexVolumeDisk: &flexVolumeDisk{
|
|
||||||
podUID: podUID,
|
|
||||||
volName: volName,
|
|
||||||
driverName: plugin.driverName,
|
|
||||||
execPath: plugin.getExecutable(),
|
|
||||||
mounter: mounter,
|
|
||||||
plugin: plugin,
|
|
||||||
},
|
|
||||||
runner: runner,
|
|
||||||
manager: manager,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (plugin *flexVolumePlugin) ConstructVolumeSpec(volumeName, sourceName string) (*volume.Spec, error) {
|
|
||||||
flexVolume := &v1.Volume{
|
|
||||||
Name: volumeName,
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
FlexVolume: &v1.FlexVolumeSource{
|
|
||||||
Driver: sourceName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return volume.NewSpecFromVolume(flexVolume), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// flexVolume is the disk resource provided by this plugin.
|
|
||||||
type flexVolumeDisk struct {
|
|
||||||
// podUID is the UID of the pod.
|
|
||||||
podUID types.UID
|
|
||||||
// podNamespace is the namespace of the pod.
|
|
||||||
podNamespace string
|
|
||||||
// podName is the name of the pod.
|
|
||||||
podName string
|
|
||||||
// volName is the name of the pod volume.
|
|
||||||
volName string
|
|
||||||
// driverName is the name of the plugin driverName.
|
|
||||||
driverName string
|
|
||||||
// Driver executable used to setup the volume.
|
|
||||||
execPath string
|
|
||||||
// mounter provides the interface that is used to mount the actual
|
|
||||||
// block device.
|
|
||||||
mounter mount.Interface
|
|
||||||
// secret for the volume.
|
|
||||||
secrets map[string]string
|
|
||||||
plugin *flexVolumePlugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlexVolumeUnmounter is the disk that will be cleaned by this plugin.
|
|
||||||
type flexVolumeUnmounter struct {
|
|
||||||
*flexVolumeDisk
|
|
||||||
// Runner used to teardown the volume.
|
|
||||||
runner exec.Interface
|
|
||||||
// manager is the utility interface that provides API calls to the
|
|
||||||
// driverName to setup & teardown disks
|
|
||||||
manager flexVolumeManager
|
|
||||||
volume.MetricsNil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlexVolumeMounter is the disk that will be exposed by this plugin.
|
|
||||||
type flexVolumeMounter struct {
|
|
||||||
*flexVolumeDisk
|
|
||||||
// fsType is the type of the filesystem to create on the volume.
|
|
||||||
fsType string
|
|
||||||
// readOnly specifies whether the disk will be setup as read-only.
|
|
||||||
readOnly bool
|
|
||||||
// options are the extra params that will be passed to the plugin
|
|
||||||
// driverName.
|
|
||||||
options map[string]string
|
|
||||||
// Runner used to setup the volume.
|
|
||||||
runner exec.Interface
|
|
||||||
// manager is the utility interface that provides API calls to the
|
|
||||||
// driverName to setup & teardown disks
|
|
||||||
manager flexVolumeManager
|
|
||||||
// blockDeviceMounter provides the interface to create filesystem if the
|
|
||||||
// filesystem doesn't exist.
|
|
||||||
blockDeviceMounter mount.Interface
|
|
||||||
volume.MetricsNil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUp creates new directory.
|
|
||||||
func (f *flexVolumeMounter) SetUp(fsGroup *int64) error {
|
|
||||||
return f.SetUpAt(f.GetPath(), fsGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAttributes get the flex volume attributes. The attributes will be queried
|
|
||||||
// using plugin callout after we finalize the callout syntax.
|
|
||||||
func (f flexVolumeMounter) GetAttributes() volume.Attributes {
|
|
||||||
return volume.Attributes{
|
|
||||||
ReadOnly: f.readOnly,
|
|
||||||
Managed: false,
|
|
||||||
SupportsSELinux: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks prior to mount operations to verify that the required components (binaries, etc.)
|
|
||||||
// to mount the volume are available on the underlying node.
|
|
||||||
// If not, it returns an error
|
|
||||||
func (f *flexVolumeMounter) CanMount() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// flexVolumeManager is the abstract interface to flex volume ops.
|
|
||||||
type flexVolumeManager interface {
|
|
||||||
// Attaches the disk to the kubelet's host machine.
|
|
||||||
attach(mounter *flexVolumeMounter) (string, error)
|
|
||||||
// Detaches the disk from the kubelet's host machine.
|
|
||||||
detach(unmounter *flexVolumeUnmounter, dir string) error
|
|
||||||
// Mounts the disk on the Kubelet's host machine.
|
|
||||||
mount(mounter *flexVolumeMounter, mnt, dir string) error
|
|
||||||
// Unmounts the disk from the Kubelet's host machine.
|
|
||||||
unmount(unounter *flexVolumeUnmounter, dir string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUpAt creates new directory.
|
|
||||||
func (f *flexVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
|
||||||
|
|
||||||
notmnt, err := f.blockDeviceMounter.IsLikelyNotMountPoint(dir)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
glog.Errorf("Cannot validate mount point: %s %v", dir, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !notmnt {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.options == nil {
|
|
||||||
f.options = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.options[optionFSType] = f.fsType
|
|
||||||
|
|
||||||
// Read write mount options.
|
|
||||||
if f.readOnly {
|
|
||||||
f.options[optionReadWrite] = "ro"
|
|
||||||
} else {
|
|
||||||
f.options[optionReadWrite] = "rw"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract secret and pass it as options.
|
|
||||||
for name, secret := range f.secrets {
|
|
||||||
f.options[optionKeySecret+"/"+name] = secret
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(4).Infof("attempting to attach volume: %s with options %v", f.volName, f.options)
|
|
||||||
device, err := f.manager.attach(f)
|
|
||||||
if err != nil {
|
|
||||||
if !isCmdNotSupportedErr(err) {
|
|
||||||
glog.Errorf("failed to attach volume: %s", f.volName)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Attach not supported or required. Continue to mount.
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(4).Infof("attempting to mount volume: %s", f.volName)
|
|
||||||
if err := f.manager.mount(f, device, dir); err != nil {
|
|
||||||
if !isCmdNotSupportedErr(err) {
|
|
||||||
glog.Errorf("failed to mount volume: %s", f.volName)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options := make([]string, 0)
|
|
||||||
|
|
||||||
if f.readOnly {
|
|
||||||
options = append(options, "ro")
|
|
||||||
} else {
|
|
||||||
options = append(options, "rw")
|
|
||||||
}
|
|
||||||
// Extract secret and pass it as options.
|
|
||||||
for name, secret := range f.secrets {
|
|
||||||
f.options[optionKeySecret+"/"+name] = secret
|
|
||||||
}
|
|
||||||
|
|
||||||
os.MkdirAll(dir, 0750)
|
|
||||||
// Mount not supported by driver. Use core mounting logic.
|
|
||||||
glog.V(4).Infof("attempting to mount the volume: %s to device: %s", f.volName, device)
|
|
||||||
err = f.blockDeviceMounter.Mount(string(device), dir, f.fsType, options)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("failed to mount the volume: %s to device: %s, error: %v", f.volName, device, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(4).Infof("Successfully mounted volume: %s on device: %s", f.volName, device)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReadOnly returns true if the volume is read only.
|
|
||||||
func (f *flexVolumeMounter) IsReadOnly() bool {
|
|
||||||
return f.readOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPathFromPlugin gets the actual volume mount directory based on plugin.
|
|
||||||
func (f *flexVolumeDisk) GetPath() string {
|
|
||||||
name := f.driverName
|
|
||||||
return f.plugin.host.GetPodVolumeDir(f.podUID, utilstrings.EscapeQualifiedNameForDisk(name), f.volName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TearDown simply deletes everything in the directory.
|
|
||||||
func (f *flexVolumeUnmounter) TearDown() error {
|
|
||||||
path := f.GetPath()
|
|
||||||
return f.TearDownAt(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TearDownAt simply deletes everything in the directory.
|
|
||||||
func (f *flexVolumeUnmounter) TearDownAt(dir string) error {
|
|
||||||
if pathExists, pathErr := util.PathExists(dir); pathErr != nil {
|
|
||||||
return fmt.Errorf("Error checking if path exists: %v", pathErr)
|
|
||||||
} else if !pathExists {
|
|
||||||
glog.Warningf("Warning: Unmount skipped because path does not exist: %v", dir)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
notmnt, err := f.mounter.IsLikelyNotMountPoint(dir)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Error checking mount point %s, error: %v", dir, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if notmnt {
|
|
||||||
return os.Remove(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
device, refCount, err := mount.GetDeviceNameFromMount(f.mounter, dir)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to get reference count for volume: %s", dir)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.manager.unmount(f, dir); err != nil {
|
|
||||||
if !isCmdNotSupportedErr(err) {
|
|
||||||
glog.Errorf("Failed to unmount volume %s", f.volName)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Unmount not supported by the driver. Use core unmount logic.
|
|
||||||
if err := f.mounter.Unmount(dir); err != nil {
|
|
||||||
glog.Errorf("Failed to unmount volume: %s, error: %v", dir, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if refCount == 1 {
|
|
||||||
if err := f.manager.detach(f, device); err != nil {
|
|
||||||
if !isCmdNotSupportedErr(err) {
|
|
||||||
glog.Errorf("Failed to teardown volume: %s, error: %v", dir, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Teardown not supported by driver. Unmount is good enough.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notmnt, err = f.mounter.IsLikelyNotMountPoint(dir)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Error checking mount point %s, error: %v", dir, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if notmnt {
|
|
||||||
return os.Remove(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVolumeSource(spec *volume.Spec) (*v1.FlexVolumeSource, bool, error) {
|
|
||||||
if spec.Volume != nil && spec.Volume.FlexVolume != nil {
|
|
||||||
return spec.Volume.FlexVolume, spec.Volume.FlexVolume.ReadOnly, nil
|
|
||||||
} else if spec.PersistentVolume != nil &&
|
|
||||||
spec.PersistentVolume.Spec.FlexVolume != nil {
|
|
||||||
return spec.PersistentVolume.Spec.FlexVolume, spec.ReadOnly, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false, fmt.Errorf("Spec does not reference a Flex volume type")
|
|
||||||
}
|
|
|
@ -18,19 +18,14 @@ package flexvolume
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
utiltesting "k8s.io/client-go/util/testing"
|
utiltesting "k8s.io/client-go/util/testing"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
"k8s.io/kubernetes/pkg/util/exec"
|
|
||||||
"k8s.io/kubernetes/pkg/util/mount"
|
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
)
|
)
|
||||||
|
@ -55,21 +50,22 @@ elif [ "$1" == "detach" -a $# -eq 2 ]; then
|
||||||
"status": "Success"
|
"status": "Success"
|
||||||
}'
|
}'
|
||||||
exit 0
|
exit 0
|
||||||
elif [ "$1" == "mount" -a $# -eq 4 ]; then
|
elif [ "$1" == "getvolumename" -a $# -eq 4 ]; then
|
||||||
echo -n '{
|
echo -n '{
|
||||||
"status": "Not supported"
|
"status": "Success",
|
||||||
|
"volume": "fakevolume"
|
||||||
}'
|
}'
|
||||||
exit 0
|
exit 0
|
||||||
elif [ "$1" == "unmount" -a $# -eq 2 ]; then
|
elif [ "$1" == "isattached" -a $# -eq 2 ]; then
|
||||||
echo -n '{
|
echo -n '{
|
||||||
"status": "Not supported"
|
"status": "Success",
|
||||||
|
"attached": true
|
||||||
}'
|
}'
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n '{
|
echo -n '{
|
||||||
"status": "Failure",
|
"status": "Not supported"
|
||||||
"reason": "Invalid usage"
|
|
||||||
}'
|
}'
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
@ -85,14 +81,10 @@ if [ "$1" == "init" -a $# -eq 1 ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$1" == "attach" -a $# -eq 2 ]; then
|
if [ "$1" == "getvolumename" -a $# -eq 2 ]; then
|
||||||
echo -n '{
|
echo -n '{
|
||||||
"status": "Not supported"
|
"status": "Success",
|
||||||
}'
|
"volumeName": "fakevolume"
|
||||||
exit 0
|
|
||||||
elif [ "$1" == "detach" -a $# -eq 2 ]; then
|
|
||||||
echo -n '{
|
|
||||||
"status": "Not supported"
|
|
||||||
}'
|
}'
|
||||||
exit 0
|
exit 0
|
||||||
elif [ "$1" == "mount" -a $# -eq 4 ]; then
|
elif [ "$1" == "mount" -a $# -eq 4 ]; then
|
||||||
|
@ -126,8 +118,7 @@ elif [ "$1" == "unmount" -a $# -eq 2 ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n '{
|
echo -n '{
|
||||||
"status": "Failure",
|
"status": "Not Supported"
|
||||||
"reason": "Invalid usage"
|
|
||||||
}'
|
}'
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
@ -230,189 +221,3 @@ func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAcc
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTestPluginAttachDetach(t *testing.T, spec *volume.Spec, tmpDir string) {
|
|
||||||
plugMgr := volume.VolumePluginMgr{}
|
|
||||||
installPluginUnderTest(t, "kubernetes.io", "fakeAttacher", tmpDir, execScriptTempl1, nil)
|
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
|
||||||
plugin, err := plugMgr.FindPluginByName("kubernetes.io/fakeAttacher")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Can't find the plugin by name")
|
|
||||||
}
|
|
||||||
fake := &mount.FakeMounter{}
|
|
||||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
|
|
||||||
secretMap := make(map[string]string)
|
|
||||||
secretMap["flexsecret"] = base64.StdEncoding.EncodeToString([]byte("foo"))
|
|
||||||
mounter, err := plugin.(*flexVolumePlugin).newMounterInternal(spec, pod, &flexVolumeUtil{}, fake, exec.New(), secretMap)
|
|
||||||
volumePath := mounter.GetPath()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to make a new Mounter: %v", err)
|
|
||||||
}
|
|
||||||
if mounter == nil {
|
|
||||||
t.Errorf("Got a nil Mounter")
|
|
||||||
}
|
|
||||||
path := mounter.GetPath()
|
|
||||||
expectedPath := fmt.Sprintf("%s/pods/poduid/volumes/kubernetes.io~fakeAttacher/vol1", tmpDir)
|
|
||||||
if path != expectedPath {
|
|
||||||
t.Errorf("Unexpected path, expected %q, got: %q", expectedPath, path)
|
|
||||||
}
|
|
||||||
if err := mounter.SetUp(nil); err != nil {
|
|
||||||
t.Errorf("Expected success, got: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(volumePath); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
t.Errorf("SetUp() failed, volume path not created: %s", volumePath)
|
|
||||||
} else {
|
|
||||||
t.Errorf("SetUp() failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf("Setup successful")
|
|
||||||
if mounter.(*flexVolumeMounter).readOnly {
|
|
||||||
t.Errorf("The volume source should not be read-only and it is.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fake.Log) != 1 {
|
|
||||||
t.Errorf("Mount was not called exactly one time. It was called %d times.", len(fake.Log))
|
|
||||||
} else {
|
|
||||||
if fake.Log[0].Action != mount.FakeActionMount {
|
|
||||||
t.Errorf("Unexpected mounter action: %#v", fake.Log[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fake.ResetLog()
|
|
||||||
|
|
||||||
unmounter, err := plugin.(*flexVolumePlugin).newUnmounterInternal("vol1", types.UID("poduid"), &flexVolumeUtil{}, fake, exec.New())
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to make a new Unmounter: %v", err)
|
|
||||||
}
|
|
||||||
if unmounter == nil {
|
|
||||||
t.Errorf("Got a nil Unmounter")
|
|
||||||
}
|
|
||||||
if err := unmounter.TearDown(); err != nil {
|
|
||||||
t.Errorf("Expected success, got: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(volumePath); err == nil {
|
|
||||||
t.Errorf("TearDown() failed, volume path still exists: %s", volumePath)
|
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
t.Errorf("SetUp() failed: %v", err)
|
|
||||||
}
|
|
||||||
if len(fake.Log) != 1 {
|
|
||||||
t.Errorf("Unmount was not called exactly one time. It was called %d times.", len(fake.Log))
|
|
||||||
} else {
|
|
||||||
if fake.Log[0].Action != mount.FakeActionUnmount {
|
|
||||||
t.Errorf("Unexpected mounter action: %#v", fake.Log[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fake.ResetLog()
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestPluginMountUnmount(t *testing.T, spec *volume.Spec, tmpDir string) {
|
|
||||||
tmpDir, err := utiltesting.MkTmpdir("flexvolume_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating temp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
plugMgr := volume.VolumePluginMgr{}
|
|
||||||
installPluginUnderTest(t, "kubernetes.io", "fakeMounter", tmpDir, execScriptTempl2, nil)
|
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
|
||||||
plugin, err := plugMgr.FindPluginByName("kubernetes.io/fakeMounter")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Can't find the plugin by name")
|
|
||||||
}
|
|
||||||
fake := &mount.FakeMounter{}
|
|
||||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
|
|
||||||
// Use nil secret to test for nil secret case.
|
|
||||||
mounter, err := plugin.(*flexVolumePlugin).newMounterInternal(spec, pod, &flexVolumeUtil{}, fake, exec.New(), nil)
|
|
||||||
volumePath := mounter.GetPath()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to make a new Mounter: %v", err)
|
|
||||||
}
|
|
||||||
if mounter == nil {
|
|
||||||
t.Errorf("Got a nil Mounter")
|
|
||||||
}
|
|
||||||
path := mounter.GetPath()
|
|
||||||
expectedPath := fmt.Sprintf("%s/pods/poduid/volumes/kubernetes.io~fakeMounter/vol1", tmpDir)
|
|
||||||
if path != expectedPath {
|
|
||||||
t.Errorf("Unexpected path, expected %q, got: %q", expectedPath, path)
|
|
||||||
}
|
|
||||||
if err := mounter.SetUp(nil); err != nil {
|
|
||||||
t.Errorf("Expected success, got: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(volumePath); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
t.Errorf("SetUp() failed, volume path not created: %s", volumePath)
|
|
||||||
} else {
|
|
||||||
t.Errorf("SetUp() failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf("Setup successful")
|
|
||||||
if mounter.(*flexVolumeMounter).readOnly {
|
|
||||||
t.Errorf("The volume source should not be read-only and it is.")
|
|
||||||
}
|
|
||||||
|
|
||||||
unmounter, err := plugin.(*flexVolumePlugin).newUnmounterInternal("vol1", types.UID("poduid"), &flexVolumeUtil{}, fake, exec.New())
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to make a new Unmounter: %v", err)
|
|
||||||
}
|
|
||||||
if unmounter == nil {
|
|
||||||
t.Errorf("Got a nil Unmounter")
|
|
||||||
}
|
|
||||||
if err := unmounter.TearDown(); err != nil {
|
|
||||||
t.Errorf("Expected success, got: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(volumePath); err == nil {
|
|
||||||
t.Errorf("TearDown() failed, volume path still exists: %s", volumePath)
|
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
t.Errorf("SetUp() failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPluginVolumeAttacher(t *testing.T) {
|
|
||||||
tmpDir, err := utiltesting.MkTmpdir("flexvolume_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating temp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
vol := &v1.Volume{
|
|
||||||
Name: "vol1",
|
|
||||||
VolumeSource: v1.VolumeSource{FlexVolume: &v1.FlexVolumeSource{Driver: "kubernetes.io/fakeAttacher", ReadOnly: false}},
|
|
||||||
}
|
|
||||||
doTestPluginAttachDetach(t, volume.NewSpecFromVolume(vol), tmpDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPluginVolumeMounter(t *testing.T) {
|
|
||||||
tmpDir, err := utiltesting.MkTmpdir("flexvolume_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating temp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
vol := &v1.Volume{
|
|
||||||
Name: "vol1",
|
|
||||||
VolumeSource: v1.VolumeSource{FlexVolume: &v1.FlexVolumeSource{Driver: "kubernetes.io/fakeMounter", ReadOnly: false}},
|
|
||||||
}
|
|
||||||
doTestPluginMountUnmount(t, volume.NewSpecFromVolume(vol), tmpDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPluginPersistentVolume(t *testing.T) {
|
|
||||||
tmpDir, err := utiltesting.MkTmpdir("flexvolume_test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating temp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir)
|
|
||||||
|
|
||||||
vol := &v1.PersistentVolume{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "vol1",
|
|
||||||
},
|
|
||||||
Spec: v1.PersistentVolumeSpec{
|
|
||||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
|
||||||
FlexVolume: &v1.FlexVolumeSource{Driver: "kubernetes.io/fakeAttacher", ReadOnly: false},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
doTestPluginAttachDetach(t, volume.NewSpecFromPersistentVolume(vol, false), tmpDir)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,221 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package flexvolume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/util/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
initCmd = "init"
|
|
||||||
attachCmd = "attach"
|
|
||||||
detachCmd = "detach"
|
|
||||||
mountCmd = "mount"
|
|
||||||
unmountCmd = "unmount"
|
|
||||||
|
|
||||||
optionFSType = "kubernetes.io/fsType"
|
|
||||||
optionReadWrite = "kubernetes.io/readwrite"
|
|
||||||
optionKeySecret = "kubernetes.io/secret"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StatusSuccess represents the successful completion of command.
|
|
||||||
StatusSuccess = "Success"
|
|
||||||
// StatusFailed represents that the command failed.
|
|
||||||
StatusFailure = "Failed"
|
|
||||||
// StatusNotSupported represents that the command is not supported.
|
|
||||||
StatusNotSupported = "Not supported"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlexVolumeDriverStatus represents the return value of the driver callout.
|
|
||||||
type FlexVolumeDriverStatus struct {
|
|
||||||
// Status of the callout. One of "Success" or "Failure".
|
|
||||||
Status string
|
|
||||||
// Message is the reason for failure.
|
|
||||||
Message string
|
|
||||||
// Device assigned by the driver.
|
|
||||||
Device string `json:"device"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// flexVolumeUtil is the utility structure to setup and teardown devices from
|
|
||||||
// the host.
|
|
||||||
type flexVolumeUtil struct{}
|
|
||||||
|
|
||||||
// isCmdNotSupportedErr checks if the error corresponds to command not supported by
|
|
||||||
// driver.
|
|
||||||
func isCmdNotSupportedErr(err error) bool {
|
|
||||||
if err.Error() == StatusNotSupported {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleCmdResponse processes the command output and returns the appropriate
|
|
||||||
// error code or message.
|
|
||||||
func handleCmdResponse(cmd string, output []byte) (*FlexVolumeDriverStatus, error) {
|
|
||||||
var status FlexVolumeDriverStatus
|
|
||||||
if err := json.Unmarshal(output, &status); err != nil {
|
|
||||||
glog.Errorf("Failed to unmarshal output for command: %s, output: %s, error: %s", cmd, output, err.Error())
|
|
||||||
return nil, err
|
|
||||||
} else if status.Status == StatusNotSupported {
|
|
||||||
glog.V(5).Infof("%s command is not supported by the driver", cmd)
|
|
||||||
return nil, errors.New(status.Status)
|
|
||||||
} else if status.Status != StatusSuccess {
|
|
||||||
errMsg := fmt.Sprintf("%s command failed, status: %s, reason: %s", cmd, status.Status, status.Message)
|
|
||||||
glog.Errorf(errMsg)
|
|
||||||
return nil, fmt.Errorf("%s", errMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &status, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// init initializes the plugin.
|
|
||||||
func (u *flexVolumeUtil) init(plugin *flexVolumePlugin) error {
|
|
||||||
// call the init script
|
|
||||||
output, err := exec.New().Command(plugin.getExecutable(), initCmd).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to init driver: %s, error: %s", plugin.driverName, err.Error())
|
|
||||||
_, err := handleCmdResponse(initCmd, output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(5).Infof("Successfully initialized driver %s", plugin.driverName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach exposes a volume on the host.
|
|
||||||
func (u *flexVolumeUtil) attach(f *flexVolumeMounter) (string, error) {
|
|
||||||
execPath := f.execPath
|
|
||||||
|
|
||||||
var options string
|
|
||||||
if f.options != nil {
|
|
||||||
out, err := json.Marshal(f.options)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to marshal plugin options, error: %s", err.Error())
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if len(out) != 0 {
|
|
||||||
options = string(out)
|
|
||||||
} else {
|
|
||||||
options = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := f.runner.Command(execPath, attachCmd, options)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to attach volume %s, output: %s, error: %s", f.volName, output, err.Error())
|
|
||||||
_, err := handleCmdResponse(attachCmd, output)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := handleCmdResponse(attachCmd, output)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("Successfully attached volume %s on device: %s", f.volName, status.Device)
|
|
||||||
|
|
||||||
return status.Device, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detach detaches a volume from the host.
|
|
||||||
func (u *flexVolumeUtil) detach(f *flexVolumeUnmounter, mntDevice string) error {
|
|
||||||
execPath := f.execPath
|
|
||||||
|
|
||||||
// Executable provider command.
|
|
||||||
cmd := f.runner.Command(execPath, detachCmd, mntDevice)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to detach volume %s, output: %s, error: %s", f.volName, output, err.Error())
|
|
||||||
_, err := handleCmdResponse(detachCmd, output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = handleCmdResponse(detachCmd, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("Successfully detached volume %s on device: %s", f.volName, mntDevice)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount mounts the volume on the host.
|
|
||||||
func (u *flexVolumeUtil) mount(f *flexVolumeMounter, mntDevice, dir string) error {
|
|
||||||
execPath := f.execPath
|
|
||||||
|
|
||||||
var options string
|
|
||||||
if f.options != nil {
|
|
||||||
out, err := json.Marshal(f.options)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to marshal plugin options, error: %s", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(out) != 0 {
|
|
||||||
options = string(out)
|
|
||||||
} else {
|
|
||||||
options = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executable provider command.
|
|
||||||
cmd := f.runner.Command(execPath, mountCmd, dir, mntDevice, options)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to mount volume %s, output: %s, error: %s", f.volName, output, err.Error())
|
|
||||||
_, err := handleCmdResponse(mountCmd, output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = handleCmdResponse(mountCmd, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("Successfully mounted volume %s on dir: %s", f.volName, dir)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmount unmounts the volume on the host.
|
|
||||||
func (u *flexVolumeUtil) unmount(f *flexVolumeUnmounter, dir string) error {
|
|
||||||
execPath := f.execPath
|
|
||||||
|
|
||||||
// Executable provider command.
|
|
||||||
cmd := f.runner.Command(execPath, unmountCmd, dir)
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to unmount volume %s, output: %s, error: %s", f.volName, output, err.Error())
|
|
||||||
_, err := handleCmdResponse(unmountCmd, output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = handleCmdResponse(unmountCmd, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("Successfully unmounted volume %s on dir: %s", f.volName, dir)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mounterDefaults flexVolumeMounter
|
||||||
|
|
||||||
|
// SetUpAt is part of the volume.Mounter interface.
|
||||||
|
// This implementation relies on the attacher's device mount path and does a bind mount to dir.
|
||||||
|
func (f *mounterDefaults) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
|
glog.Warning(logPrefix(f.plugin), "using default SetUpAt to ", dir)
|
||||||
|
|
||||||
|
a, err := f.plugin.NewAttacher()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("NewAttacher failed: %v", err)
|
||||||
|
}
|
||||||
|
src, err := a.GetDeviceMountPath(f.spec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetDeviceMountPath failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := doMount(f.mounter, src, dir, "auto", []string{"bind"}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the default volume attributes.
|
||||||
|
func (f *mounterDefaults) GetAttributes() volume.Attributes {
|
||||||
|
glog.V(5).Infof(logPrefix(f.plugin), "using default GetAttributes")
|
||||||
|
return volume.Attributes{
|
||||||
|
ReadOnly: f.readOnly,
|
||||||
|
Managed: !f.readOnly,
|
||||||
|
SupportsSELinux: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *mounterDefaults) CanMount() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlexVolumeMounter is the disk that will be exposed by this plugin.
|
||||||
|
type flexVolumeMounter struct {
|
||||||
|
*flexVolume
|
||||||
|
// Runner used to setup the volume.
|
||||||
|
runner exec.Interface
|
||||||
|
// blockDeviceMounter provides the interface to create filesystem if the
|
||||||
|
// filesystem doesn't exist.
|
||||||
|
blockDeviceMounter mount.Interface
|
||||||
|
// the considered volume spec
|
||||||
|
spec *volume.Spec
|
||||||
|
readOnly bool
|
||||||
|
volume.MetricsNil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ volume.Mounter = &flexVolumeMounter{}
|
||||||
|
|
||||||
|
// Mounter interface
|
||||||
|
|
||||||
|
// SetUp creates new directory.
|
||||||
|
func (f *flexVolumeMounter) SetUp(fsGroup *int64) error {
|
||||||
|
return f.SetUpAt(f.GetPath(), fsGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpAt creates new directory.
|
||||||
|
func (f *flexVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
|
// Mount only once.
|
||||||
|
alreadyMounted, err := prepareForMount(f.mounter, dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if alreadyMounted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
call := f.plugin.NewDriverCall(mountCmd)
|
||||||
|
|
||||||
|
// Interface parameters
|
||||||
|
call.Append(dir)
|
||||||
|
|
||||||
|
extraOptions := make(map[string]string)
|
||||||
|
|
||||||
|
// Extract secret and pass it as options.
|
||||||
|
if err := addSecretsToOptions(extraOptions, f.spec, f.podNamespace, f.driverName, f.plugin.host); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicit parameters
|
||||||
|
if fsGroup != nil {
|
||||||
|
extraOptions[optionFSGroup] = strconv.FormatInt(*fsGroup, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
call.AppendSpec(f.spec, f.plugin.host, extraOptions)
|
||||||
|
|
||||||
|
_, err = call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
err = (*mounterDefaults)(f).SetUpAt(dir, fsGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.readOnly {
|
||||||
|
volume.SetVolumeOwnership(f, fsGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAttributes get the flex volume attributes. The attributes will be queried
|
||||||
|
// using plugin callout after we finalize the callout syntax.
|
||||||
|
func (f *flexVolumeMounter) GetAttributes() volume.Attributes {
|
||||||
|
return (*mounterDefaults)(f).GetAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flexVolumeMounter) CanMount() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetUpAt(t *testing.T) {
|
||||||
|
spec := fakeVolumeSpec()
|
||||||
|
pod := &v1.Pod{}
|
||||||
|
mounter := &mount.FakeMounter{}
|
||||||
|
|
||||||
|
plugin, rootDir := testPlugin()
|
||||||
|
plugin.unsupportedCommands = []string{"unsupportedCmd"}
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
// first call without fsGroup
|
||||||
|
assertDriverCall(t, successOutput(), mountCmd, rootDir+"/mount-dir",
|
||||||
|
specJson(plugin, spec, nil)),
|
||||||
|
|
||||||
|
// second test has fsGroup
|
||||||
|
assertDriverCall(t, notSupportedOutput(), mountCmd, rootDir+"/mount-dir",
|
||||||
|
specJson(plugin, spec, map[string]string{
|
||||||
|
optionFSGroup: "42",
|
||||||
|
})),
|
||||||
|
assertDriverCall(t, fakeVolumeNameOutput("sdx"), getVolumeNameCmd,
|
||||||
|
specJson(plugin, spec, nil)),
|
||||||
|
)
|
||||||
|
|
||||||
|
m, _ := plugin.newMounterInternal(spec, pod, mounter, plugin.runner)
|
||||||
|
m.SetUpAt(rootDir+"/mount-dir", nil)
|
||||||
|
|
||||||
|
fsGroup := int64(42)
|
||||||
|
m.SetUpAt(rootDir+"/mount-dir", &fsGroup)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pluginDefaults flexVolumePlugin
|
||||||
|
|
||||||
|
func logPrefix(plugin *flexVolumePlugin) string {
|
||||||
|
return "flexVolume driver " + plugin.driverName + ": "
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *pluginDefaults) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||||
|
glog.Warning(logPrefix((*flexVolumePlugin)(plugin)), "using default GetVolumeName for volume ", spec.Name)
|
||||||
|
return spec.Name(), nil
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
api "k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
utilstrings "k8s.io/kubernetes/pkg/util/strings"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
const flexVolumePluginName = "kubernetes.io/flexvolume"
|
||||||
|
|
||||||
|
// FlexVolumePlugin object.
|
||||||
|
type flexVolumePlugin struct {
|
||||||
|
driverName string
|
||||||
|
execPath string
|
||||||
|
host volume.VolumeHost
|
||||||
|
runner exec.Interface
|
||||||
|
|
||||||
|
sync.Mutex
|
||||||
|
unsupportedCommands []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ volume.AttachableVolumePlugin = &flexVolumePlugin{}
|
||||||
|
var _ volume.PersistentVolumePlugin = &flexVolumePlugin{}
|
||||||
|
|
||||||
|
// Init is part of the volume.VolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) Init(host volume.VolumeHost) error {
|
||||||
|
plugin.host = host
|
||||||
|
// call the init script
|
||||||
|
call := plugin.NewDriverCall(initCmd)
|
||||||
|
_, err := call.Run()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *flexVolumePlugin) getExecutable() string {
|
||||||
|
parts := strings.Split(plugin.driverName, "/")
|
||||||
|
execName := parts[len(parts)-1]
|
||||||
|
return path.Join(plugin.execPath, execName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name is part of the volume.VolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) GetPluginName() string {
|
||||||
|
return plugin.driverName
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVolumeName is part of the volume.VolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||||
|
call := plugin.NewDriverCall(getVolumeNameCmd)
|
||||||
|
call.AppendSpec(spec, plugin.host, nil)
|
||||||
|
|
||||||
|
status, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
return (*pluginDefaults)(plugin).GetVolumeName(spec)
|
||||||
|
} else if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return utilstrings.EscapeQualifiedNameForDisk(status.VolumeName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanSupport is part of the volume.VolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) CanSupport(spec *volume.Spec) bool {
|
||||||
|
source, _ := getVolumeSource(spec)
|
||||||
|
return (source != nil) && (source.Driver == plugin.driverName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiresRemount is part of the volume.VolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) RequiresRemount() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessModes gets the allowed access modes for this plugin.
|
||||||
|
func (plugin *flexVolumePlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
|
||||||
|
return []api.PersistentVolumeAccessMode{
|
||||||
|
api.ReadWriteOnce,
|
||||||
|
api.ReadOnlyMany,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMounter is part of the volume.VolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
||||||
|
return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(), plugin.runner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMounterInternal is the internal mounter routine to build the volume.
|
||||||
|
func (plugin *flexVolumePlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface, runner exec.Interface) (volume.Mounter, error) {
|
||||||
|
source, readOnly := getVolumeSource(spec)
|
||||||
|
return &flexVolumeMounter{
|
||||||
|
flexVolume: &flexVolume{
|
||||||
|
driverName: source.Driver,
|
||||||
|
execPath: plugin.getExecutable(),
|
||||||
|
mounter: mounter,
|
||||||
|
plugin: plugin,
|
||||||
|
podUID: pod.UID,
|
||||||
|
podNamespace: pod.Namespace,
|
||||||
|
volName: spec.Name(),
|
||||||
|
},
|
||||||
|
runner: runner,
|
||||||
|
spec: spec,
|
||||||
|
readOnly: readOnly,
|
||||||
|
blockDeviceMounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: runner},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnmounter is part of the volume.VolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
||||||
|
return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(), plugin.runner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newUnmounterInternal is the internal unmounter routine to clean the volume.
|
||||||
|
func (plugin *flexVolumePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface, runner exec.Interface) (volume.Unmounter, error) {
|
||||||
|
return &flexVolumeUnmounter{
|
||||||
|
flexVolume: &flexVolume{
|
||||||
|
driverName: plugin.driverName,
|
||||||
|
execPath: plugin.getExecutable(),
|
||||||
|
mounter: mounter,
|
||||||
|
plugin: plugin,
|
||||||
|
podUID: podUID,
|
||||||
|
volName: volName,
|
||||||
|
},
|
||||||
|
runner: runner,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAttacher is part of the volume.AttachableVolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) NewAttacher() (volume.Attacher, error) {
|
||||||
|
return &flexVolumeAttacher{plugin}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDetacher is part of the volume.AttachableVolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) NewDetacher() (volume.Detacher, error) {
|
||||||
|
return &flexVolumeDetacher{plugin}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstructVolumeSpec is part of the volume.AttachableVolumePlugin interface.
|
||||||
|
func (plugin *flexVolumePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
||||||
|
flexVolume := &api.Volume{
|
||||||
|
Name: volumeName,
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
FlexVolume: &api.FlexVolumeSource{
|
||||||
|
Driver: plugin.driverName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return volume.NewSpecFromVolume(flexVolume), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the given commands as unsupported.
|
||||||
|
func (plugin *flexVolumePlugin) unsupported(commands ...string) {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
|
plugin.unsupportedCommands = append(plugin.unsupportedCommands, commands...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true iff the given command is known to be unsupported.
|
||||||
|
func (plugin *flexVolumePlugin) isUnsupported(command string) bool {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
|
for _, unsupportedCommand := range plugin.unsupportedCommands {
|
||||||
|
if command == unsupportedCommand {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *flexVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
|
||||||
|
mounter := plugin.host.GetMounter()
|
||||||
|
return mount.GetMountRefs(mounter, deviceMountPath)
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInit(t *testing.T) {
|
||||||
|
plugin, _ := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, successOutput(), "init"),
|
||||||
|
)
|
||||||
|
plugin.Init(plugin.host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeVolumeNameOutput(name string) exec.FakeCombinedOutputAction {
|
||||||
|
return fakeResultOutput(&DriverStatus{
|
||||||
|
Status: StatusSuccess,
|
||||||
|
VolumeName: name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVolumeName(t *testing.T) {
|
||||||
|
spec := fakeVolumeSpec()
|
||||||
|
plugin, _ := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, fakeVolumeNameOutput("/dev/sdx"), getVolumeNameCmd,
|
||||||
|
specJson(plugin, spec, nil)),
|
||||||
|
)
|
||||||
|
|
||||||
|
name, err := plugin.GetVolumeName(spec)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("GetVolumeName() failed: %v", err)
|
||||||
|
}
|
||||||
|
expectedName := "~dev~sdx"
|
||||||
|
if name != expectedName {
|
||||||
|
t.Errorf("GetVolumeName() returned %v instead of %v", name, expectedName)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
utilstrings "k8s.io/kubernetes/pkg/util/strings"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is the primary entrypoint for volume plugins.
|
||||||
|
func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
|
||||||
|
plugins := []volume.VolumePlugin{}
|
||||||
|
|
||||||
|
files, _ := ioutil.ReadDir(pluginDir)
|
||||||
|
for _, f := range files {
|
||||||
|
// only directories are counted as plugins
|
||||||
|
// and pluginDir/dirname/dirname should be an executable
|
||||||
|
// unless dirname contains '~' for escaping namespace
|
||||||
|
// e.g. dirname = vendor~cifs
|
||||||
|
// then, executable will be pluginDir/dirname/cifs
|
||||||
|
if f.IsDir() {
|
||||||
|
execPath := path.Join(pluginDir, f.Name())
|
||||||
|
plugins = append(plugins, &flexVolumePlugin{
|
||||||
|
driverName: utilstrings.UnescapePluginName(f.Name()),
|
||||||
|
execPath: execPath,
|
||||||
|
runner: exec.New(),
|
||||||
|
unsupportedCommands: []string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return plugins
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type unmounterDefaults flexVolumeUnmounter
|
||||||
|
|
||||||
|
func (f *unmounterDefaults) TearDownAt(dir string) error {
|
||||||
|
glog.Warning(logPrefix(f.plugin), "using default TearDownAt for ", dir)
|
||||||
|
return util.UnmountPath(dir, f.mounter)
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/kubernetes/pkg/util/exec"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlexVolumeUnmounter is the disk that will be cleaned by this plugin.
|
||||||
|
type flexVolumeUnmounter struct {
|
||||||
|
*flexVolume
|
||||||
|
// Runner used to teardown the volume.
|
||||||
|
runner exec.Interface
|
||||||
|
volume.MetricsNil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ volume.Unmounter = &flexVolumeUnmounter{}
|
||||||
|
|
||||||
|
// Unmounter interface
|
||||||
|
func (f *flexVolumeUnmounter) TearDown() error {
|
||||||
|
path := f.GetPath()
|
||||||
|
return f.TearDownAt(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flexVolumeUnmounter) TearDownAt(dir string) error {
|
||||||
|
|
||||||
|
if pathExists, pathErr := util.PathExists(dir); pathErr != nil {
|
||||||
|
return fmt.Errorf("Error checking if path exists: %v", pathErr)
|
||||||
|
} else if !pathExists {
|
||||||
|
glog.Warningf("Warning: Unmount skipped because path does not exist: %v", dir)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
notmnt, err := isNotMounted(f.mounter, dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if notmnt {
|
||||||
|
glog.Warningf("Warning: Path: %v already unmounted", dir)
|
||||||
|
} else {
|
||||||
|
call := f.plugin.NewDriverCall(unmountCmd)
|
||||||
|
call.Append(dir)
|
||||||
|
_, err := call.Run()
|
||||||
|
if isCmdNotSupportedErr(err) {
|
||||||
|
err = (*unmounterDefaults)(f).TearDownAt(dir)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flexvolume driver may remove the directory. Ignore if it does.
|
||||||
|
if pathExists, pathErr := util.PathExists(dir); pathErr != nil {
|
||||||
|
return fmt.Errorf("Error checking if path exists: %v", pathErr)
|
||||||
|
} else if !pathExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return os.Remove(dir)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTearDownAt(t *testing.T) {
|
||||||
|
mounter := &mount.FakeMounter{}
|
||||||
|
|
||||||
|
plugin, rootDir := testPlugin()
|
||||||
|
plugin.runner = fakeRunner(
|
||||||
|
assertDriverCall(t, notSupportedOutput(), unmountCmd,
|
||||||
|
rootDir+"/mount-dir"),
|
||||||
|
)
|
||||||
|
|
||||||
|
u, _ := plugin.newUnmounterInternal("volName", types.UID("poduid"), mounter, plugin.runner)
|
||||||
|
u.TearDownAt(rootDir + "/mount-dir")
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
api "k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addSecretsToOptions(options map[string]string, spec *volume.Spec, namespace string, driverName string, host volume.VolumeHost) error {
|
||||||
|
fv, _ := getVolumeSource(spec)
|
||||||
|
if fv.SecretRef == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeClient := host.GetKubeClient()
|
||||||
|
if kubeClient == nil {
|
||||||
|
return fmt.Errorf("Cannot get kube client")
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, err := util.GetSecretForPV(namespace, fv.SecretRef.Name, driverName, host.GetKubeClient())
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Couldn't get secret %v/%v err: %v", namespace, fv.SecretRef.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for name, data := range secrets {
|
||||||
|
options[optionKeySecret+"/"+name] = base64.StdEncoding.EncodeToString([]byte(data))
|
||||||
|
glog.V(1).Infof("found flex volume secret info: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVolumeSource(spec *volume.Spec) (volumeSource *api.FlexVolumeSource, readOnly bool) {
|
||||||
|
if spec.Volume != nil && spec.Volume.FlexVolume != nil {
|
||||||
|
volumeSource = spec.Volume.FlexVolume
|
||||||
|
readOnly = volumeSource.ReadOnly
|
||||||
|
} else if spec.PersistentVolume != nil {
|
||||||
|
volumeSource = spec.PersistentVolume.Spec.FlexVolume
|
||||||
|
readOnly = spec.ReadOnly
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareForMount(mounter mount.Interface, deviceMountPath string) (bool, error) {
|
||||||
|
|
||||||
|
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
notMnt = true
|
||||||
|
} else {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !notMnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mounts the device at the given path.
|
||||||
|
// It is expected that prepareForMount has been called before.
|
||||||
|
func doMount(mounter mount.Interface, devicePath, deviceMountPath, fsType string, options []string) error {
|
||||||
|
err := mounter.Mount(devicePath, deviceMountPath, fsType, options)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to mount the volume at %s, device: %s, error: %s", deviceMountPath, devicePath, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNotMounted(mounter mount.Interface, deviceMountPath string) (bool, error) {
|
||||||
|
notmnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Error checking mount point %s, error: %v", deviceMountPath, err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return notmnt, nil
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package flexvolume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
utilstrings "k8s.io/kubernetes/pkg/util/strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type flexVolume struct {
|
||||||
|
// driverName is the name of the plugin driverName.
|
||||||
|
driverName string
|
||||||
|
// Driver executable used to setup the volume.
|
||||||
|
execPath string
|
||||||
|
// mounter provides the interface that is used to mount the actual
|
||||||
|
// block device.
|
||||||
|
mounter mount.Interface
|
||||||
|
// podUID is the UID of the pod.
|
||||||
|
podUID types.UID
|
||||||
|
// podNamespace is the namespace of the pod.
|
||||||
|
podNamespace string
|
||||||
|
// volName is the name of the pod's volume.
|
||||||
|
volName string
|
||||||
|
// the underlying plugin
|
||||||
|
plugin *flexVolumePlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// volume.Volume interface
|
||||||
|
|
||||||
|
func (f *flexVolume) GetPath() string {
|
||||||
|
name := f.driverName
|
||||||
|
return f.plugin.host.GetPodVolumeDir(f.podUID, utilstrings.EscapeQualifiedNameForDisk(name), f.volName)
|
||||||
|
}
|
Loading…
Reference in New Issue