plugins.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package volume
  14. import (
  15. "fmt"
  16. "net"
  17. "strings"
  18. "sync"
  19. "github.com/golang/glog"
  20. "k8s.io/kubernetes/pkg/api"
  21. "k8s.io/kubernetes/pkg/api/resource"
  22. "k8s.io/kubernetes/pkg/api/unversioned"
  23. clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
  24. "k8s.io/kubernetes/pkg/cloudprovider"
  25. "k8s.io/kubernetes/pkg/types"
  26. utilerrors "k8s.io/kubernetes/pkg/util/errors"
  27. "k8s.io/kubernetes/pkg/util/io"
  28. "k8s.io/kubernetes/pkg/util/mount"
  29. "k8s.io/kubernetes/pkg/util/validation"
  30. )
  31. // VolumeOptions contains option information about a volume.
  32. type VolumeOptions struct {
  33. // The attributes below are required by volume.Provisioner
  34. // TODO: refactor all of this out of volumes when an admin can configure
  35. // many kinds of provisioners.
  36. // Capacity is the size of a volume.
  37. Capacity resource.Quantity
  38. // AccessModes of a volume
  39. AccessModes []api.PersistentVolumeAccessMode
  40. // Reclamation policy for a persistent volume
  41. PersistentVolumeReclaimPolicy api.PersistentVolumeReclaimPolicy
  42. // PV.Name of the appropriate PersistentVolume. Used to generate cloud
  43. // volume name.
  44. PVName string
  45. // PVC.Name of the PersistentVolumeClaim; only set during dynamic provisioning.
  46. PVCName string
  47. // Unique name of Kubernetes cluster.
  48. ClusterName string
  49. // Tags to attach to the real volume in the cloud provider - e.g. AWS EBS
  50. CloudTags *map[string]string
  51. // Volume provisioning parameters from StorageClass
  52. Parameters map[string]string
  53. // Volume selector from PersistentVolumeClaim
  54. Selector *unversioned.LabelSelector
  55. }
  56. // VolumePlugin is an interface to volume plugins that can be used on a
  57. // kubernetes node (e.g. by kubelet) to instantiate and manage volumes.
  58. type VolumePlugin interface {
  59. // Init initializes the plugin. This will be called exactly once
  60. // before any New* calls are made - implementations of plugins may
  61. // depend on this.
  62. Init(host VolumeHost) error
  63. // Name returns the plugin's name. Plugins should use namespaced names
  64. // such as "example.com/volume". The "kubernetes.io" namespace is
  65. // reserved for plugins which are bundled with kubernetes.
  66. GetPluginName() string
  67. // GetVolumeName returns the name/ID to uniquely identifying the actual
  68. // backing device, directory, path, etc. referenced by the specified volume
  69. // spec.
  70. // For Attachable volumes, this value must be able to be passed back to
  71. // volume Detach methods to identify the device to act on.
  72. // If the plugin does not support the given spec, this returns an error.
  73. GetVolumeName(spec *Spec) (string, error)
  74. // CanSupport tests whether the plugin supports a given volume
  75. // specification from the API. The spec pointer should be considered
  76. // const.
  77. CanSupport(spec *Spec) bool
  78. // RequiresRemount returns true if this plugin requires mount calls to be
  79. // reexecuted. Atomically updating volumes, like Downward API, depend on
  80. // this to update the contents of the volume.
  81. RequiresRemount() bool
  82. // NewMounter creates a new volume.Mounter from an API specification.
  83. // Ownership of the spec pointer in *not* transferred.
  84. // - spec: The api.Volume spec
  85. // - pod: The enclosing pod
  86. NewMounter(spec *Spec, podRef *api.Pod, opts VolumeOptions) (Mounter, error)
  87. // NewUnmounter creates a new volume.Unmounter from recoverable state.
  88. // - name: The volume name, as per the api.Volume spec.
  89. // - podUID: The UID of the enclosing pod
  90. NewUnmounter(name string, podUID types.UID) (Unmounter, error)
  91. // ConstructVolumeSpec constructs a volume spec based on the given volume name
  92. // and mountPath. The spec may have incomplete information due to limited
  93. // information from input. This function is used by volume manager to reconstruct
  94. // volume spec by reading the volume directories from disk
  95. ConstructVolumeSpec(volumeName, mountPath string) (*Spec, error)
  96. }
  97. // PersistentVolumePlugin is an extended interface of VolumePlugin and is used
  98. // by volumes that want to provide long term persistence of data
  99. type PersistentVolumePlugin interface {
  100. VolumePlugin
  101. // GetAccessModes describes the ways a given volume can be accessed/mounted.
  102. GetAccessModes() []api.PersistentVolumeAccessMode
  103. }
  104. // RecyclableVolumePlugin is an extended interface of VolumePlugin and is used
  105. // by persistent volumes that want to be recycled before being made available
  106. // again to new claims
  107. type RecyclableVolumePlugin interface {
  108. VolumePlugin
  109. // NewRecycler creates a new volume.Recycler which knows how to reclaim
  110. // this resource after the volume's release from a PersistentVolumeClaim
  111. NewRecycler(pvName string, spec *Spec) (Recycler, error)
  112. }
  113. // DeletableVolumePlugin is an extended interface of VolumePlugin and is used
  114. // by persistent volumes that want to be deleted from the cluster after their
  115. // release from a PersistentVolumeClaim.
  116. type DeletableVolumePlugin interface {
  117. VolumePlugin
  118. // NewDeleter creates a new volume.Deleter which knows how to delete this
  119. // resource in accordance with the underlying storage provider after the
  120. // volume's release from a claim
  121. NewDeleter(spec *Spec) (Deleter, error)
  122. }
  123. const (
  124. // Name of a volume in external cloud that is being provisioned and thus
  125. // should be ignored by rest of Kubernetes.
  126. ProvisionedVolumeName = "placeholder-for-provisioning"
  127. )
  128. // ProvisionableVolumePlugin is an extended interface of VolumePlugin and is
  129. // used to create volumes for the cluster.
  130. type ProvisionableVolumePlugin interface {
  131. VolumePlugin
  132. // NewProvisioner creates a new volume.Provisioner which knows how to
  133. // create PersistentVolumes in accordance with the plugin's underlying
  134. // storage provider
  135. NewProvisioner(options VolumeOptions) (Provisioner, error)
  136. }
  137. // AttachableVolumePlugin is an extended interface of VolumePlugin and is used for volumes that require attachment
  138. // to a node before mounting.
  139. type AttachableVolumePlugin interface {
  140. VolumePlugin
  141. NewAttacher() (Attacher, error)
  142. NewDetacher() (Detacher, error)
  143. GetDeviceMountRefs(deviceMountPath string) ([]string, error)
  144. }
  145. // VolumeHost is an interface that plugins can use to access the kubelet.
  146. type VolumeHost interface {
  147. // GetPluginDir returns the absolute path to a directory under which
  148. // a given plugin may store data. This directory might not actually
  149. // exist on disk yet. For plugin data that is per-pod, see
  150. // GetPodPluginDir().
  151. GetPluginDir(pluginName string) string
  152. // GetPodVolumeDir returns the absolute path a directory which
  153. // represents the named volume under the named plugin for the given
  154. // pod. If the specified pod does not exist, the result of this call
  155. // might not exist.
  156. GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string
  157. // GetPodPluginDir returns the absolute path to a directory under which
  158. // a given plugin may store data for a given pod. If the specified pod
  159. // does not exist, the result of this call might not exist. This
  160. // directory might not actually exist on disk yet.
  161. GetPodPluginDir(podUID types.UID, pluginName string) string
  162. // GetKubeClient returns a client interface
  163. GetKubeClient() clientset.Interface
  164. // NewWrapperMounter finds an appropriate plugin with which to handle
  165. // the provided spec. This is used to implement volume plugins which
  166. // "wrap" other plugins. For example, the "secret" volume is
  167. // implemented in terms of the "emptyDir" volume.
  168. NewWrapperMounter(volName string, spec Spec, pod *api.Pod, opts VolumeOptions) (Mounter, error)
  169. // NewWrapperUnmounter finds an appropriate plugin with which to handle
  170. // the provided spec. See comments on NewWrapperMounter for more
  171. // context.
  172. NewWrapperUnmounter(volName string, spec Spec, podUID types.UID) (Unmounter, error)
  173. // Get cloud provider from kubelet.
  174. GetCloudProvider() cloudprovider.Interface
  175. // Get mounter interface.
  176. GetMounter() mount.Interface
  177. // Get writer interface for writing data to disk.
  178. GetWriter() io.Writer
  179. // Returns the hostname of the host kubelet is running on
  180. GetHostName() string
  181. // Returns host IP or nil in the case of error.
  182. GetHostIP() (net.IP, error)
  183. // Returns the rootcontext to use when performing mounts for a volume.
  184. // This is a temporary measure in order to set the rootContext of tmpfs
  185. // mounts correctly. It will be replaced and expanded on by future
  186. // SecurityContext work.
  187. GetRootContext() string
  188. // Returns node allocatable
  189. GetNodeAllocatable() (api.ResourceList, error)
  190. }
  191. // VolumePluginMgr tracks registered plugins.
  192. type VolumePluginMgr struct {
  193. mutex sync.Mutex
  194. plugins map[string]VolumePlugin
  195. }
  196. // Spec is an internal representation of a volume. All API volume types translate to Spec.
  197. type Spec struct {
  198. Volume *api.Volume
  199. PersistentVolume *api.PersistentVolume
  200. ReadOnly bool
  201. }
  202. // Name returns the name of either Volume or PersistentVolume, one of which must not be nil.
  203. func (spec *Spec) Name() string {
  204. switch {
  205. case spec.Volume != nil:
  206. return spec.Volume.Name
  207. case spec.PersistentVolume != nil:
  208. return spec.PersistentVolume.Name
  209. default:
  210. return ""
  211. }
  212. }
  213. // VolumeConfig is how volume plugins receive configuration. An instance
  214. // specific to the plugin will be passed to the plugin's
  215. // ProbeVolumePlugins(config) func. Reasonable defaults will be provided by
  216. // the binary hosting the plugins while allowing override of those default
  217. // values. Those config values are then set to an instance of VolumeConfig
  218. // and passed to the plugin.
  219. //
  220. // Values in VolumeConfig are intended to be relevant to several plugins, but
  221. // not necessarily all plugins. The preference is to leverage strong typing
  222. // in this struct. All config items must have a descriptive but non-specific
  223. // name (i.e, RecyclerMinimumTimeout is OK but RecyclerMinimumTimeoutForNFS is
  224. // !OK). An instance of config will be given directly to the plugin, so
  225. // config names specific to plugins are unneeded and wrongly expose plugins in
  226. // this VolumeConfig struct.
  227. //
  228. // OtherAttributes is a map of string values intended for one-off
  229. // configuration of a plugin or config that is only relevant to a single
  230. // plugin. All values are passed by string and require interpretation by the
  231. // plugin. Passing config as strings is the least desirable option but can be
  232. // used for truly one-off configuration. The binary should still use strong
  233. // typing for this value when binding CLI values before they are passed as
  234. // strings in OtherAttributes.
  235. type VolumeConfig struct {
  236. // RecyclerPodTemplate is pod template that understands how to scrub clean
  237. // a persistent volume after its release. The template is used by plugins
  238. // which override specific properties of the pod in accordance with that
  239. // plugin. See NewPersistentVolumeRecyclerPodTemplate for the properties
  240. // that are expected to be overridden.
  241. RecyclerPodTemplate *api.Pod
  242. // RecyclerMinimumTimeout is the minimum amount of time in seconds for the
  243. // recycler pod's ActiveDeadlineSeconds attribute. Added to the minimum
  244. // timeout is the increment per Gi of capacity.
  245. RecyclerMinimumTimeout int
  246. // RecyclerTimeoutIncrement is the number of seconds added to the recycler
  247. // pod's ActiveDeadlineSeconds for each Gi of capacity in the persistent
  248. // volume. Example: 5Gi volume x 30s increment = 150s + 30s minimum = 180s
  249. // ActiveDeadlineSeconds for recycler pod
  250. RecyclerTimeoutIncrement int
  251. // PVName is name of the PersistentVolume instance that is being recycled.
  252. // It is used to generate unique recycler pod name.
  253. PVName string
  254. // OtherAttributes stores config as strings. These strings are opaque to
  255. // the system and only understood by the binary hosting the plugin and the
  256. // plugin itself.
  257. OtherAttributes map[string]string
  258. // ProvisioningEnabled configures whether provisioning of this plugin is
  259. // enabled or not. Currently used only in host_path plugin.
  260. ProvisioningEnabled bool
  261. }
  262. // NewSpecFromVolume creates an Spec from an api.Volume
  263. func NewSpecFromVolume(vs *api.Volume) *Spec {
  264. return &Spec{
  265. Volume: vs,
  266. }
  267. }
  268. // NewSpecFromPersistentVolume creates an Spec from an api.PersistentVolume
  269. func NewSpecFromPersistentVolume(pv *api.PersistentVolume, readOnly bool) *Spec {
  270. return &Spec{
  271. PersistentVolume: pv,
  272. ReadOnly: readOnly,
  273. }
  274. }
  275. // InitPlugins initializes each plugin. All plugins must have unique names.
  276. // This must be called exactly once before any New* methods are called on any
  277. // plugins.
  278. func (pm *VolumePluginMgr) InitPlugins(plugins []VolumePlugin, host VolumeHost) error {
  279. pm.mutex.Lock()
  280. defer pm.mutex.Unlock()
  281. if pm.plugins == nil {
  282. pm.plugins = map[string]VolumePlugin{}
  283. }
  284. allErrs := []error{}
  285. for _, plugin := range plugins {
  286. name := plugin.GetPluginName()
  287. if errs := validation.IsQualifiedName(name); len(errs) != 0 {
  288. allErrs = append(allErrs, fmt.Errorf("volume plugin has invalid name: %q: %s", name, strings.Join(errs, ";")))
  289. continue
  290. }
  291. if _, found := pm.plugins[name]; found {
  292. allErrs = append(allErrs, fmt.Errorf("volume plugin %q was registered more than once", name))
  293. continue
  294. }
  295. err := plugin.Init(host)
  296. if err != nil {
  297. glog.Errorf("Failed to load volume plugin %s, error: %s", plugin, err.Error())
  298. allErrs = append(allErrs, err)
  299. continue
  300. }
  301. pm.plugins[name] = plugin
  302. glog.V(1).Infof("Loaded volume plugin %q", name)
  303. }
  304. return utilerrors.NewAggregate(allErrs)
  305. }
  306. // FindPluginBySpec looks for a plugin that can support a given volume
  307. // specification. If no plugins can support or more than one plugin can
  308. // support it, return error.
  309. func (pm *VolumePluginMgr) FindPluginBySpec(spec *Spec) (VolumePlugin, error) {
  310. pm.mutex.Lock()
  311. defer pm.mutex.Unlock()
  312. matches := []string{}
  313. for k, v := range pm.plugins {
  314. if v.CanSupport(spec) {
  315. matches = append(matches, k)
  316. }
  317. }
  318. if len(matches) == 0 {
  319. return nil, fmt.Errorf("no volume plugin matched")
  320. }
  321. if len(matches) > 1 {
  322. return nil, fmt.Errorf("multiple volume plugins matched: %s", strings.Join(matches, ","))
  323. }
  324. return pm.plugins[matches[0]], nil
  325. }
  326. // FindPluginByName fetches a plugin by name or by legacy name. If no plugin
  327. // is found, returns error.
  328. func (pm *VolumePluginMgr) FindPluginByName(name string) (VolumePlugin, error) {
  329. pm.mutex.Lock()
  330. defer pm.mutex.Unlock()
  331. // Once we can get rid of legacy names we can reduce this to a map lookup.
  332. matches := []string{}
  333. for k, v := range pm.plugins {
  334. if v.GetPluginName() == name {
  335. matches = append(matches, k)
  336. }
  337. }
  338. if len(matches) == 0 {
  339. return nil, fmt.Errorf("no volume plugin matched")
  340. }
  341. if len(matches) > 1 {
  342. return nil, fmt.Errorf("multiple volume plugins matched: %s", strings.Join(matches, ","))
  343. }
  344. return pm.plugins[matches[0]], nil
  345. }
  346. // FindPersistentPluginBySpec looks for a persistent volume plugin that can
  347. // support a given volume specification. If no plugin is found, return an
  348. // error
  349. func (pm *VolumePluginMgr) FindPersistentPluginBySpec(spec *Spec) (PersistentVolumePlugin, error) {
  350. volumePlugin, err := pm.FindPluginBySpec(spec)
  351. if err != nil {
  352. return nil, fmt.Errorf("Could not find volume plugin for spec: %#v", spec)
  353. }
  354. if persistentVolumePlugin, ok := volumePlugin.(PersistentVolumePlugin); ok {
  355. return persistentVolumePlugin, nil
  356. }
  357. return nil, fmt.Errorf("no persistent volume plugin matched")
  358. }
  359. // FindPersistentPluginByName fetches a persistent volume plugin by name. If
  360. // no plugin is found, returns error.
  361. func (pm *VolumePluginMgr) FindPersistentPluginByName(name string) (PersistentVolumePlugin, error) {
  362. volumePlugin, err := pm.FindPluginByName(name)
  363. if err != nil {
  364. return nil, err
  365. }
  366. if persistentVolumePlugin, ok := volumePlugin.(PersistentVolumePlugin); ok {
  367. return persistentVolumePlugin, nil
  368. }
  369. return nil, fmt.Errorf("no persistent volume plugin matched")
  370. }
  371. // FindRecyclablePluginByName fetches a persistent volume plugin by name. If
  372. // no plugin is found, returns error.
  373. func (pm *VolumePluginMgr) FindRecyclablePluginBySpec(spec *Spec) (RecyclableVolumePlugin, error) {
  374. volumePlugin, err := pm.FindPluginBySpec(spec)
  375. if err != nil {
  376. return nil, err
  377. }
  378. if recyclableVolumePlugin, ok := volumePlugin.(RecyclableVolumePlugin); ok {
  379. return recyclableVolumePlugin, nil
  380. }
  381. return nil, fmt.Errorf("no recyclable volume plugin matched")
  382. }
  383. // FindProvisionablePluginByName fetches a persistent volume plugin by name. If
  384. // no plugin is found, returns error.
  385. func (pm *VolumePluginMgr) FindProvisionablePluginByName(name string) (ProvisionableVolumePlugin, error) {
  386. volumePlugin, err := pm.FindPluginByName(name)
  387. if err != nil {
  388. return nil, err
  389. }
  390. if provisionableVolumePlugin, ok := volumePlugin.(ProvisionableVolumePlugin); ok {
  391. return provisionableVolumePlugin, nil
  392. }
  393. return nil, fmt.Errorf("no provisionable volume plugin matched")
  394. }
  395. // FindDeletablePluginBySppec fetches a persistent volume plugin by spec. If
  396. // no plugin is found, returns error.
  397. func (pm *VolumePluginMgr) FindDeletablePluginBySpec(spec *Spec) (DeletableVolumePlugin, error) {
  398. volumePlugin, err := pm.FindPluginBySpec(spec)
  399. if err != nil {
  400. return nil, err
  401. }
  402. if deletableVolumePlugin, ok := volumePlugin.(DeletableVolumePlugin); ok {
  403. return deletableVolumePlugin, nil
  404. }
  405. return nil, fmt.Errorf("no deletable volume plugin matched")
  406. }
  407. // FindDeletablePluginByName fetches a persistent volume plugin by name. If
  408. // no plugin is found, returns error.
  409. func (pm *VolumePluginMgr) FindDeletablePluginByName(name string) (DeletableVolumePlugin, error) {
  410. volumePlugin, err := pm.FindPluginByName(name)
  411. if err != nil {
  412. return nil, err
  413. }
  414. if deletableVolumePlugin, ok := volumePlugin.(DeletableVolumePlugin); ok {
  415. return deletableVolumePlugin, nil
  416. }
  417. return nil, fmt.Errorf("no deletable volume plugin matched")
  418. }
  419. // FindCreatablePluginBySpec fetches a persistent volume plugin by name. If
  420. // no plugin is found, returns error.
  421. func (pm *VolumePluginMgr) FindCreatablePluginBySpec(spec *Spec) (ProvisionableVolumePlugin, error) {
  422. volumePlugin, err := pm.FindPluginBySpec(spec)
  423. if err != nil {
  424. return nil, err
  425. }
  426. if provisionableVolumePlugin, ok := volumePlugin.(ProvisionableVolumePlugin); ok {
  427. return provisionableVolumePlugin, nil
  428. }
  429. return nil, fmt.Errorf("no creatable volume plugin matched")
  430. }
  431. // FindAttachablePluginBySpec fetches a persistent volume plugin by name.
  432. // Unlike the other "FindPlugin" methods, this does not return error if no
  433. // plugin is found. All volumes require a mounter and unmounter, but not
  434. // every volume will have an attacher/detacher.
  435. func (pm *VolumePluginMgr) FindAttachablePluginBySpec(spec *Spec) (AttachableVolumePlugin, error) {
  436. volumePlugin, err := pm.FindPluginBySpec(spec)
  437. if err != nil {
  438. return nil, err
  439. }
  440. if attachableVolumePlugin, ok := volumePlugin.(AttachableVolumePlugin); ok {
  441. return attachableVolumePlugin, nil
  442. }
  443. return nil, nil
  444. }
  445. // FindAttachablePluginByName fetches an attachable volume plugin by name.
  446. // Unlike the other "FindPlugin" methods, this does not return error if no
  447. // plugin is found. All volumes require a mounter and unmounter, but not
  448. // every volume will have an attacher/detacher.
  449. func (pm *VolumePluginMgr) FindAttachablePluginByName(name string) (AttachableVolumePlugin, error) {
  450. volumePlugin, err := pm.FindPluginByName(name)
  451. if err != nil {
  452. return nil, err
  453. }
  454. if attachablePlugin, ok := volumePlugin.(AttachableVolumePlugin); ok {
  455. return attachablePlugin, nil
  456. }
  457. return nil, nil
  458. }
  459. // NewPersistentVolumeRecyclerPodTemplate creates a template for a recycler
  460. // pod. By default, a recycler pod simply runs "rm -rf" on a volume and tests
  461. // for emptiness. Most attributes of the template will be correct for most
  462. // plugin implementations. The following attributes can be overridden per
  463. // plugin via configuration:
  464. //
  465. // 1. pod.Spec.Volumes[0].VolumeSource must be overridden. Recycler
  466. // implementations without a valid VolumeSource will fail.
  467. // 2. pod.GenerateName helps distinguish recycler pods by name. Recommended.
  468. // Default is "pv-recycler-".
  469. // 3. pod.Spec.ActiveDeadlineSeconds gives the recycler pod a maximum timeout
  470. // before failing. Recommended. Default is 60 seconds.
  471. //
  472. // See HostPath and NFS for working recycler examples
  473. func NewPersistentVolumeRecyclerPodTemplate() *api.Pod {
  474. timeout := int64(60)
  475. pod := &api.Pod{
  476. ObjectMeta: api.ObjectMeta{
  477. GenerateName: "pv-recycler-",
  478. Namespace: api.NamespaceDefault,
  479. },
  480. Spec: api.PodSpec{
  481. ActiveDeadlineSeconds: &timeout,
  482. RestartPolicy: api.RestartPolicyNever,
  483. Volumes: []api.Volume{
  484. {
  485. Name: "vol",
  486. // IMPORTANT! All plugins using this template MUST
  487. // override pod.Spec.Volumes[0].VolumeSource Recycler
  488. // implementations without a valid VolumeSource will fail.
  489. VolumeSource: api.VolumeSource{},
  490. },
  491. },
  492. Containers: []api.Container{
  493. {
  494. Name: "pv-recycler",
  495. Image: "gcr.io/google_containers/busybox",
  496. Command: []string{"/bin/sh"},
  497. Args: []string{"-c", "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/* && test -z \"$(ls -A /scrub)\" || exit 1"},
  498. VolumeMounts: []api.VolumeMount{
  499. {
  500. Name: "vol",
  501. MountPath: "/scrub",
  502. },
  503. },
  504. },
  505. },
  506. },
  507. }
  508. return pod
  509. }