2017-10-25 02:44:48 +00:00
|
|
|
/*
|
|
|
|
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 csi
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-05-30 14:33:33 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
2018-06-04 20:47:24 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2017-10-25 02:44:48 +00:00
|
|
|
"time"
|
|
|
|
|
2018-08-07 19:28:19 +00:00
|
|
|
"context"
|
2018-08-22 10:49:05 +00:00
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
"github.com/golang/glog"
|
|
|
|
api "k8s.io/api/core/v1"
|
2018-08-28 13:01:39 +00:00
|
|
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
2017-10-25 02:44:48 +00:00
|
|
|
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2018-08-22 10:49:05 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
2018-05-09 18:28:53 +00:00
|
|
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
2018-08-28 13:01:39 +00:00
|
|
|
clientset "k8s.io/client-go/kubernetes"
|
2018-08-22 10:49:05 +00:00
|
|
|
csiapiinformer "k8s.io/csi-api/pkg/client/informers/externalversions"
|
|
|
|
csiinformer "k8s.io/csi-api/pkg/client/informers/externalversions/csi/v1alpha1"
|
2018-08-28 12:59:33 +00:00
|
|
|
csilister "k8s.io/csi-api/pkg/client/listers/csi/v1alpha1"
|
2018-05-09 18:28:53 +00:00
|
|
|
"k8s.io/kubernetes/pkg/features"
|
2017-10-25 02:44:48 +00:00
|
|
|
"k8s.io/kubernetes/pkg/volume"
|
2018-08-13 23:38:30 +00:00
|
|
|
"k8s.io/kubernetes/pkg/volume/csi/nodeupdater"
|
2017-10-25 02:44:48 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2018-01-24 18:04:03 +00:00
|
|
|
csiPluginName = "kubernetes.io/csi"
|
2017-10-25 02:44:48 +00:00
|
|
|
|
|
|
|
// TODO (vladimirvivien) implement a more dynamic way to discover
|
|
|
|
// the unix domain socket path for each installed csi driver.
|
|
|
|
// TODO (vladimirvivien) would be nice to name socket with a .sock extension
|
|
|
|
// for consistency.
|
2017-11-29 01:48:12 +00:00
|
|
|
csiAddrTemplate = "/var/lib/kubelet/plugins/%v/csi.sock"
|
2017-10-25 02:44:48 +00:00
|
|
|
csiTimeout = 15 * time.Second
|
|
|
|
volNameSep = "^"
|
2017-12-04 22:29:01 +00:00
|
|
|
volDataFileName = "vol_data.json"
|
2018-05-09 18:28:53 +00:00
|
|
|
fsTypeBlockName = "block"
|
2018-08-22 10:49:05 +00:00
|
|
|
|
|
|
|
// TODO: increase to something useful
|
|
|
|
csiResyncPeriod = time.Minute
|
2017-10-25 02:44:48 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type csiPlugin struct {
|
2018-08-22 10:49:05 +00:00
|
|
|
host volume.VolumeHost
|
|
|
|
blockEnabled bool
|
2018-08-28 12:59:33 +00:00
|
|
|
csiDriverLister csilister.CSIDriverLister
|
2018-08-22 10:49:05 +00:00
|
|
|
csiDriverInformer csiinformer.CSIDriverInformer
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ProbeVolumePlugins returns implemented plugins
|
|
|
|
func ProbeVolumePlugins() []volume.VolumePlugin {
|
|
|
|
p := &csiPlugin{
|
2018-05-09 18:28:53 +00:00
|
|
|
host: nil,
|
|
|
|
blockEnabled: utilfeature.DefaultFeatureGate.Enabled(features.CSIBlockVolume),
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
return []volume.VolumePlugin{p}
|
|
|
|
}
|
|
|
|
|
|
|
|
// volume.VolumePlugin methods
|
|
|
|
var _ volume.VolumePlugin = &csiPlugin{}
|
|
|
|
|
2018-06-04 20:47:24 +00:00
|
|
|
type csiDriver struct {
|
|
|
|
driverName string
|
|
|
|
driverEndpoint string
|
|
|
|
}
|
|
|
|
|
|
|
|
type csiDriversStore struct {
|
|
|
|
driversMap map[string]csiDriver
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
2018-08-07 19:28:19 +00:00
|
|
|
// TODO (verult) consider using a struct instead of global variables
|
2018-06-04 20:47:24 +00:00
|
|
|
// csiDrivers map keep track of all registered CSI drivers on the node and their
|
|
|
|
// corresponding sockets
|
|
|
|
var csiDrivers csiDriversStore
|
|
|
|
|
2018-08-13 23:38:30 +00:00
|
|
|
var nodeUpdater nodeupdater.Interface
|
2018-06-04 20:47:24 +00:00
|
|
|
|
|
|
|
// RegistrationCallback is called by kubelet's plugin watcher upon detection
|
|
|
|
// of a new registration socket opened by CSI Driver registrar side car.
|
Add hierarchy support for plugin directory
it traverses and watch plugin directory and its sub directory recursively,
plugin socket file only need be unique within one directory,
- plugin socket directory
- |
- ---->sub directory 1
- | |
- | -----> socket1, socket2 ...
- ----->sub directory 2
- |
- ------> socket1, socket2 ...
the design itself allow sub directory be anything,
but in practical, each plugin type could just use one sub directory.
four bonus changes added as below
1. extract example handler out from test, it is easier to read the code
with the seperation.
2. there are two variables here: "Watcher" and "watcher".
"Watcher" is the plugin watcher, and "watcher" is the fsnotify watcher.
so rename the "watcher" to "fsWatcher" to make code easier to
understand.
3. change RegisterCallbackFn() return value order, it is
conventional to return error last, after this change,
the pkg/volume/csi is compliance with golint, so remove it
from hack/.golint_failures
4. refactor errors handling at invokeRegistrationCallbackAtHandler()
to make error message more clear.
2018-05-31 00:07:58 +00:00
|
|
|
func RegistrationCallback(pluginName string, endpoint string, versions []string, socketPath string) (chan bool, error) {
|
2018-06-04 20:47:24 +00:00
|
|
|
|
|
|
|
glog.Infof(log("Callback from kubelet with plugin name: %s endpoint: %s versions: %s socket path: %s",
|
|
|
|
pluginName, endpoint, strings.Join(versions, ","), socketPath))
|
|
|
|
|
|
|
|
if endpoint == "" {
|
|
|
|
endpoint = socketPath
|
|
|
|
}
|
2018-08-07 19:28:19 +00:00
|
|
|
|
2018-06-04 20:47:24 +00:00
|
|
|
// Storing endpoint of newly registered CSI driver into the map, where CSI driver name will be the key
|
|
|
|
// all other CSI components will be able to get the actual socket of CSI drivers by its name.
|
|
|
|
csiDrivers.Lock()
|
|
|
|
defer csiDrivers.Unlock()
|
|
|
|
csiDrivers.driversMap[pluginName] = csiDriver{driverName: pluginName, driverEndpoint: endpoint}
|
|
|
|
|
2018-08-07 19:28:19 +00:00
|
|
|
// Get node info from the driver.
|
|
|
|
csi := newCsiDriverClient(pluginName)
|
|
|
|
// TODO (verult) retry with exponential backoff, possibly added in csi client library.
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
|
|
|
|
defer cancel()
|
2018-08-13 23:38:30 +00:00
|
|
|
driverNodeID, maxVolumePerNode, _, err := csi.NodeGetInfo(ctx)
|
2018-08-07 19:28:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error during CSI NodeGetInfo() call: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calling nodeLabelManager to update annotations and labels for newly registered CSI driver
|
2018-08-13 23:38:30 +00:00
|
|
|
err = nodeUpdater.AddLabelsAndLimits(pluginName, driverNodeID, maxVolumePerNode)
|
2018-08-07 19:28:19 +00:00
|
|
|
if err != nil {
|
|
|
|
// Unregister the driver and return error
|
|
|
|
csiDrivers.Lock()
|
|
|
|
defer csiDrivers.Unlock()
|
|
|
|
delete(csiDrivers.driversMap, pluginName)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-06-04 20:47:24 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
func (p *csiPlugin) Init(host volume.VolumeHost) error {
|
|
|
|
glog.Info(log("plugin initializing..."))
|
|
|
|
p.host = host
|
2018-06-04 20:47:24 +00:00
|
|
|
|
|
|
|
// Initializing csiDrivers map and label management channels
|
|
|
|
csiDrivers = csiDriversStore{driversMap: map[string]csiDriver{}}
|
2018-08-13 23:38:30 +00:00
|
|
|
nodeUpdater = nodeupdater.NewNodeUpdater(host.GetNodeName(), host.GetKubeClient())
|
2018-06-04 20:47:24 +00:00
|
|
|
|
2018-08-22 10:49:05 +00:00
|
|
|
csiClient := host.GetCSIClient()
|
|
|
|
if csiClient != nil {
|
|
|
|
// Start informer for CSIDrivers.
|
|
|
|
factory := csiapiinformer.NewSharedInformerFactory(csiClient, csiResyncPeriod)
|
|
|
|
p.csiDriverInformer = factory.Csi().V1alpha1().CSIDrivers()
|
2018-08-28 12:59:33 +00:00
|
|
|
p.csiDriverLister = p.csiDriverInformer.Lister()
|
2018-08-22 10:49:05 +00:00
|
|
|
go factory.Start(wait.NeverStop)
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) GetPluginName() string {
|
|
|
|
return csiPluginName
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetvolumeName returns a concatenated string of CSIVolumeSource.Driver<volNameSe>CSIVolumeSource.VolumeHandle
|
|
|
|
// That string value is used in Detach() to extract driver name and volumeName.
|
|
|
|
func (p *csiPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
|
|
|
csi, err := getCSISourceFromSpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(log("plugin.GetVolumeName failed to extract volume source from spec: %v", err))
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// return driverName<separator>volumeHandle
|
|
|
|
return fmt.Sprintf("%s%s%s", csi.Driver, volNameSep, csi.VolumeHandle), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) CanSupport(spec *volume.Spec) bool {
|
|
|
|
// TODO (vladimirvivien) CanSupport should also take into account
|
|
|
|
// the availability/registration of specified Driver in the volume source
|
|
|
|
return spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CSI != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) RequiresRemount() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) NewMounter(
|
|
|
|
spec *volume.Spec,
|
|
|
|
pod *api.Pod,
|
|
|
|
_ volume.VolumeOptions) (volume.Mounter, error) {
|
|
|
|
pvSource, err := getCSISourceFromSpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-03-11 02:35:37 +00:00
|
|
|
readOnly, err := getReadOnlyFromSpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-10-25 02:44:48 +00:00
|
|
|
|
|
|
|
k8s := p.host.GetKubeClient()
|
|
|
|
if k8s == nil {
|
|
|
|
glog.Error(log("failed to get a kubernetes client"))
|
|
|
|
return nil, errors.New("failed to get a Kubernetes client")
|
|
|
|
}
|
|
|
|
|
2018-05-30 14:33:33 +00:00
|
|
|
csi := newCsiDriverClient(pvSource.Driver)
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
mounter := &csiMountMgr{
|
2017-12-04 22:29:01 +00:00
|
|
|
plugin: p,
|
|
|
|
k8s: k8s,
|
|
|
|
spec: spec,
|
|
|
|
pod: pod,
|
|
|
|
podUID: pod.UID,
|
|
|
|
driverName: pvSource.Driver,
|
|
|
|
volumeID: pvSource.VolumeHandle,
|
|
|
|
specVolumeID: spec.Name(),
|
2018-05-30 14:33:33 +00:00
|
|
|
csiClient: csi,
|
2018-03-11 02:35:37 +00:00
|
|
|
readOnly: readOnly,
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
2018-05-30 14:33:33 +00:00
|
|
|
|
|
|
|
// Save volume info in pod dir
|
|
|
|
dir := mounter.GetPath()
|
|
|
|
dataDir := path.Dir(dir) // dropoff /mount at end
|
|
|
|
|
|
|
|
if err := os.MkdirAll(dataDir, 0750); err != nil {
|
|
|
|
glog.Error(log("failed to create dir %#v: %v", dataDir, err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
glog.V(4).Info(log("created path successfully [%s]", dataDir))
|
|
|
|
|
|
|
|
// persist volume info data for teardown
|
|
|
|
node := string(p.host.GetNodeName())
|
|
|
|
attachID := getAttachmentName(pvSource.VolumeHandle, pvSource.Driver, node)
|
|
|
|
volData := map[string]string{
|
|
|
|
volDataKey.specVolID: spec.Name(),
|
|
|
|
volDataKey.volHandle: pvSource.VolumeHandle,
|
|
|
|
volDataKey.driverName: pvSource.Driver,
|
|
|
|
volDataKey.nodeName: node,
|
|
|
|
volDataKey.attachmentID: attachID,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := saveVolumeData(dataDir, volDataFileName, volData); err != nil {
|
|
|
|
glog.Error(log("failed to save volume info data: %v", err))
|
|
|
|
if err := os.RemoveAll(dataDir); err != nil {
|
|
|
|
glog.Error(log("failed to remove dir after error [%s]: %v", dataDir, err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.V(4).Info(log("mounter created successfully"))
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
return mounter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) NewUnmounter(specName string, podUID types.UID) (volume.Unmounter, error) {
|
|
|
|
glog.V(4).Infof(log("setting up unmounter for [name=%v, podUID=%v]", specName, podUID))
|
2018-05-30 14:33:33 +00:00
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
unmounter := &csiMountMgr{
|
2017-12-04 22:29:01 +00:00
|
|
|
plugin: p,
|
|
|
|
podUID: podUID,
|
|
|
|
specVolumeID: specName,
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
2018-05-30 14:33:33 +00:00
|
|
|
|
|
|
|
// load volume info from file
|
|
|
|
dir := unmounter.GetPath()
|
|
|
|
dataDir := path.Dir(dir) // dropoff /mount at end
|
|
|
|
data, err := loadVolumeData(dataDir, volDataFileName)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(log("unmounter failed to load volume data file [%s]: %v", dir, err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
unmounter.driverName = data[volDataKey.driverName]
|
|
|
|
unmounter.volumeID = data[volDataKey.volHandle]
|
|
|
|
unmounter.csiClient = newCsiDriverClient(unmounter.driverName)
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
return unmounter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
2017-12-04 22:29:01 +00:00
|
|
|
glog.V(4).Info(log("plugin.ConstructVolumeSpec [pv.Name=%v, path=%v]", volumeName, mountPath))
|
2017-10-25 02:44:48 +00:00
|
|
|
|
2017-12-04 22:29:01 +00:00
|
|
|
volData, err := loadVolumeData(mountPath, volDataFileName)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(log("plugin.ConstructVolumeSpec failed loading volume data using [%s]: %v", mountPath, err))
|
|
|
|
return nil, err
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
|
2017-12-04 22:29:01 +00:00
|
|
|
glog.V(4).Info(log("plugin.ConstructVolumeSpec extracted [%#v]", volData))
|
2017-10-25 02:44:48 +00:00
|
|
|
|
2018-06-26 00:22:44 +00:00
|
|
|
fsMode := api.PersistentVolumeFilesystem
|
2017-10-25 02:44:48 +00:00
|
|
|
pv := &api.PersistentVolume{
|
|
|
|
ObjectMeta: meta.ObjectMeta{
|
2017-12-04 22:29:01 +00:00
|
|
|
Name: volData[volDataKey.specVolID],
|
2017-10-25 02:44:48 +00:00
|
|
|
},
|
|
|
|
Spec: api.PersistentVolumeSpec{
|
|
|
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
|
|
|
CSI: &api.CSIPersistentVolumeSource{
|
2017-12-04 22:29:01 +00:00
|
|
|
Driver: volData[volDataKey.driverName],
|
|
|
|
VolumeHandle: volData[volDataKey.volHandle],
|
2017-10-25 02:44:48 +00:00
|
|
|
},
|
|
|
|
},
|
2018-06-26 00:22:44 +00:00
|
|
|
VolumeMode: &fsMode,
|
2017-10-25 02:44:48 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return volume.NewSpecFromPersistentVolume(pv, false), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) SupportsMountOption() bool {
|
|
|
|
// TODO (vladimirvivien) use CSI VolumeCapability.MountVolume.mount_flags
|
2018-07-19 12:01:09 +00:00
|
|
|
// to probe for the result for this method
|
2017-10-25 02:44:48 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) SupportsBulkVolumeVerification() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// volume.AttachableVolumePlugin methods
|
|
|
|
var _ volume.AttachableVolumePlugin = &csiPlugin{}
|
|
|
|
|
2018-08-01 13:00:45 +00:00
|
|
|
var _ volume.DeviceMountableVolumePlugin = &csiPlugin{}
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
func (p *csiPlugin) NewAttacher() (volume.Attacher, error) {
|
|
|
|
k8s := p.host.GetKubeClient()
|
|
|
|
if k8s == nil {
|
|
|
|
glog.Error(log("unable to get kubernetes client from host"))
|
|
|
|
return nil, errors.New("unable to get Kubernetes client")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &csiAttacher{
|
|
|
|
plugin: p,
|
|
|
|
k8s: k8s,
|
|
|
|
waitSleepTime: 1 * time.Second,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-08-01 13:00:45 +00:00
|
|
|
func (p *csiPlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
|
|
|
|
return p.NewAttacher()
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
func (p *csiPlugin) NewDetacher() (volume.Detacher, error) {
|
|
|
|
k8s := p.host.GetKubeClient()
|
|
|
|
if k8s == nil {
|
|
|
|
glog.Error(log("unable to get kubernetes client from host"))
|
|
|
|
return nil, errors.New("unable to get Kubernetes client")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &csiAttacher{
|
|
|
|
plugin: p,
|
|
|
|
k8s: k8s,
|
|
|
|
waitSleepTime: 1 * time.Second,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-08-01 13:00:45 +00:00
|
|
|
func (p *csiPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
|
|
|
|
return p.NewDetacher()
|
|
|
|
}
|
|
|
|
|
2017-10-25 02:44:48 +00:00
|
|
|
func (p *csiPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
|
|
|
|
m := p.host.GetMounter(p.GetPluginName())
|
2018-05-29 04:36:31 +00:00
|
|
|
return m.GetMountRefs(deviceMountPath)
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 18:28:53 +00:00
|
|
|
// BlockVolumePlugin methods
|
|
|
|
var _ volume.BlockVolumePlugin = &csiPlugin{}
|
|
|
|
|
|
|
|
func (p *csiPlugin) NewBlockVolumeMapper(spec *volume.Spec, podRef *api.Pod, opts volume.VolumeOptions) (volume.BlockVolumeMapper, error) {
|
|
|
|
if !p.blockEnabled {
|
|
|
|
return nil, errors.New("CSIBlockVolume feature not enabled")
|
|
|
|
}
|
|
|
|
|
|
|
|
pvSource, err := getCSISourceFromSpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
readOnly, err := getReadOnlyFromSpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 18:28:53 +00:00
|
|
|
glog.V(4).Info(log("setting up block mapper for [volume=%v,driver=%v]", pvSource.VolumeHandle, pvSource.Driver))
|
|
|
|
client := newCsiDriverClient(pvSource.Driver)
|
|
|
|
|
|
|
|
k8s := p.host.GetKubeClient()
|
|
|
|
if k8s == nil {
|
|
|
|
glog.Error(log("failed to get a kubernetes client"))
|
|
|
|
return nil, errors.New("failed to get a Kubernetes client")
|
|
|
|
}
|
|
|
|
|
|
|
|
mapper := &csiBlockMapper{
|
|
|
|
csiClient: client,
|
|
|
|
k8s: k8s,
|
|
|
|
plugin: p,
|
|
|
|
volumeID: pvSource.VolumeHandle,
|
|
|
|
driverName: pvSource.Driver,
|
|
|
|
readOnly: readOnly,
|
|
|
|
spec: spec,
|
2018-06-11 17:34:04 +00:00
|
|
|
specName: spec.Name(),
|
2018-05-09 18:28:53 +00:00
|
|
|
podUID: podRef.UID,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save volume info in pod dir
|
|
|
|
dataDir := getVolumeDeviceDataDir(spec.Name(), p.host)
|
|
|
|
|
|
|
|
if err := os.MkdirAll(dataDir, 0750); err != nil {
|
|
|
|
glog.Error(log("failed to create data dir %s: %v", dataDir, err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
glog.V(4).Info(log("created path successfully [%s]", dataDir))
|
|
|
|
|
|
|
|
// persist volume info data for teardown
|
|
|
|
node := string(p.host.GetNodeName())
|
|
|
|
attachID := getAttachmentName(pvSource.VolumeHandle, pvSource.Driver, node)
|
|
|
|
volData := map[string]string{
|
|
|
|
volDataKey.specVolID: spec.Name(),
|
|
|
|
volDataKey.volHandle: pvSource.VolumeHandle,
|
|
|
|
volDataKey.driverName: pvSource.Driver,
|
|
|
|
volDataKey.nodeName: node,
|
|
|
|
volDataKey.attachmentID: attachID,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := saveVolumeData(dataDir, volDataFileName, volData); err != nil {
|
|
|
|
glog.Error(log("failed to save volume info data: %v", err))
|
|
|
|
if err := os.RemoveAll(dataDir); err != nil {
|
|
|
|
glog.Error(log("failed to remove dir after error [%s]: %v", dataDir, err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapper, nil
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 18:28:53 +00:00
|
|
|
func (p *csiPlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) {
|
|
|
|
if !p.blockEnabled {
|
|
|
|
return nil, errors.New("CSIBlockVolume feature not enabled")
|
2018-03-11 02:35:37 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 18:28:53 +00:00
|
|
|
glog.V(4).Infof(log("setting up block unmapper for [Spec=%v, podUID=%v]", volName, podUID))
|
|
|
|
unmapper := &csiBlockMapper{
|
|
|
|
plugin: p,
|
|
|
|
podUID: podUID,
|
|
|
|
specName: volName,
|
|
|
|
}
|
|
|
|
|
|
|
|
// load volume info from file
|
|
|
|
dataDir := getVolumeDeviceDataDir(unmapper.specName, p.host)
|
|
|
|
data, err := loadVolumeData(dataDir, volDataFileName)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(log("unmapper failed to load volume data file [%s]: %v", dataDir, err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
unmapper.driverName = data[volDataKey.driverName]
|
|
|
|
unmapper.volumeID = data[volDataKey.volHandle]
|
|
|
|
unmapper.csiClient = newCsiDriverClient(unmapper.driverName)
|
|
|
|
|
|
|
|
return unmapper, nil
|
2018-03-11 02:35:37 +00:00
|
|
|
}
|
|
|
|
|
2018-05-09 18:28:53 +00:00
|
|
|
func (p *csiPlugin) ConstructBlockVolumeSpec(podUID types.UID, specVolName, mapPath string) (*volume.Spec, error) {
|
|
|
|
if !p.blockEnabled {
|
|
|
|
return nil, errors.New("CSIBlockVolume feature not enabled")
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.V(4).Infof("plugin.ConstructBlockVolumeSpec [podUID=%s, specVolName=%s, path=%s]", string(podUID), specVolName, mapPath)
|
|
|
|
|
|
|
|
dataDir := getVolumeDeviceDataDir(specVolName, p.host)
|
|
|
|
volData, err := loadVolumeData(dataDir, volDataFileName)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(log("plugin.ConstructBlockVolumeSpec failed loading volume data using [%s]: %v", mapPath, err))
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.V(4).Info(log("plugin.ConstructBlockVolumeSpec extracted [%#v]", volData))
|
|
|
|
|
|
|
|
blockMode := api.PersistentVolumeBlock
|
|
|
|
pv := &api.PersistentVolume{
|
|
|
|
ObjectMeta: meta.ObjectMeta{
|
|
|
|
Name: volData[volDataKey.specVolID],
|
|
|
|
},
|
|
|
|
Spec: api.PersistentVolumeSpec{
|
|
|
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
|
|
|
CSI: &api.CSIPersistentVolumeSource{
|
|
|
|
Driver: volData[volDataKey.driverName],
|
|
|
|
VolumeHandle: volData[volDataKey.volHandle],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
VolumeMode: &blockMode,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return volume.NewSpecFromPersistentVolume(pv, false), nil
|
2017-10-25 02:44:48 +00:00
|
|
|
}
|
2018-08-28 13:01:39 +00:00
|
|
|
|
|
|
|
func (p *csiPlugin) skipAttach(driver string) (bool, error) {
|
|
|
|
if !utilfeature.DefaultFeatureGate.Enabled(features.CSISkipAttach) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
if p.csiDriverLister == nil {
|
|
|
|
return false, errors.New("CSIDriver lister does not exist")
|
|
|
|
}
|
|
|
|
csiDriver, err := p.csiDriverLister.Get(driver)
|
|
|
|
if err != nil {
|
|
|
|
if apierrs.IsNotFound(err) {
|
|
|
|
// Don't skip attach if CSIDriver does not exist
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if csiDriver.Spec.AttachRequired != nil && *csiDriver.Spec.AttachRequired == false {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *csiPlugin) getPublishVolumeInfo(client clientset.Interface, handle, driver, nodeName string) (map[string]string, error) {
|
|
|
|
skip, err := p.skipAttach(driver)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if skip {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
attachID := getAttachmentName(handle, driver, nodeName)
|
|
|
|
|
|
|
|
// search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName
|
|
|
|
attachment, err := client.StorageV1beta1().VolumeAttachments().Get(attachID, meta.GetOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err // This err already has enough context ("VolumeAttachment xyz not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
if attachment == nil {
|
|
|
|
err = errors.New("no existing VolumeAttachment found")
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return attachment.Status.AttachmentMetadata, nil
|
|
|
|
}
|