2015-05-29 20:34:32 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
|
|
|
|
|
|
|
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 volumeclaimbinder
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2015-04-10 16:54:01 +00:00
|
|
|
"github.com/golang/glog"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2015-09-03 21:40:58 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/cache"
|
2015-09-03 21:43:19 +00:00
|
|
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
2015-04-10 16:54:01 +00:00
|
|
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/controller/framework"
|
|
|
|
"k8s.io/kubernetes/pkg/fields"
|
|
|
|
"k8s.io/kubernetes/pkg/labels"
|
|
|
|
"k8s.io/kubernetes/pkg/runtime"
|
2015-08-28 17:56:24 +00:00
|
|
|
"k8s.io/kubernetes/pkg/types"
|
2015-09-14 09:51:40 +00:00
|
|
|
ioutil "k8s.io/kubernetes/pkg/util/io"
|
2015-08-28 17:56:24 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/mount"
|
2015-04-10 16:54:01 +00:00
|
|
|
"k8s.io/kubernetes/pkg/volume"
|
|
|
|
"k8s.io/kubernetes/pkg/watch"
|
2015-05-29 20:34:32 +00:00
|
|
|
)
|
2015-09-03 03:14:26 +00:00
|
|
|
|
|
|
|
var _ volume.VolumeHost = &PersistentVolumeRecycler{}
|
2015-05-29 20:34:32 +00:00
|
|
|
|
|
|
|
// PersistentVolumeRecycler is a controller that watches for PersistentVolumes that are released from their claims.
|
|
|
|
// This controller will Recycle those volumes whose reclaim policy is set to PersistentVolumeReclaimRecycle and make them
|
|
|
|
// available again for a new claim.
|
|
|
|
type PersistentVolumeRecycler struct {
|
|
|
|
volumeController *framework.Controller
|
|
|
|
stopChannel chan struct{}
|
|
|
|
client recyclerClient
|
|
|
|
kubeClient client.Interface
|
|
|
|
pluginMgr volume.VolumePluginMgr
|
|
|
|
}
|
|
|
|
|
|
|
|
// PersistentVolumeRecycler creates a new PersistentVolumeRecycler
|
|
|
|
func NewPersistentVolumeRecycler(kubeClient client.Interface, syncPeriod time.Duration, plugins []volume.VolumePlugin) (*PersistentVolumeRecycler, error) {
|
|
|
|
recyclerClient := NewRecyclerClient(kubeClient)
|
|
|
|
recycler := &PersistentVolumeRecycler{
|
|
|
|
client: recyclerClient,
|
|
|
|
kubeClient: kubeClient,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := recycler.pluginMgr.InitPlugins(plugins, recycler); err != nil {
|
|
|
|
return nil, fmt.Errorf("Could not initialize volume plugins for PVClaimBinder: %+v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, volumeController := framework.NewInformer(
|
|
|
|
&cache.ListWatch{
|
|
|
|
ListFunc: func() (runtime.Object, error) {
|
|
|
|
return kubeClient.PersistentVolumes().List(labels.Everything(), fields.Everything())
|
|
|
|
},
|
|
|
|
WatchFunc: func(resourceVersion string) (watch.Interface, error) {
|
|
|
|
return kubeClient.PersistentVolumes().Watch(labels.Everything(), fields.Everything(), resourceVersion)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
&api.PersistentVolume{},
|
2015-10-06 09:12:00 +00:00
|
|
|
// TODO: Can we have much longer period here?
|
2015-05-29 20:34:32 +00:00
|
|
|
syncPeriod,
|
|
|
|
framework.ResourceEventHandlerFuncs{
|
|
|
|
AddFunc: func(obj interface{}) {
|
|
|
|
pv := obj.(*api.PersistentVolume)
|
|
|
|
recycler.reclaimVolume(pv)
|
|
|
|
},
|
|
|
|
UpdateFunc: func(oldObj, newObj interface{}) {
|
|
|
|
pv := newObj.(*api.PersistentVolume)
|
|
|
|
recycler.reclaimVolume(pv)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
recycler.volumeController = volumeController
|
|
|
|
return recycler, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (recycler *PersistentVolumeRecycler) reclaimVolume(pv *api.PersistentVolume) error {
|
|
|
|
if pv.Status.Phase == api.VolumeReleased && pv.Spec.ClaimRef != nil {
|
|
|
|
glog.V(5).Infof("Reclaiming PersistentVolume[%s]\n", pv.Name)
|
|
|
|
|
|
|
|
latest, err := recycler.client.GetPersistentVolume(pv.Name)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Could not find PersistentVolume %s", pv.Name)
|
|
|
|
}
|
|
|
|
if latest.Status.Phase != api.VolumeReleased {
|
|
|
|
return fmt.Errorf("PersistentVolume[%s] phase is %s, expected %s. Skipping.", pv.Name, latest.Status.Phase, api.VolumeReleased)
|
|
|
|
}
|
|
|
|
|
2015-09-07 16:11:37 +00:00
|
|
|
// both handleRecycle and handleDelete block until completion
|
2015-05-29 20:34:32 +00:00
|
|
|
// TODO: allow parallel recycling operations to increase throughput
|
|
|
|
switch pv.Spec.PersistentVolumeReclaimPolicy {
|
|
|
|
case api.PersistentVolumeReclaimRecycle:
|
|
|
|
err = recycler.handleRecycle(pv)
|
2015-09-07 16:11:37 +00:00
|
|
|
case api.PersistentVolumeReclaimDelete:
|
|
|
|
err = recycler.handleDelete(pv)
|
2015-05-29 20:34:32 +00:00
|
|
|
case api.PersistentVolumeReclaimRetain:
|
|
|
|
glog.V(5).Infof("Volume %s is set to retain after release. Skipping.\n", pv.Name)
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("No PersistentVolumeReclaimPolicy defined for spec: %+v", pv)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
errMsg := fmt.Sprintf("Could not recycle volume spec: %+v", err)
|
|
|
|
glog.Errorf(errMsg)
|
|
|
|
return fmt.Errorf(errMsg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (recycler *PersistentVolumeRecycler) handleRecycle(pv *api.PersistentVolume) error {
|
|
|
|
glog.V(5).Infof("Recycling PersistentVolume[%s]\n", pv.Name)
|
|
|
|
|
|
|
|
currentPhase := pv.Status.Phase
|
|
|
|
nextPhase := currentPhase
|
|
|
|
|
2015-06-29 16:54:43 +00:00
|
|
|
spec := volume.NewSpecFromPersistentVolume(pv, false)
|
2015-05-29 20:34:32 +00:00
|
|
|
plugin, err := recycler.pluginMgr.FindRecyclablePluginBySpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Could not find recyclable volume plugin for spec: %+v", err)
|
|
|
|
}
|
|
|
|
volRecycler, err := plugin.NewRecycler(spec)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Could not obtain Recycler for spec: %+v", err)
|
|
|
|
}
|
|
|
|
// blocks until completion
|
|
|
|
err = volRecycler.Recycle()
|
|
|
|
if err != nil {
|
2015-08-08 01:52:23 +00:00
|
|
|
glog.Errorf("PersistentVolume[%s] failed recycling: %+v", pv.Name, err)
|
2015-05-29 20:34:32 +00:00
|
|
|
pv.Status.Message = fmt.Sprintf("Recycling error: %s", err)
|
|
|
|
nextPhase = api.VolumeFailed
|
|
|
|
} else {
|
|
|
|
glog.V(5).Infof("PersistentVolume[%s] successfully recycled\n", pv.Name)
|
|
|
|
nextPhase = api.VolumePending
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Error updating pv.Status: %+v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if currentPhase != nextPhase {
|
|
|
|
glog.V(5).Infof("PersistentVolume[%s] changing phase from %s to %s\n", pv.Name, currentPhase, nextPhase)
|
|
|
|
pv.Status.Phase = nextPhase
|
|
|
|
_, err := recycler.client.UpdatePersistentVolumeStatus(pv)
|
|
|
|
if err != nil {
|
|
|
|
// Rollback to previous phase
|
|
|
|
pv.Status.Phase = currentPhase
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-07 16:11:37 +00:00
|
|
|
func (recycler *PersistentVolumeRecycler) handleDelete(pv *api.PersistentVolume) error {
|
|
|
|
glog.V(5).Infof("Deleting PersistentVolume[%s]\n", pv.Name)
|
|
|
|
|
|
|
|
currentPhase := pv.Status.Phase
|
|
|
|
nextPhase := currentPhase
|
|
|
|
|
|
|
|
spec := volume.NewSpecFromPersistentVolume(pv, false)
|
|
|
|
plugin, err := recycler.pluginMgr.FindDeletablePluginBySpec(spec)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Could not find deletable volume plugin for spec: %+v", err)
|
|
|
|
}
|
|
|
|
deleter, err := plugin.NewDeleter(spec)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not obtain Deleter for spec: %+v", err)
|
|
|
|
}
|
|
|
|
// blocks until completion
|
|
|
|
err = deleter.Delete()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("PersistentVolume[%s] failed deletion: %+v", pv.Name, err)
|
|
|
|
pv.Status.Message = fmt.Sprintf("Deletion error: %s", err)
|
|
|
|
nextPhase = api.VolumeFailed
|
|
|
|
} else {
|
|
|
|
glog.V(5).Infof("PersistentVolume[%s] successfully deleted through plugin\n", pv.Name)
|
|
|
|
// after successful deletion through the plugin, we can also remove the PV from the cluster
|
|
|
|
err = recycler.client.DeletePersistentVolume(pv)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error deleting persistent volume: %+v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if currentPhase != nextPhase {
|
|
|
|
glog.V(5).Infof("PersistentVolume[%s] changing phase from %s to %s\n", pv.Name, currentPhase, nextPhase)
|
|
|
|
pv.Status.Phase = nextPhase
|
|
|
|
_, err := recycler.client.UpdatePersistentVolumeStatus(pv)
|
|
|
|
if err != nil {
|
|
|
|
// Rollback to previous phase
|
|
|
|
pv.Status.Phase = currentPhase
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-05-29 20:34:32 +00:00
|
|
|
// Run starts this recycler's control loops
|
|
|
|
func (recycler *PersistentVolumeRecycler) Run() {
|
|
|
|
glog.V(5).Infof("Starting PersistentVolumeRecycler\n")
|
|
|
|
if recycler.stopChannel == nil {
|
|
|
|
recycler.stopChannel = make(chan struct{})
|
|
|
|
go recycler.volumeController.Run(recycler.stopChannel)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop gracefully shuts down this binder
|
|
|
|
func (recycler *PersistentVolumeRecycler) Stop() {
|
|
|
|
glog.V(5).Infof("Stopping PersistentVolumeRecycler\n")
|
|
|
|
if recycler.stopChannel != nil {
|
|
|
|
close(recycler.stopChannel)
|
|
|
|
recycler.stopChannel = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// recyclerClient abstracts access to PVs
|
|
|
|
type recyclerClient interface {
|
|
|
|
GetPersistentVolume(name string) (*api.PersistentVolume, error)
|
|
|
|
UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error)
|
2015-09-07 16:11:37 +00:00
|
|
|
DeletePersistentVolume(volume *api.PersistentVolume) error
|
2015-05-29 20:34:32 +00:00
|
|
|
UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewRecyclerClient(c client.Interface) recyclerClient {
|
|
|
|
return &realRecyclerClient{c}
|
|
|
|
}
|
|
|
|
|
|
|
|
type realRecyclerClient struct {
|
|
|
|
client client.Interface
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *realRecyclerClient) GetPersistentVolume(name string) (*api.PersistentVolume, error) {
|
|
|
|
return c.client.PersistentVolumes().Get(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *realRecyclerClient) UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) {
|
|
|
|
return c.client.PersistentVolumes().Update(volume)
|
|
|
|
}
|
|
|
|
|
2015-09-07 16:11:37 +00:00
|
|
|
func (c *realRecyclerClient) DeletePersistentVolume(volume *api.PersistentVolume) error {
|
|
|
|
return c.client.PersistentVolumes().Delete(volume.Name)
|
|
|
|
}
|
|
|
|
|
2015-05-29 20:34:32 +00:00
|
|
|
func (c *realRecyclerClient) UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) {
|
|
|
|
return c.client.PersistentVolumes().UpdateStatus(volume)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PersistentVolumeRecycler is host to the volume plugins, but does not actually mount any volumes.
|
|
|
|
// Because no mounting is performed, most of the VolumeHost methods are not implemented.
|
|
|
|
func (f *PersistentVolumeRecycler) GetPluginDir(podUID string) string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *PersistentVolumeRecycler) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *PersistentVolumeRecycler) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *PersistentVolumeRecycler) GetKubeClient() client.Interface {
|
|
|
|
return f.kubeClient
|
|
|
|
}
|
|
|
|
|
2015-09-14 09:51:40 +00:00
|
|
|
func (f *PersistentVolumeRecycler) NewWrapperBuilder(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Builder, error) {
|
2015-05-29 20:34:32 +00:00
|
|
|
return nil, fmt.Errorf("NewWrapperBuilder not supported by PVClaimBinder's VolumeHost implementation")
|
|
|
|
}
|
|
|
|
|
2015-09-14 09:51:40 +00:00
|
|
|
func (f *PersistentVolumeRecycler) NewWrapperCleaner(spec *volume.Spec, podUID types.UID) (volume.Cleaner, error) {
|
2015-05-29 20:34:32 +00:00
|
|
|
return nil, fmt.Errorf("NewWrapperCleaner not supported by PVClaimBinder's VolumeHost implementation")
|
|
|
|
}
|
2015-04-10 16:54:01 +00:00
|
|
|
|
|
|
|
func (f *PersistentVolumeRecycler) GetCloudProvider() cloudprovider.Interface {
|
|
|
|
return nil
|
|
|
|
}
|
2015-09-14 09:51:40 +00:00
|
|
|
|
|
|
|
func (f *PersistentVolumeRecycler) GetMounter() mount.Interface {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *PersistentVolumeRecycler) GetWriter() ioutil.Writer {
|
|
|
|
return nil
|
|
|
|
}
|