123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- /*
- 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 volume
- import (
- "fmt"
- "net"
- "strings"
- "sync"
- "github.com/golang/glog"
- "k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/api/resource"
- "k8s.io/kubernetes/pkg/api/unversioned"
- clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
- "k8s.io/kubernetes/pkg/cloudprovider"
- "k8s.io/kubernetes/pkg/types"
- utilerrors "k8s.io/kubernetes/pkg/util/errors"
- "k8s.io/kubernetes/pkg/util/io"
- "k8s.io/kubernetes/pkg/util/mount"
- "k8s.io/kubernetes/pkg/util/validation"
- )
- // VolumeOptions contains option information about a volume.
- type VolumeOptions struct {
- // The attributes below are required by volume.Provisioner
- // TODO: refactor all of this out of volumes when an admin can configure
- // many kinds of provisioners.
- // Capacity is the size of a volume.
- Capacity resource.Quantity
- // AccessModes of a volume
- AccessModes []api.PersistentVolumeAccessMode
- // Reclamation policy for a persistent volume
- PersistentVolumeReclaimPolicy api.PersistentVolumeReclaimPolicy
- // PV.Name of the appropriate PersistentVolume. Used to generate cloud
- // volume name.
- PVName string
- // PVC.Name of the PersistentVolumeClaim; only set during dynamic provisioning.
- PVCName string
- // Unique name of Kubernetes cluster.
- ClusterName string
- // Tags to attach to the real volume in the cloud provider - e.g. AWS EBS
- CloudTags *map[string]string
- // Volume provisioning parameters from StorageClass
- Parameters map[string]string
- // Volume selector from PersistentVolumeClaim
- Selector *unversioned.LabelSelector
- }
- // 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 {
- // Init initializes the plugin. This will be called exactly once
- // before any New* calls are made - implementations of plugins may
- // depend on this.
- Init(host VolumeHost) error
- // 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.
- GetPluginName() string
- // GetVolumeName returns the name/ID to uniquely identifying the actual
- // backing device, directory, path, etc. referenced by the specified volume
- // spec.
- // For Attachable volumes, this value must be able to be passed back to
- // volume Detach methods to identify the device to act on.
- // If the plugin does not support the given spec, this returns an error.
- GetVolumeName(spec *Spec) (string, error)
- // CanSupport tests whether the plugin supports a given volume
- // specification from the API. The spec pointer should be considered
- // const.
- CanSupport(spec *Spec) bool
- // RequiresRemount returns true if this plugin requires mount calls to be
- // reexecuted. Atomically updating volumes, like Downward API, depend on
- // this to update the contents of the volume.
- RequiresRemount() bool
- // NewMounter creates a new volume.Mounter from an API specification.
- // Ownership of the spec pointer in *not* transferred.
- // - spec: The api.Volume spec
- // - pod: The enclosing pod
- NewMounter(spec *Spec, podRef *api.Pod, opts VolumeOptions) (Mounter, error)
- // NewUnmounter creates a new volume.Unmounter from recoverable state.
- // - name: The volume name, as per the api.Volume spec.
- // - podUID: The UID of the enclosing pod
- NewUnmounter(name string, podUID types.UID) (Unmounter, error)
- // ConstructVolumeSpec constructs a volume spec based on the given volume name
- // and mountPath. The spec may have incomplete information due to limited
- // information from input. This function is used by volume manager to reconstruct
- // volume spec by reading the volume directories from disk
- ConstructVolumeSpec(volumeName, mountPath string) (*Spec, error)
- }
- // 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.
- GetAccessModes() []api.PersistentVolumeAccessMode
- }
- // 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(pvName string, spec *Spec) (Recycler, error)
- }
- // DeletableVolumePlugin is an extended interface of VolumePlugin and is used
- // by persistent volumes that want to be deleted from the cluster after their
- // release from a PersistentVolumeClaim.
- type DeletableVolumePlugin interface {
- VolumePlugin
- // NewDeleter creates a new volume.Deleter which knows how to delete this
- // resource in accordance with the underlying storage provider after the
- // volume's release from a claim
- NewDeleter(spec *Spec) (Deleter, error)
- }
- const (
- // Name of a volume in external cloud that is being provisioned and thus
- // should be ignored by rest of Kubernetes.
- ProvisionedVolumeName = "placeholder-for-provisioning"
- )
- // ProvisionableVolumePlugin is an extended interface of VolumePlugin and is
- // used to create volumes for the cluster.
- type ProvisionableVolumePlugin interface {
- VolumePlugin
- // NewProvisioner creates a new volume.Provisioner which knows how to
- // create PersistentVolumes in accordance with the plugin's underlying
- // storage provider
- NewProvisioner(options VolumeOptions) (Provisioner, error)
- }
- // AttachableVolumePlugin is an extended interface of VolumePlugin and is used for volumes that require attachment
- // to a node before mounting.
- type AttachableVolumePlugin interface {
- VolumePlugin
- NewAttacher() (Attacher, error)
- NewDetacher() (Detacher, error)
- GetDeviceMountRefs(deviceMountPath string) ([]string, error)
- }
- // VolumeHost is an interface that plugins can use to access the kubelet.
- type VolumeHost interface {
- // 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
- // GetKubeClient returns a client interface
- GetKubeClient() clientset.Interface
- // NewWrapperMounter 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.
- NewWrapperMounter(volName string, spec Spec, pod *api.Pod, opts VolumeOptions) (Mounter, error)
- // NewWrapperUnmounter finds an appropriate plugin with which to handle
- // the provided spec. See comments on NewWrapperMounter for more
- // context.
- NewWrapperUnmounter(volName string, spec Spec, podUID types.UID) (Unmounter, error)
- // Get cloud provider from kubelet.
- GetCloudProvider() cloudprovider.Interface
- // Get mounter interface.
- GetMounter() mount.Interface
- // Get writer interface for writing data to disk.
- GetWriter() io.Writer
- // Returns the hostname of the host kubelet is running on
- GetHostName() string
- // Returns host IP or nil in the case of error.
- GetHostIP() (net.IP, error)
- // Returns the rootcontext to use when performing mounts for a 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.
- GetRootContext() string
- // Returns node allocatable
- GetNodeAllocatable() (api.ResourceList, error)
- }
- // VolumePluginMgr tracks registered plugins.
- type VolumePluginMgr struct {
- mutex sync.Mutex
- plugins map[string]VolumePlugin
- }
- // Spec is an internal representation of a volume. All API volume types translate to Spec.
- type Spec struct {
- Volume *api.Volume
- PersistentVolume *api.PersistentVolume
- ReadOnly bool
- }
- // Name returns the name of either Volume or PersistentVolume, one of which must not be nil.
- func (spec *Spec) Name() string {
- switch {
- case spec.Volume != nil:
- return spec.Volume.Name
- case spec.PersistentVolume != nil:
- return spec.PersistentVolume.Name
- default:
- return ""
- }
- }
- // VolumeConfig is how volume plugins receive configuration. An instance
- // specific to the plugin will be passed to the plugin's
- // ProbeVolumePlugins(config) func. Reasonable defaults will be provided by
- // the binary hosting the plugins while allowing override of those default
- // values. Those config values are then set to an instance of VolumeConfig
- // and passed to the plugin.
- //
- // Values in VolumeConfig are intended to be relevant to several plugins, but
- // not necessarily all plugins. The preference is to leverage strong typing
- // in this struct. All config items must have a descriptive but non-specific
- // name (i.e, RecyclerMinimumTimeout is OK but RecyclerMinimumTimeoutForNFS is
- // !OK). An instance of config will be given directly to the plugin, so
- // config names specific to plugins are unneeded and wrongly expose plugins in
- // this VolumeConfig struct.
- //
- // OtherAttributes is a map of string values intended for one-off
- // configuration of a plugin or config that is only relevant to a single
- // plugin. All values are passed by string and require interpretation by the
- // plugin. Passing config as strings is the least desirable option but can be
- // used for truly one-off configuration. The binary should still use strong
- // typing for this value when binding CLI values before they are passed as
- // strings in OtherAttributes.
- type VolumeConfig struct {
- // RecyclerPodTemplate is pod template that understands how to scrub clean
- // a persistent volume after its release. The template is used by plugins
- // which override specific properties of the pod in accordance with that
- // plugin. See NewPersistentVolumeRecyclerPodTemplate for the properties
- // that are expected to be overridden.
- RecyclerPodTemplate *api.Pod
- // RecyclerMinimumTimeout is the minimum amount of time in seconds for the
- // recycler pod's ActiveDeadlineSeconds attribute. Added to the minimum
- // timeout is the increment per Gi of capacity.
- RecyclerMinimumTimeout int
- // RecyclerTimeoutIncrement is the number of seconds added to the recycler
- // pod's ActiveDeadlineSeconds for each Gi of capacity in the persistent
- // volume. Example: 5Gi volume x 30s increment = 150s + 30s minimum = 180s
- // ActiveDeadlineSeconds for recycler pod
- RecyclerTimeoutIncrement int
- // PVName is name of the PersistentVolume instance that is being recycled.
- // It is used to generate unique recycler pod name.
- PVName string
- // OtherAttributes stores config as strings. These strings are opaque to
- // the system and only understood by the binary hosting the plugin and the
- // plugin itself.
- OtherAttributes map[string]string
- // ProvisioningEnabled configures whether provisioning of this plugin is
- // enabled or not. Currently used only in host_path plugin.
- ProvisioningEnabled bool
- }
- // NewSpecFromVolume creates an Spec from an api.Volume
- func NewSpecFromVolume(vs *api.Volume) *Spec {
- return &Spec{
- Volume: vs,
- }
- }
- // NewSpecFromPersistentVolume creates an Spec from an api.PersistentVolume
- func NewSpecFromPersistentVolume(pv *api.PersistentVolume, readOnly bool) *Spec {
- return &Spec{
- PersistentVolume: pv,
- ReadOnly: readOnly,
- }
- }
- // 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.
- func (pm *VolumePluginMgr) InitPlugins(plugins []VolumePlugin, host VolumeHost) error {
- pm.mutex.Lock()
- defer pm.mutex.Unlock()
- if pm.plugins == nil {
- pm.plugins = map[string]VolumePlugin{}
- }
- allErrs := []error{}
- for _, plugin := range plugins {
- name := plugin.GetPluginName()
- if errs := validation.IsQualifiedName(name); len(errs) != 0 {
- allErrs = append(allErrs, fmt.Errorf("volume plugin has invalid name: %q: %s", name, strings.Join(errs, ";")))
- continue
- }
- if _, found := pm.plugins[name]; found {
- allErrs = append(allErrs, fmt.Errorf("volume plugin %q was registered more than once", name))
- continue
- }
- err := plugin.Init(host)
- if err != nil {
- glog.Errorf("Failed to load volume plugin %s, error: %s", plugin, err.Error())
- allErrs = append(allErrs, err)
- continue
- }
- pm.plugins[name] = plugin
- glog.V(1).Infof("Loaded volume plugin %q", name)
- }
- return utilerrors.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.
- func (pm *VolumePluginMgr) FindPluginBySpec(spec *Spec) (VolumePlugin, error) {
- 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.
- func (pm *VolumePluginMgr) FindPluginByName(name string) (VolumePlugin, error) {
- 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.GetPluginName() == 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
- }
- // 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 is found, returns error.
- func (pm *VolumePluginMgr) FindPersistentPluginByName(name string) (PersistentVolumePlugin, error) {
- volumePlugin, err := pm.FindPluginByName(name)
- if err != nil {
- return nil, err
- }
- if persistentVolumePlugin, ok := volumePlugin.(PersistentVolumePlugin); ok {
- return persistentVolumePlugin, nil
- }
- return nil, fmt.Errorf("no persistent volume plugin matched")
- }
- // 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")
- }
- // FindProvisionablePluginByName fetches a persistent volume plugin by name. If
- // no plugin is found, returns error.
- func (pm *VolumePluginMgr) FindProvisionablePluginByName(name string) (ProvisionableVolumePlugin, error) {
- volumePlugin, err := pm.FindPluginByName(name)
- if err != nil {
- return nil, err
- }
- if provisionableVolumePlugin, ok := volumePlugin.(ProvisionableVolumePlugin); ok {
- return provisionableVolumePlugin, nil
- }
- return nil, fmt.Errorf("no provisionable volume plugin matched")
- }
- // FindDeletablePluginBySppec fetches a persistent volume plugin by spec. If
- // no plugin is found, returns error.
- func (pm *VolumePluginMgr) FindDeletablePluginBySpec(spec *Spec) (DeletableVolumePlugin, error) {
- volumePlugin, err := pm.FindPluginBySpec(spec)
- if err != nil {
- return nil, err
- }
- if deletableVolumePlugin, ok := volumePlugin.(DeletableVolumePlugin); ok {
- return deletableVolumePlugin, nil
- }
- return nil, fmt.Errorf("no deletable volume plugin matched")
- }
- // FindDeletablePluginByName fetches a persistent volume plugin by name. If
- // no plugin is found, returns error.
- func (pm *VolumePluginMgr) FindDeletablePluginByName(name string) (DeletableVolumePlugin, error) {
- volumePlugin, err := pm.FindPluginByName(name)
- if err != nil {
- return nil, err
- }
- if deletableVolumePlugin, ok := volumePlugin.(DeletableVolumePlugin); ok {
- return deletableVolumePlugin, nil
- }
- return nil, fmt.Errorf("no deletable volume plugin matched")
- }
- // FindCreatablePluginBySpec fetches a persistent volume plugin by name. If
- // no plugin is found, returns error.
- func (pm *VolumePluginMgr) FindCreatablePluginBySpec(spec *Spec) (ProvisionableVolumePlugin, error) {
- volumePlugin, err := pm.FindPluginBySpec(spec)
- if err != nil {
- return nil, err
- }
- if provisionableVolumePlugin, ok := volumePlugin.(ProvisionableVolumePlugin); ok {
- return provisionableVolumePlugin, nil
- }
- return nil, fmt.Errorf("no creatable volume plugin matched")
- }
- // FindAttachablePluginBySpec fetches a persistent volume plugin by name.
- // Unlike the other "FindPlugin" methods, this does not return error if no
- // plugin is found. All volumes require a mounter and unmounter, but not
- // every volume will have an attacher/detacher.
- func (pm *VolumePluginMgr) FindAttachablePluginBySpec(spec *Spec) (AttachableVolumePlugin, error) {
- volumePlugin, err := pm.FindPluginBySpec(spec)
- if err != nil {
- return nil, err
- }
- if attachableVolumePlugin, ok := volumePlugin.(AttachableVolumePlugin); ok {
- return attachableVolumePlugin, nil
- }
- return nil, nil
- }
- // FindAttachablePluginByName fetches an attachable volume plugin by name.
- // Unlike the other "FindPlugin" methods, this does not return error if no
- // plugin is found. All volumes require a mounter and unmounter, but not
- // every volume will have an attacher/detacher.
- func (pm *VolumePluginMgr) FindAttachablePluginByName(name string) (AttachableVolumePlugin, error) {
- volumePlugin, err := pm.FindPluginByName(name)
- if err != nil {
- return nil, err
- }
- if attachablePlugin, ok := volumePlugin.(AttachableVolumePlugin); ok {
- return attachablePlugin, nil
- }
- return nil, nil
- }
- // NewPersistentVolumeRecyclerPodTemplate creates a template for a recycler
- // pod. By default, a recycler pod simply runs "rm -rf" on a volume and tests
- // for emptiness. Most attributes of the template will be correct for most
- // plugin implementations. The following attributes can be overridden per
- // plugin via configuration:
- //
- // 1. pod.Spec.Volumes[0].VolumeSource must be overridden. Recycler
- // implementations without a valid VolumeSource will fail.
- // 2. pod.GenerateName helps distinguish recycler pods by name. Recommended.
- // Default is "pv-recycler-".
- // 3. pod.Spec.ActiveDeadlineSeconds gives the recycler pod a maximum timeout
- // before failing. Recommended. Default is 60 seconds.
- //
- // See HostPath and NFS for working recycler examples
- func NewPersistentVolumeRecyclerPodTemplate() *api.Pod {
- timeout := int64(60)
- pod := &api.Pod{
- ObjectMeta: api.ObjectMeta{
- GenerateName: "pv-recycler-",
- Namespace: api.NamespaceDefault,
- },
- Spec: api.PodSpec{
- ActiveDeadlineSeconds: &timeout,
- RestartPolicy: api.RestartPolicyNever,
- Volumes: []api.Volume{
- {
- Name: "vol",
- // IMPORTANT! All plugins using this template MUST
- // override pod.Spec.Volumes[0].VolumeSource Recycler
- // implementations without a valid VolumeSource will fail.
- VolumeSource: api.VolumeSource{},
- },
- },
- Containers: []api.Container{
- {
- Name: "pv-recycler",
- Image: "gcr.io/google_containers/busybox",
- Command: []string{"/bin/sh"},
- Args: []string{"-c", "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/* && test -z \"$(ls -A /scrub)\" || exit 1"},
- VolumeMounts: []api.VolumeMount{
- {
- Name: "vol",
- MountPath: "/scrub",
- },
- },
- },
- },
- },
- }
- return pod
- }
|