2014-11-23 15:47:25 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
2014-11-23 15:47:25 +00:00
|
|
|
|
|
|
|
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 volume
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
2015-02-18 01:26:41 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
2014-11-23 15:47:25 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
2015-05-04 14:43:10 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
|
2014-11-23 15:47:25 +00:00
|
|
|
"github.com/golang/glog"
|
|
|
|
)
|
|
|
|
|
2015-04-10 20:56:11 +00:00
|
|
|
// VolumeOptions contains option information about a volume.
|
|
|
|
//
|
|
|
|
// Currently, this struct containers only a single field for the
|
|
|
|
// rootcontext of the volume. This is a temporary measure in order
|
|
|
|
// to set the rootContext of tmpfs mounts correctly; it will be replaced
|
|
|
|
// and expanded on by future SecurityContext work.
|
|
|
|
type VolumeOptions struct {
|
|
|
|
// The rootcontext to use when performing mounts for a volume.
|
|
|
|
RootContext string
|
|
|
|
}
|
|
|
|
|
2015-03-19 05:18:31 +00:00
|
|
|
// VolumePlugin is an interface to volume plugins that can be used on a
|
|
|
|
// kubernetes node (e.g. by kubelet) to instantiate and manage volumes.
|
|
|
|
type VolumePlugin interface {
|
2014-11-23 15:47:25 +00:00
|
|
|
// Init initializes the plugin. This will be called exactly once
|
|
|
|
// before any New* calls are made - implementations of plugins may
|
|
|
|
// depend on this.
|
2015-03-19 05:18:31 +00:00
|
|
|
Init(host VolumeHost)
|
2014-11-23 15:47:25 +00:00
|
|
|
|
|
|
|
// Name returns the plugin's name. Plugins should use namespaced names
|
|
|
|
// such as "example.com/volume". The "kubernetes.io" namespace is
|
|
|
|
// reserved for plugins which are bundled with kubernetes.
|
|
|
|
Name() string
|
|
|
|
|
2015-03-19 05:18:31 +00:00
|
|
|
// CanSupport tests whether the plugin supports a given volume
|
2014-11-23 15:47:25 +00:00
|
|
|
// specification from the API. The spec pointer should be considered
|
|
|
|
// const.
|
2015-04-14 16:29:33 +00:00
|
|
|
CanSupport(spec *Spec) bool
|
2014-11-23 15:47:25 +00:00
|
|
|
|
|
|
|
// NewBuilder creates a new volume.Builder from an API specification.
|
|
|
|
// Ownership of the spec pointer in *not* transferred.
|
|
|
|
// - spec: The api.Volume spec
|
2015-05-11 00:12:57 +00:00
|
|
|
// - pod: The enclosing pod
|
|
|
|
NewBuilder(spec *Spec, podRef *api.Pod, opts VolumeOptions, mounter mount.Interface) (Builder, error)
|
2014-11-23 15:47:25 +00:00
|
|
|
|
|
|
|
// NewCleaner creates a new volume.Cleaner from recoverable state.
|
|
|
|
// - name: The volume name, as per the api.Volume spec.
|
|
|
|
// - podUID: The UID of the enclosing pod
|
2015-05-04 14:43:10 +00:00
|
|
|
NewCleaner(name string, podUID types.UID, mounter mount.Interface) (Cleaner, error)
|
2014-11-23 15:47:25 +00:00
|
|
|
}
|
|
|
|
|
2015-03-12 19:37:02 +00:00
|
|
|
// PersistentVolumePlugin is an extended interface of VolumePlugin and is used
|
|
|
|
// by volumes that want to provide long term persistence of data
|
|
|
|
type PersistentVolumePlugin interface {
|
|
|
|
VolumePlugin
|
|
|
|
// GetAccessModes describes the ways a given volume can be accessed/mounted.
|
2015-05-18 20:22:30 +00:00
|
|
|
GetAccessModes() []api.PersistentVolumeAccessMode
|
2015-03-12 19:37:02 +00:00
|
|
|
}
|
|
|
|
|
2015-05-29 20:32:44 +00:00
|
|
|
// RecyclableVolumePlugin is an extended interface of VolumePlugin and is used
|
|
|
|
// by persistent volumes that want to be recycled before being made available again to new claims
|
|
|
|
type RecyclableVolumePlugin interface {
|
|
|
|
VolumePlugin
|
|
|
|
// NewRecycler creates a new volume.Recycler which knows how to reclaim this resource
|
|
|
|
// after the volume's release from a PersistentVolumeClaim
|
|
|
|
NewRecycler(spec *Spec) (Recycler, error)
|
|
|
|
}
|
|
|
|
|
2015-03-19 05:18:31 +00:00
|
|
|
// VolumeHost is an interface that plugins can use to access the kubelet.
|
|
|
|
type VolumeHost interface {
|
2014-11-23 15:47:25 +00:00
|
|
|
// GetPluginDir returns the absolute path to a directory under which
|
|
|
|
// a given plugin may store data. This directory might not actually
|
|
|
|
// exist on disk yet. For plugin data that is per-pod, see
|
|
|
|
// GetPodPluginDir().
|
|
|
|
GetPluginDir(pluginName string) string
|
|
|
|
|
|
|
|
// GetPodVolumeDir returns the absolute path a directory which
|
|
|
|
// represents the named volume under the named plugin for the given
|
|
|
|
// pod. If the specified pod does not exist, the result of this call
|
|
|
|
// might not exist.
|
|
|
|
GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string
|
|
|
|
|
|
|
|
// GetPodPluginDir returns the absolute path to a directory under which
|
|
|
|
// a given plugin may store data for a given pod. If the specified pod
|
|
|
|
// does not exist, the result of this call might not exist. This
|
|
|
|
// directory might not actually exist on disk yet.
|
|
|
|
GetPodPluginDir(podUID types.UID, pluginName string) string
|
2015-02-18 01:26:41 +00:00
|
|
|
|
|
|
|
// GetKubeClient returns a client interface
|
|
|
|
GetKubeClient() client.Interface
|
2015-03-07 21:38:50 +00:00
|
|
|
|
|
|
|
// NewWrapperBuilder finds an appropriate plugin with which to handle
|
|
|
|
// the provided spec. This is used to implement volume plugins which
|
|
|
|
// "wrap" other plugins. For example, the "secret" volume is
|
|
|
|
// implemented in terms of the "emptyDir" volume.
|
2015-05-11 00:12:57 +00:00
|
|
|
NewWrapperBuilder(spec *Spec, pod *api.Pod, opts VolumeOptions, mounter mount.Interface) (Builder, error)
|
2015-03-07 21:38:50 +00:00
|
|
|
|
|
|
|
// NewWrapperCleaner finds an appropriate plugin with which to handle
|
|
|
|
// the provided spec. See comments on NewWrapperBuilder for more
|
|
|
|
// context.
|
2015-05-04 14:43:10 +00:00
|
|
|
NewWrapperCleaner(spec *Spec, podUID types.UID, mounter mount.Interface) (Cleaner, error)
|
2014-11-23 15:47:25 +00:00
|
|
|
}
|
|
|
|
|
2015-03-19 05:18:31 +00:00
|
|
|
// VolumePluginMgr tracks registered plugins.
|
|
|
|
type VolumePluginMgr struct {
|
2014-11-23 15:47:25 +00:00
|
|
|
mutex sync.Mutex
|
2015-03-19 05:18:31 +00:00
|
|
|
plugins map[string]VolumePlugin
|
2014-11-23 15:47:25 +00:00
|
|
|
}
|
|
|
|
|
2015-04-14 16:29:33 +00:00
|
|
|
// Spec is an internal representation of a volume. All API volume types translate to Spec.
|
|
|
|
type Spec struct {
|
|
|
|
Name string
|
|
|
|
VolumeSource api.VolumeSource
|
|
|
|
PersistentVolumeSource api.PersistentVolumeSource
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSpecFromVolume creates an Spec from an api.Volume
|
|
|
|
func NewSpecFromVolume(vs *api.Volume) *Spec {
|
|
|
|
return &Spec{
|
|
|
|
Name: vs.Name,
|
|
|
|
VolumeSource: vs.VolumeSource,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSpecFromPersistentVolume creates an Spec from an api.PersistentVolume
|
|
|
|
func NewSpecFromPersistentVolume(pv *api.PersistentVolume) *Spec {
|
|
|
|
return &Spec{
|
|
|
|
Name: pv.Name,
|
|
|
|
PersistentVolumeSource: pv.Spec.PersistentVolumeSource,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-23 15:47:25 +00:00
|
|
|
// InitPlugins initializes each plugin. All plugins must have unique names.
|
|
|
|
// This must be called exactly once before any New* methods are called on any
|
|
|
|
// plugins.
|
2015-03-19 05:18:31 +00:00
|
|
|
func (pm *VolumePluginMgr) InitPlugins(plugins []VolumePlugin, host VolumeHost) error {
|
2014-11-23 15:47:25 +00:00
|
|
|
pm.mutex.Lock()
|
|
|
|
defer pm.mutex.Unlock()
|
|
|
|
|
|
|
|
if pm.plugins == nil {
|
2015-03-19 05:18:31 +00:00
|
|
|
pm.plugins = map[string]VolumePlugin{}
|
2014-11-23 15:47:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
allErrs := []error{}
|
|
|
|
for _, plugin := range plugins {
|
|
|
|
name := plugin.Name()
|
|
|
|
if !util.IsQualifiedName(name) {
|
|
|
|
allErrs = append(allErrs, fmt.Errorf("volume plugin has invalid name: %#v", plugin))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, found := pm.plugins[name]; found {
|
|
|
|
allErrs = append(allErrs, fmt.Errorf("volume plugin %q was registered more than once", name))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
plugin.Init(host)
|
|
|
|
pm.plugins[name] = plugin
|
|
|
|
glog.V(1).Infof("Loaded volume plugin %q", name)
|
|
|
|
}
|
|
|
|
return errors.NewAggregate(allErrs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindPluginBySpec looks for a plugin that can support a given volume
|
|
|
|
// specification. If no plugins can support or more than one plugin can
|
|
|
|
// support it, return error.
|
2015-04-14 16:29:33 +00:00
|
|
|
func (pm *VolumePluginMgr) FindPluginBySpec(spec *Spec) (VolumePlugin, error) {
|
2014-11-23 15:47:25 +00:00
|
|
|
pm.mutex.Lock()
|
|
|
|
defer pm.mutex.Unlock()
|
|
|
|
|
|
|
|
matches := []string{}
|
|
|
|
for k, v := range pm.plugins {
|
|
|
|
if v.CanSupport(spec) {
|
|
|
|
matches = append(matches, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(matches) == 0 {
|
|
|
|
return nil, fmt.Errorf("no volume plugin matched")
|
|
|
|
}
|
|
|
|
if len(matches) > 1 {
|
|
|
|
return nil, fmt.Errorf("multiple volume plugins matched: %s", strings.Join(matches, ","))
|
|
|
|
}
|
|
|
|
return pm.plugins[matches[0]], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindPluginByName fetches a plugin by name or by legacy name. If no plugin
|
|
|
|
// is found, returns error.
|
2015-03-19 05:18:31 +00:00
|
|
|
func (pm *VolumePluginMgr) FindPluginByName(name string) (VolumePlugin, error) {
|
2014-11-23 15:47:25 +00:00
|
|
|
pm.mutex.Lock()
|
|
|
|
defer pm.mutex.Unlock()
|
|
|
|
|
|
|
|
// Once we can get rid of legacy names we can reduce this to a map lookup.
|
|
|
|
matches := []string{}
|
|
|
|
for k, v := range pm.plugins {
|
|
|
|
if v.Name() == name {
|
|
|
|
matches = append(matches, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(matches) == 0 {
|
|
|
|
return nil, fmt.Errorf("no volume plugin matched")
|
|
|
|
}
|
|
|
|
if len(matches) > 1 {
|
|
|
|
return nil, fmt.Errorf("multiple volume plugins matched: %s", strings.Join(matches, ","))
|
|
|
|
}
|
|
|
|
return pm.plugins[matches[0]], nil
|
|
|
|
}
|
2015-03-12 19:37:02 +00:00
|
|
|
|
2015-05-29 20:32:44 +00:00
|
|
|
// FindPersistentPluginBySpec looks for a persistent volume plugin that can support a given volume
|
|
|
|
// specification. If no plugin is found, return an error
|
|
|
|
func (pm *VolumePluginMgr) FindPersistentPluginBySpec(spec *Spec) (PersistentVolumePlugin, error) {
|
|
|
|
volumePlugin, err := pm.FindPluginBySpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Could not find volume plugin for spec: %+v", spec)
|
|
|
|
}
|
|
|
|
if persistentVolumePlugin, ok := volumePlugin.(PersistentVolumePlugin); ok {
|
|
|
|
return persistentVolumePlugin, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("no persistent volume plugin matched")
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindPersistentPluginByName fetches a persistent volume plugin by name. If no plugin
|
2015-03-12 19:37:02 +00:00
|
|
|
// is found, returns error.
|
|
|
|
func (pm *VolumePluginMgr) FindPersistentPluginByName(name string) (PersistentVolumePlugin, error) {
|
|
|
|
volumePlugin, err := pm.FindPluginByName(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-24 21:33:31 +00:00
|
|
|
if persistentVolumePlugin, ok := volumePlugin.(PersistentVolumePlugin); ok {
|
|
|
|
return persistentVolumePlugin, nil
|
|
|
|
}
|
2015-05-29 20:32:44 +00:00
|
|
|
return nil, fmt.Errorf("no persistent volume plugin matched: %+v")
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindRecyclablePluginByName fetches a persistent volume plugin by name. If no plugin
|
|
|
|
// is found, returns error.
|
|
|
|
func (pm *VolumePluginMgr) FindRecyclablePluginBySpec(spec *Spec) (RecyclableVolumePlugin, error) {
|
|
|
|
volumePlugin, err := pm.FindPluginBySpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if recyclableVolumePlugin, ok := volumePlugin.(RecyclableVolumePlugin); ok {
|
|
|
|
return recyclableVolumePlugin, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("no recyclable volume plugin matched")
|
2015-03-12 19:37:02 +00:00
|
|
|
}
|