mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-27 08:40:23 +00:00
1102 lines
34 KiB
Go
1102 lines
34 KiB
Go
/*
|
|
Copyright 2014 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 rbd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
dstrings "strings"
|
|
|
|
"github.com/golang/glog"
|
|
"k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/apimachinery/pkg/util/uuid"
|
|
clientset "k8s.io/client-go/kubernetes"
|
|
"k8s.io/kubernetes/pkg/util/mount"
|
|
"k8s.io/kubernetes/pkg/util/strings"
|
|
"k8s.io/kubernetes/pkg/volume"
|
|
volutil "k8s.io/kubernetes/pkg/volume/util"
|
|
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
|
)
|
|
|
|
var (
|
|
supportedFeatures = sets.NewString("layering")
|
|
)
|
|
|
|
// This is the primary entrypoint for volume plugins.
|
|
func ProbeVolumePlugins() []volume.VolumePlugin {
|
|
return []volume.VolumePlugin{&rbdPlugin{}}
|
|
}
|
|
|
|
// rbdPlugin implements Volume.VolumePlugin.
|
|
type rbdPlugin struct {
|
|
host volume.VolumeHost
|
|
}
|
|
|
|
var _ volume.VolumePlugin = &rbdPlugin{}
|
|
var _ volume.PersistentVolumePlugin = &rbdPlugin{}
|
|
var _ volume.DeletableVolumePlugin = &rbdPlugin{}
|
|
var _ volume.ProvisionableVolumePlugin = &rbdPlugin{}
|
|
var _ volume.AttachableVolumePlugin = &rbdPlugin{}
|
|
var _ volume.ExpandableVolumePlugin = &rbdPlugin{}
|
|
var _ volume.BlockVolumePlugin = &rbdPlugin{}
|
|
|
|
const (
|
|
rbdPluginName = "kubernetes.io/rbd"
|
|
secretKeyName = "key" // key name used in secret
|
|
rbdImageFormat1 = "1"
|
|
rbdImageFormat2 = "2"
|
|
rbdDefaultAdminId = "admin"
|
|
rbdDefaultAdminSecretNamespace = "default"
|
|
rbdDefaultPool = "rbd"
|
|
rbdDefaultUserId = rbdDefaultAdminId
|
|
)
|
|
|
|
func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
|
|
return host.GetPodVolumeDir(uid, strings.EscapeQualifiedNameForDisk(rbdPluginName), volName)
|
|
}
|
|
|
|
func (plugin *rbdPlugin) Init(host volume.VolumeHost) error {
|
|
plugin.host = host
|
|
return nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) GetPluginName() string {
|
|
return rbdPluginName
|
|
}
|
|
|
|
func (plugin *rbdPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
|
pool, err := getVolumeSourcePool(spec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
img, err := getVolumeSourceImage(spec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
"%v:%v",
|
|
pool,
|
|
img), nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) CanSupport(spec *volume.Spec) bool {
|
|
return (spec.Volume != nil && spec.Volume.RBD != nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.RBD != nil)
|
|
}
|
|
|
|
func (plugin *rbdPlugin) RequiresRemount() bool {
|
|
return false
|
|
}
|
|
|
|
func (plugin *rbdPlugin) SupportsMountOption() bool {
|
|
return true
|
|
}
|
|
|
|
func (plugin *rbdPlugin) SupportsBulkVolumeVerification() bool {
|
|
return false
|
|
}
|
|
|
|
func (plugin *rbdPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
|
|
return []v1.PersistentVolumeAccessMode{
|
|
v1.ReadWriteOnce,
|
|
v1.ReadOnlyMany,
|
|
}
|
|
}
|
|
|
|
type rbdVolumeExpander struct {
|
|
*rbdMounter
|
|
}
|
|
|
|
func (plugin *rbdPlugin) getAdminAndSecret(spec *volume.Spec) (string, string, error) {
|
|
class, err := volutil.GetClassForVolume(plugin.host.GetKubeClient(), spec.PersistentVolume)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
adminSecretName := ""
|
|
adminSecretNamespace := rbdDefaultAdminSecretNamespace
|
|
admin := ""
|
|
|
|
for k, v := range class.Parameters {
|
|
switch dstrings.ToLower(k) {
|
|
case "adminid":
|
|
admin = v
|
|
case "adminsecretname":
|
|
adminSecretName = v
|
|
case "adminsecretnamespace":
|
|
adminSecretNamespace = v
|
|
}
|
|
}
|
|
|
|
if admin == "" {
|
|
admin = rbdDefaultAdminId
|
|
}
|
|
secret, err := parsePVSecret(adminSecretNamespace, adminSecretName, plugin.host.GetKubeClient())
|
|
if err != nil {
|
|
return admin, "", fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err)
|
|
}
|
|
|
|
return admin, secret, nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) {
|
|
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.RBD == nil {
|
|
return oldSize, fmt.Errorf("spec.PersistentVolumeSource.Spec.RBD is nil")
|
|
}
|
|
|
|
// get admin and secret
|
|
admin, secret, err := plugin.getAdminAndSecret(spec)
|
|
if err != nil {
|
|
return oldSize, err
|
|
}
|
|
|
|
expander := &rbdVolumeExpander{
|
|
rbdMounter: &rbdMounter{
|
|
rbd: &rbd{
|
|
volName: spec.Name(),
|
|
Image: spec.PersistentVolume.Spec.RBD.RBDImage,
|
|
Pool: spec.PersistentVolume.Spec.RBD.RBDPool,
|
|
plugin: plugin,
|
|
manager: &RBDUtil{},
|
|
mounter: &mount.SafeFormatAndMount{Interface: plugin.host.GetMounter(plugin.GetPluginName())},
|
|
exec: plugin.host.GetExec(plugin.GetPluginName()),
|
|
},
|
|
Mon: spec.PersistentVolume.Spec.RBD.CephMonitors,
|
|
adminId: admin,
|
|
adminSecret: secret,
|
|
},
|
|
}
|
|
|
|
expandedSize, err := expander.ResizeImage(oldSize, newSize)
|
|
if err != nil {
|
|
return oldSize, err
|
|
} else {
|
|
return expandedSize, nil
|
|
}
|
|
}
|
|
|
|
func (expander *rbdVolumeExpander) ResizeImage(oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) {
|
|
return expander.manager.ExpandImage(expander, oldSize, newSize)
|
|
}
|
|
|
|
func (plugin *rbdPlugin) RequiresFSResize() bool {
|
|
return true
|
|
}
|
|
|
|
func (plugin *rbdPlugin) createMounterFromVolumeSpecAndPod(spec *volume.Spec, pod *v1.Pod) (*rbdMounter, error) {
|
|
var err error
|
|
mon, err := getVolumeSourceMonitors(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img, err := getVolumeSourceImage(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fstype, err := getVolumeSourceFSType(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pool, err := getVolumeSourcePool(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
id, err := getVolumeSourceUser(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyring, err := getVolumeSourceKeyRing(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ro, err := getVolumeSourceReadOnly(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
secretName, secretNs, err := getSecretNameAndNamespace(spec, pod.Namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
secret := ""
|
|
if len(secretName) > 0 && len(secretNs) > 0 {
|
|
// if secret is provideded, retrieve it
|
|
kubeClient := plugin.host.GetKubeClient()
|
|
if kubeClient == nil {
|
|
return nil, fmt.Errorf("Cannot get kube client")
|
|
}
|
|
secrets, err := kubeClient.CoreV1().Secrets(secretNs).Get(secretName, metav1.GetOptions{})
|
|
if err != nil {
|
|
err = fmt.Errorf("Couldn't get secret %v/%v err: %v", secretNs, secretName, err)
|
|
return nil, err
|
|
}
|
|
for _, data := range secrets.Data {
|
|
secret = string(data)
|
|
}
|
|
}
|
|
|
|
return &rbdMounter{
|
|
rbd: newRBD("", spec.Name(), img, pool, ro, plugin, &RBDUtil{}),
|
|
Mon: mon,
|
|
Id: id,
|
|
Keyring: keyring,
|
|
Secret: secret,
|
|
fsType: fstype,
|
|
}, nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
|
secretName, secretNs, err := getSecretNameAndNamespace(spec, pod.Namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
secret := ""
|
|
if len(secretName) > 0 && len(secretNs) > 0 {
|
|
// if secret is provideded, retrieve it
|
|
kubeClient := plugin.host.GetKubeClient()
|
|
if kubeClient == nil {
|
|
return nil, fmt.Errorf("Cannot get kube client")
|
|
}
|
|
secrets, err := kubeClient.CoreV1().Secrets(secretNs).Get(secretName, metav1.GetOptions{})
|
|
if err != nil {
|
|
err = fmt.Errorf("Couldn't get secret %v/%v err: %v", secretNs, secretName, err)
|
|
return nil, err
|
|
}
|
|
for _, data := range secrets.Data {
|
|
secret = string(data)
|
|
}
|
|
}
|
|
|
|
// Inject real implementations here, test through the internal function.
|
|
return plugin.newMounterInternal(spec, pod.UID, &RBDUtil{}, secret)
|
|
}
|
|
|
|
func (plugin *rbdPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, secret string) (volume.Mounter, error) {
|
|
mon, err := getVolumeSourceMonitors(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img, err := getVolumeSourceImage(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fstype, err := getVolumeSourceFSType(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pool, err := getVolumeSourcePool(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
id, err := getVolumeSourceUser(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyring, err := getVolumeSourceKeyRing(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ro, err := getVolumeSourceReadOnly(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &rbdMounter{
|
|
rbd: newRBD(podUID, spec.Name(), img, pool, ro, plugin, manager),
|
|
Mon: mon,
|
|
Id: id,
|
|
Keyring: keyring,
|
|
Secret: secret,
|
|
fsType: fstype,
|
|
mountOptions: volutil.MountOptionFromSpec(spec),
|
|
}, nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
|
// Inject real implementations here, test through the internal function.
|
|
return plugin.newUnmounterInternal(volName, podUID, &RBDUtil{})
|
|
}
|
|
|
|
func (plugin *rbdPlugin) newUnmounterInternal(volName string, podUID types.UID, manager diskManager) (volume.Unmounter, error) {
|
|
return &rbdUnmounter{
|
|
rbdMounter: &rbdMounter{
|
|
rbd: newRBD(podUID, volName, "", "", false, plugin, manager),
|
|
Mon: make([]string, 0),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
|
mounter := plugin.host.GetMounter(plugin.GetPluginName())
|
|
pluginDir := plugin.host.GetPluginDir(plugin.GetPluginName())
|
|
sourceName, err := mounter.GetDeviceNameFromMount(mountPath, pluginDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s := dstrings.Split(sourceName, "-image-")
|
|
if len(s) != 2 {
|
|
// The mountPath parameter is the volume mount path for a specific pod, its format
|
|
// is /var/lib/kubelet/pods/{podUID}/volumes/{volumePluginName}/{volumeName}.
|
|
// mounter.GetDeviceNameFromMount will find the device path(such as /dev/rbd0) by
|
|
// mountPath first, and then try to find the global device mount path from the mounted
|
|
// path list of this device. sourceName is extracted from this global device mount path.
|
|
// mounter.GetDeviceNameFromMount expects the global device mount path conforms to canonical
|
|
// format: /var/lib/kubelet/plugins/kubernetes.io/rbd/mounts/{pool}-image-{image}.
|
|
// If this assertion failed, it means that the global device mount path is created by
|
|
// the deprecated format: /var/lib/kubelet/plugins/kubernetes.io/rbd/rbd/{pool}-image-{image}.
|
|
// So we will try to check whether this old style global device mount path exist or not.
|
|
// If existed, extract the sourceName from this old style path, otherwise return an error.
|
|
glog.V(3).Infof("SourceName %s wrong, fallback to old format", sourceName)
|
|
sourceName, err = plugin.getDeviceNameFromOldMountPath(mounter, mountPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s = dstrings.Split(sourceName, "-image-")
|
|
if len(s) != 2 {
|
|
return nil, fmt.Errorf("sourceName %s wrong, should be pool+\"-image-\"+imageName", sourceName)
|
|
}
|
|
}
|
|
rbdVolume := &v1.Volume{
|
|
Name: volumeName,
|
|
VolumeSource: v1.VolumeSource{
|
|
RBD: &v1.RBDVolumeSource{
|
|
RBDPool: s[0],
|
|
RBDImage: s[1],
|
|
},
|
|
},
|
|
}
|
|
return volume.NewSpecFromVolume(rbdVolume), nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) {
|
|
pluginDir := plugin.host.GetVolumeDevicePluginDir(rbdPluginName)
|
|
blkutil := volumepathhandler.NewBlockVolumePathHandler()
|
|
|
|
globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
glog.V(5).Infof("globalMapPathUUID: %v, err: %v", globalMapPathUUID, err)
|
|
globalMapPath := filepath.Dir(globalMapPathUUID)
|
|
if len(globalMapPath) == 1 {
|
|
return nil, fmt.Errorf("failed to retrieve volume plugin information from globalMapPathUUID: %v", globalMapPathUUID)
|
|
}
|
|
return getVolumeSpecFromGlobalMapPath(globalMapPath)
|
|
}
|
|
|
|
func getVolumeSpecFromGlobalMapPath(globalMapPath string) (*volume.Spec, error) {
|
|
// Retrieve volume spec information from globalMapPath
|
|
// globalMapPath example:
|
|
// plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}
|
|
pool, image, err := getPoolAndImageFromMapPath(globalMapPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block := v1.PersistentVolumeBlock
|
|
rbdVolume := &v1.PersistentVolume{
|
|
Spec: v1.PersistentVolumeSpec{
|
|
PersistentVolumeSource: v1.PersistentVolumeSource{
|
|
RBD: &v1.RBDPersistentVolumeSource{
|
|
RBDImage: image,
|
|
RBDPool: pool,
|
|
},
|
|
},
|
|
VolumeMode: &block,
|
|
},
|
|
}
|
|
|
|
return volume.NewSpecFromPersistentVolume(rbdVolume, true), nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) {
|
|
|
|
var uid types.UID
|
|
if pod != nil {
|
|
uid = pod.UID
|
|
}
|
|
secret := ""
|
|
// var err error
|
|
if pod != nil {
|
|
secretName, secretNs, err := getSecretNameAndNamespace(spec, pod.Namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(secretName) > 0 && len(secretNs) > 0 {
|
|
// if secret is provideded, retrieve it
|
|
kubeClient := plugin.host.GetKubeClient()
|
|
if kubeClient == nil {
|
|
return nil, fmt.Errorf("Cannot get kube client")
|
|
}
|
|
secrets, err := kubeClient.Core().Secrets(secretNs).Get(secretName, metav1.GetOptions{})
|
|
if err != nil {
|
|
err = fmt.Errorf("Couldn't get secret %v/%v err: %v", secretNs, secretName, err)
|
|
return nil, err
|
|
}
|
|
for _, data := range secrets.Data {
|
|
secret = string(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
return plugin.newBlockVolumeMapperInternal(spec, uid, &RBDUtil{}, secret, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()))
|
|
}
|
|
|
|
func (plugin *rbdPlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager diskManager, secret string, mounter mount.Interface, exec mount.Exec) (volume.BlockVolumeMapper, error) {
|
|
mon, err := getVolumeSourceMonitors(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img, err := getVolumeSourceImage(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pool, err := getVolumeSourcePool(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
id, err := getVolumeSourceUser(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyring, err := getVolumeSourceKeyRing(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ro, err := getVolumeSourceReadOnly(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &rbdDiskMapper{
|
|
rbd: newRBD(podUID, spec.Name(), img, pool, ro, plugin, manager),
|
|
mon: mon,
|
|
id: id,
|
|
keyring: keyring,
|
|
secret: secret,
|
|
}, nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) {
|
|
return plugin.newUnmapperInternal(volName, podUID, &RBDUtil{})
|
|
}
|
|
|
|
func (plugin *rbdPlugin) newUnmapperInternal(volName string, podUID types.UID, manager diskManager) (volume.BlockVolumeUnmapper, error) {
|
|
return &rbdDiskUnmapper{
|
|
rbdDiskMapper: &rbdDiskMapper{
|
|
rbd: newRBD(podUID, volName, "", "", false, plugin, manager),
|
|
mon: make([]string, 0),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) getDeviceNameFromOldMountPath(mounter mount.Interface, mountPath string) (string, error) {
|
|
refs, err := mount.GetMountRefsByDev(mounter, mountPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// baseMountPath is the prefix of deprecated device global mounted path,
|
|
// such as: /var/lib/kubelet/plugins/kubernetes.io/rbd/rbd
|
|
baseMountPath := filepath.Join(plugin.host.GetPluginDir(rbdPluginName), "rbd")
|
|
for _, ref := range refs {
|
|
if dstrings.HasPrefix(ref, baseMountPath) {
|
|
return filepath.Rel(baseMountPath, ref)
|
|
}
|
|
}
|
|
return "", fmt.Errorf("can't find source name from mounted path: %s", mountPath)
|
|
}
|
|
|
|
func (plugin *rbdPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
|
|
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.RBD == nil {
|
|
return nil, fmt.Errorf("spec.PersistentVolumeSource.Spec.RBD is nil")
|
|
}
|
|
|
|
admin, secret, err := plugin.getAdminAndSecret(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return plugin.newDeleterInternal(spec, admin, secret, &RBDUtil{})
|
|
}
|
|
|
|
func (plugin *rbdPlugin) newDeleterInternal(spec *volume.Spec, admin, secret string, manager diskManager) (volume.Deleter, error) {
|
|
return &rbdVolumeDeleter{
|
|
rbdMounter: &rbdMounter{
|
|
rbd: newRBD("", spec.Name(), spec.PersistentVolume.Spec.RBD.RBDImage, spec.PersistentVolume.Spec.RBD.RBDPool, false, plugin, manager),
|
|
Mon: spec.PersistentVolume.Spec.RBD.CephMonitors,
|
|
adminId: admin,
|
|
adminSecret: secret,
|
|
}}, nil
|
|
}
|
|
|
|
func (plugin *rbdPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
|
return plugin.newProvisionerInternal(options, &RBDUtil{})
|
|
}
|
|
|
|
func (plugin *rbdPlugin) newProvisionerInternal(options volume.VolumeOptions, manager diskManager) (volume.Provisioner, error) {
|
|
return &rbdVolumeProvisioner{
|
|
rbdMounter: &rbdMounter{
|
|
rbd: newRBD("", "", "", "", false, plugin, manager),
|
|
},
|
|
options: options,
|
|
}, nil
|
|
}
|
|
|
|
// rbdVolumeProvisioner implements volume.Provisioner interface.
|
|
type rbdVolumeProvisioner struct {
|
|
*rbdMounter
|
|
options volume.VolumeOptions
|
|
}
|
|
|
|
var _ volume.Provisioner = &rbdVolumeProvisioner{}
|
|
|
|
func (r *rbdVolumeProvisioner) Provision() (*v1.PersistentVolume, error) {
|
|
if !volutil.AccessModesContainedInAll(r.plugin.GetAccessModes(), r.options.PVC.Spec.AccessModes) {
|
|
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", r.options.PVC.Spec.AccessModes, r.plugin.GetAccessModes())
|
|
}
|
|
|
|
if r.options.PVC.Spec.Selector != nil {
|
|
return nil, fmt.Errorf("claim Selector is not supported")
|
|
}
|
|
var err error
|
|
adminSecretName := ""
|
|
adminSecretNamespace := rbdDefaultAdminSecretNamespace
|
|
secret := ""
|
|
secretName := ""
|
|
secretNamespace := ""
|
|
keyring := ""
|
|
imageFormat := rbdImageFormat2
|
|
fstype := ""
|
|
|
|
for k, v := range r.options.Parameters {
|
|
switch dstrings.ToLower(k) {
|
|
case "monitors":
|
|
arr := dstrings.Split(v, ",")
|
|
for _, m := range arr {
|
|
r.Mon = append(r.Mon, m)
|
|
}
|
|
case "adminid":
|
|
r.adminId = v
|
|
case "adminsecretname":
|
|
adminSecretName = v
|
|
case "adminsecretnamespace":
|
|
adminSecretNamespace = v
|
|
case "userid":
|
|
r.Id = v
|
|
case "pool":
|
|
r.Pool = v
|
|
case "usersecretname":
|
|
secretName = v
|
|
case "usersecretnamespace":
|
|
secretNamespace = v
|
|
case "keyring":
|
|
keyring = v
|
|
case "imageformat":
|
|
imageFormat = v
|
|
case "imagefeatures":
|
|
arr := dstrings.Split(v, ",")
|
|
for _, f := range arr {
|
|
if !supportedFeatures.Has(f) {
|
|
return nil, fmt.Errorf("invalid feature %q for volume plugin %s, supported features are: %v", f, r.plugin.GetPluginName(), supportedFeatures)
|
|
} else {
|
|
r.imageFeatures = append(r.imageFeatures, f)
|
|
}
|
|
}
|
|
case volume.VolumeParameterFSType:
|
|
fstype = v
|
|
default:
|
|
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, r.plugin.GetPluginName())
|
|
}
|
|
}
|
|
// sanity check
|
|
if imageFormat != rbdImageFormat1 && imageFormat != rbdImageFormat2 {
|
|
return nil, fmt.Errorf("invalid ceph imageformat %s, expecting %s or %s",
|
|
imageFormat, rbdImageFormat1, rbdImageFormat2)
|
|
}
|
|
r.imageFormat = imageFormat
|
|
if adminSecretName == "" {
|
|
return nil, fmt.Errorf("missing Ceph admin secret name")
|
|
}
|
|
if secret, err = parsePVSecret(adminSecretNamespace, adminSecretName, r.plugin.host.GetKubeClient()); err != nil {
|
|
return nil, fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err)
|
|
}
|
|
r.adminSecret = secret
|
|
if len(r.Mon) < 1 {
|
|
return nil, fmt.Errorf("missing Ceph monitors")
|
|
}
|
|
if secretName == "" && keyring == "" {
|
|
return nil, fmt.Errorf("must specify either keyring or user secret name")
|
|
}
|
|
if r.adminId == "" {
|
|
r.adminId = rbdDefaultAdminId
|
|
}
|
|
if r.Pool == "" {
|
|
r.Pool = rbdDefaultPool
|
|
}
|
|
if r.Id == "" {
|
|
r.Id = r.adminId
|
|
}
|
|
|
|
// create random image name
|
|
image := fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.NewUUID())
|
|
r.rbdMounter.Image = image
|
|
rbd, sizeMB, err := r.manager.CreateImage(r)
|
|
if err != nil {
|
|
glog.Errorf("rbd: create volume failed, err: %v", err)
|
|
return nil, err
|
|
}
|
|
glog.Infof("successfully created rbd image %q", image)
|
|
pv := new(v1.PersistentVolume)
|
|
metav1.SetMetaDataAnnotation(&pv.ObjectMeta, volutil.VolumeDynamicallyCreatedByKey, "rbd-dynamic-provisioner")
|
|
|
|
if secretName != "" {
|
|
rbd.SecretRef = new(v1.SecretReference)
|
|
rbd.SecretRef.Name = secretName
|
|
rbd.SecretRef.Namespace = secretNamespace
|
|
} else {
|
|
var filePathRegex = regexp.MustCompile(`^(?:/[^/!;` + "`" + ` ]+)+$`)
|
|
if keyring != "" && !filePathRegex.MatchString(keyring) {
|
|
return nil, fmt.Errorf("keyring field must contain a path to a file")
|
|
}
|
|
rbd.Keyring = keyring
|
|
}
|
|
|
|
rbd.RadosUser = r.Id
|
|
rbd.FSType = fstype
|
|
pv.Spec.PersistentVolumeSource.RBD = rbd
|
|
pv.Spec.PersistentVolumeReclaimPolicy = r.options.PersistentVolumeReclaimPolicy
|
|
pv.Spec.AccessModes = r.options.PVC.Spec.AccessModes
|
|
if len(pv.Spec.AccessModes) == 0 {
|
|
pv.Spec.AccessModes = r.plugin.GetAccessModes()
|
|
}
|
|
pv.Spec.Capacity = v1.ResourceList{
|
|
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dMi", sizeMB)),
|
|
}
|
|
pv.Spec.MountOptions = r.options.MountOptions
|
|
return pv, nil
|
|
}
|
|
|
|
// rbdVolumeDeleter implements volume.Deleter interface.
|
|
type rbdVolumeDeleter struct {
|
|
*rbdMounter
|
|
}
|
|
|
|
var _ volume.Deleter = &rbdVolumeDeleter{}
|
|
|
|
func (r *rbdVolumeDeleter) GetPath() string {
|
|
return getPath(r.podUID, r.volName, r.plugin.host)
|
|
}
|
|
|
|
func (r *rbdVolumeDeleter) Delete() error {
|
|
return r.manager.DeleteImage(r)
|
|
}
|
|
|
|
// rbd implmenets volume.Volume interface.
|
|
// It's embedded in Mounter/Unmounter/Deleter.
|
|
type rbd struct {
|
|
volName string
|
|
podUID types.UID
|
|
Pool string
|
|
Image string
|
|
ReadOnly bool
|
|
plugin *rbdPlugin
|
|
mounter *mount.SafeFormatAndMount
|
|
exec mount.Exec
|
|
// Utility interface that provides API calls to the provider to attach/detach disks.
|
|
manager diskManager
|
|
volume.MetricsProvider `json:"-"`
|
|
}
|
|
|
|
var _ volume.Volume = &rbd{}
|
|
|
|
func (rbd *rbd) GetPath() string {
|
|
// safe to use PodVolumeDir now: volume teardown occurs before pod is cleaned up
|
|
return getPath(rbd.podUID, rbd.volName, rbd.plugin.host)
|
|
}
|
|
|
|
// newRBD creates a new rbd.
|
|
func newRBD(podUID types.UID, volName string, image string, pool string, readOnly bool, plugin *rbdPlugin, manager diskManager) *rbd {
|
|
return &rbd{
|
|
podUID: podUID,
|
|
volName: volName,
|
|
Image: image,
|
|
Pool: pool,
|
|
ReadOnly: readOnly,
|
|
plugin: plugin,
|
|
mounter: volutil.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
|
|
exec: plugin.host.GetExec(plugin.GetPluginName()),
|
|
manager: manager,
|
|
MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
|
|
}
|
|
}
|
|
|
|
// rbdMounter implements volume.Mounter interface.
|
|
// It contains information which need to be persisted in whole life cycle of PV
|
|
// on the node. It is persisted at the very beginning in the pod mount point
|
|
// directory.
|
|
// Note: Capitalized field names of this struct determines the information
|
|
// persisted on the disk, DO NOT change them. (TODO: refactoring to use a dedicated struct?)
|
|
type rbdMounter struct {
|
|
*rbd
|
|
// capitalized so they can be exported in persistRBD()
|
|
Mon []string
|
|
Id string
|
|
Keyring string
|
|
Secret string
|
|
fsType string
|
|
adminSecret string
|
|
adminId string
|
|
mountOptions []string
|
|
imageFormat string
|
|
imageFeatures []string
|
|
}
|
|
|
|
var _ volume.Mounter = &rbdMounter{}
|
|
|
|
func (b *rbd) GetAttributes() volume.Attributes {
|
|
return volume.Attributes{
|
|
ReadOnly: b.ReadOnly,
|
|
Managed: !b.ReadOnly,
|
|
SupportsSELinux: true,
|
|
}
|
|
}
|
|
|
|
// 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 (b *rbdMounter) CanMount() error {
|
|
return nil
|
|
}
|
|
|
|
func (b *rbdMounter) SetUp(fsGroup *int64) error {
|
|
return b.SetUpAt(b.GetPath(), fsGroup)
|
|
}
|
|
|
|
func (b *rbdMounter) SetUpAt(dir string, fsGroup *int64) error {
|
|
// diskSetUp checks mountpoints and prevent repeated calls
|
|
glog.V(4).Infof("rbd: attempting to setup at %s", dir)
|
|
err := diskSetUp(b.manager, *b, dir, b.mounter, fsGroup)
|
|
if err != nil {
|
|
glog.Errorf("rbd: failed to setup at %s %v", dir, err)
|
|
}
|
|
glog.V(3).Infof("rbd: successfully setup at %s", dir)
|
|
return err
|
|
}
|
|
|
|
// rbdUnmounter implements volume.Unmounter interface.
|
|
type rbdUnmounter struct {
|
|
*rbdMounter
|
|
}
|
|
|
|
var _ volume.Unmounter = &rbdUnmounter{}
|
|
|
|
// Unmounts the bind mount, and detaches the disk only if the disk
|
|
// resource was the last reference to that disk on the kubelet.
|
|
func (c *rbdUnmounter) TearDown() error {
|
|
return c.TearDownAt(c.GetPath())
|
|
}
|
|
|
|
func (c *rbdUnmounter) TearDownAt(dir string) error {
|
|
glog.V(4).Infof("rbd: attempting to teardown at %s", dir)
|
|
if pathExists, pathErr := volutil.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
|
|
}
|
|
err := diskTearDown(c.manager, *c, dir, c.mounter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
glog.V(3).Infof("rbd: successfully teardown at %s", dir)
|
|
return nil
|
|
}
|
|
|
|
var _ volume.BlockVolumeMapper = &rbdDiskMapper{}
|
|
|
|
type rbdDiskMapper struct {
|
|
*rbd
|
|
mon []string
|
|
id string
|
|
keyring string
|
|
secret string
|
|
adminSecret string
|
|
adminId string
|
|
imageFormat string
|
|
imageFeatures []string
|
|
}
|
|
|
|
var _ volume.BlockVolumeUnmapper = &rbdDiskUnmapper{}
|
|
|
|
// GetGlobalMapPath returns global map path and error
|
|
// path: plugins/kubernetes.io/{PluginName}/volumeDevices/{rbd pool}-image-{rbd image-name}/{podUid}
|
|
func (rbd *rbd) GetGlobalMapPath(spec *volume.Spec) (string, error) {
|
|
return rbd.rbdGlobalMapPath(spec)
|
|
}
|
|
|
|
// GetPodDeviceMapPath returns pod device map path and volume name
|
|
// path: pods/{podUid}/volumeDevices/kubernetes.io~rbd
|
|
// volumeName: pv0001
|
|
func (rbd *rbd) GetPodDeviceMapPath() (string, string) {
|
|
return rbd.rbdPodDeviceMapPath()
|
|
}
|
|
|
|
func (rbd *rbdDiskMapper) SetUpDevice() (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (rbd *rbd) rbdGlobalMapPath(spec *volume.Spec) (string, error) {
|
|
var err error
|
|
mon, err := getVolumeSourceMonitors(spec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
img, err := getVolumeSourceImage(spec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
pool, err := getVolumeSourcePool(spec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ro, err := getVolumeSourceReadOnly(spec)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
mounter := &rbdMounter{
|
|
rbd: newRBD("", spec.Name(), img, pool, ro, rbd.plugin, &RBDUtil{}),
|
|
Mon: mon,
|
|
}
|
|
return rbd.manager.MakeGlobalVDPDName(*mounter.rbd), nil
|
|
}
|
|
|
|
func (rbd *rbd) rbdPodDeviceMapPath() (string, string) {
|
|
name := rbdPluginName
|
|
return rbd.plugin.host.GetPodVolumeDeviceDir(rbd.podUID, strings.EscapeQualifiedNameForDisk(name)), rbd.volName
|
|
}
|
|
|
|
type rbdDiskUnmapper struct {
|
|
*rbdDiskMapper
|
|
}
|
|
|
|
func getPoolAndImageFromMapPath(mapPath string) (string, string, error) {
|
|
|
|
pathParts := dstrings.Split(mapPath, "/")
|
|
if len(pathParts) < 2 {
|
|
return "", "", fmt.Errorf("corrupted mapPath")
|
|
}
|
|
rbdParts := dstrings.Split(pathParts[len(pathParts)-1], "-image-")
|
|
|
|
if len(rbdParts) < 2 {
|
|
return "", "", fmt.Errorf("corrupted mapPath")
|
|
}
|
|
return string(rbdParts[0]), string(rbdParts[1]), nil
|
|
}
|
|
|
|
func getBlockVolumeDevice(mapPath string) (string, error) {
|
|
pool, image, err := getPoolAndImageFromMapPath(mapPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// Getting full device path
|
|
device, found := getDevFromImageAndPool(pool, image)
|
|
if !found {
|
|
return "", err
|
|
}
|
|
return device, nil
|
|
}
|
|
|
|
func (rbd *rbdDiskUnmapper) TearDownDevice(mapPath, _ string) error {
|
|
|
|
device, err := getBlockVolumeDevice(mapPath)
|
|
if err != nil {
|
|
return fmt.Errorf("rbd: failed to get loopback for device: %v, err: %v", device, err)
|
|
}
|
|
// Get loopback device which takes fd lock for device beofore detaching a volume from node.
|
|
// TODO: This is a workaround for issue #54108
|
|
// Currently local attach plugins such as FC, iSCSI, RBD can't obtain devicePath during
|
|
// GenerateUnmapDeviceFunc() in operation_generator. As a result, these plugins fail to get
|
|
// and remove loopback device then it will be remained on kubelet node. To avoid the problem,
|
|
// local attach plugins needs to remove loopback device during TearDownDevice().
|
|
blkUtil := volumepathhandler.NewBlockVolumePathHandler()
|
|
loop, err := volumepathhandler.BlockVolumePathHandler.GetLoopDevice(blkUtil, device)
|
|
if err != nil {
|
|
return fmt.Errorf("rbd: failed to get loopback for device: %v, err: %v", device, err)
|
|
}
|
|
// Remove loop device before detaching volume since volume detach operation gets busy if volume is opened by loopback.
|
|
err = volumepathhandler.BlockVolumePathHandler.RemoveLoopDevice(blkUtil, loop)
|
|
if err != nil {
|
|
return fmt.Errorf("rbd: failed to remove loopback :%v, err: %v", loop, err)
|
|
}
|
|
glog.V(4).Infof("rbd: successfully removed loop device: %s", loop)
|
|
|
|
err = rbd.manager.DetachBlockDisk(*rbd, mapPath)
|
|
if err != nil {
|
|
return fmt.Errorf("rbd: failed to detach disk: %s\nError: %v", mapPath, err)
|
|
}
|
|
glog.V(4).Infof("rbd: %q is unmapped, deleting the directory", mapPath)
|
|
|
|
err = os.RemoveAll(mapPath)
|
|
if err != nil {
|
|
return fmt.Errorf("rbd: failed to delete the directory: %s\nError: %v", mapPath, err)
|
|
}
|
|
glog.V(4).Infof("rbd: successfully detached disk: %s", mapPath)
|
|
|
|
return nil
|
|
}
|
|
|
|
func getVolumeSourceMonitors(spec *volume.Spec) ([]string, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
return spec.Volume.RBD.CephMonitors, nil
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
return spec.PersistentVolume.Spec.RBD.CephMonitors, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Spec does not reference a RBD volume type")
|
|
}
|
|
|
|
func getVolumeSourceImage(spec *volume.Spec) (string, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
return spec.Volume.RBD.RBDImage, nil
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
return spec.PersistentVolume.Spec.RBD.RBDImage, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("Spec does not reference a RBD volume type")
|
|
}
|
|
|
|
func getVolumeSourceFSType(spec *volume.Spec) (string, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
return spec.Volume.RBD.FSType, nil
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
return spec.PersistentVolume.Spec.RBD.FSType, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("Spec does not reference a RBD volume type")
|
|
}
|
|
|
|
func getVolumeSourcePool(spec *volume.Spec) (string, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
return spec.Volume.RBD.RBDPool, nil
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
return spec.PersistentVolume.Spec.RBD.RBDPool, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("Spec does not reference a RBD volume type")
|
|
}
|
|
|
|
func getVolumeSourceUser(spec *volume.Spec) (string, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
return spec.Volume.RBD.RadosUser, nil
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
return spec.PersistentVolume.Spec.RBD.RadosUser, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("Spec does not reference a RBD volume type")
|
|
}
|
|
|
|
func getVolumeSourceKeyRing(spec *volume.Spec) (string, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
return spec.Volume.RBD.Keyring, nil
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
return spec.PersistentVolume.Spec.RBD.Keyring, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("Spec does not reference a RBD volume type")
|
|
}
|
|
|
|
func getVolumeSourceReadOnly(spec *volume.Spec) (bool, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
return spec.Volume.RBD.ReadOnly, nil
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
// rbd volumes used as a PersistentVolume gets the ReadOnly flag indirectly through
|
|
// the persistent-claim volume used to mount the PV
|
|
return spec.ReadOnly, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("Spec does not reference a RBD volume type")
|
|
}
|
|
|
|
func parsePodSecret(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (string, error) {
|
|
secret, err := volutil.GetSecretForPod(pod, secretName, kubeClient)
|
|
if err != nil {
|
|
glog.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName)
|
|
return "", fmt.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName)
|
|
}
|
|
return parseSecretMap(secret)
|
|
}
|
|
|
|
func parsePVSecret(namespace, secretName string, kubeClient clientset.Interface) (string, error) {
|
|
secret, err := volutil.GetSecretForPV(namespace, secretName, rbdPluginName, kubeClient)
|
|
if err != nil {
|
|
glog.Errorf("failed to get secret from [%q/%q]", namespace, secretName)
|
|
return "", fmt.Errorf("failed to get secret from [%q/%q]", namespace, secretName)
|
|
}
|
|
return parseSecretMap(secret)
|
|
}
|
|
|
|
// parseSecretMap locates the secret by key name.
|
|
func parseSecretMap(secretMap map[string]string) (string, error) {
|
|
if len(secretMap) == 0 {
|
|
return "", fmt.Errorf("empty secret map")
|
|
}
|
|
secret := ""
|
|
for k, v := range secretMap {
|
|
if k == secretKeyName {
|
|
return v, nil
|
|
}
|
|
secret = v
|
|
}
|
|
// If not found, the last secret in the map wins as done before
|
|
return secret, nil
|
|
}
|
|
|
|
func getSecretNameAndNamespace(spec *volume.Spec, defaultNamespace string) (string, string, error) {
|
|
if spec.Volume != nil && spec.Volume.RBD != nil {
|
|
localSecretRef := spec.Volume.RBD.SecretRef
|
|
if localSecretRef != nil {
|
|
return localSecretRef.Name, defaultNamespace, nil
|
|
}
|
|
return "", "", nil
|
|
|
|
} else if spec.PersistentVolume != nil &&
|
|
spec.PersistentVolume.Spec.RBD != nil {
|
|
secretRef := spec.PersistentVolume.Spec.RBD.SecretRef
|
|
secretNs := defaultNamespace
|
|
if secretRef != nil {
|
|
if len(secretRef.Namespace) != 0 {
|
|
secretNs = secretRef.Namespace
|
|
}
|
|
return secretRef.Name, secretNs, nil
|
|
}
|
|
return "", "", nil
|
|
}
|
|
return "", "", fmt.Errorf("Spec does not reference an RBD volume type")
|
|
}
|