2018-01-09 18:57:14 +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 cloud
import (
2018-03-06 22:33:18 +00:00
"context"
2018-01-09 18:57:14 +00:00
"encoding/json"
"fmt"
"time"
2018-11-26 18:23:56 +00:00
"k8s.io/klog"
2018-01-09 18:57:14 +00:00
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
2018-11-26 18:23:56 +00:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
"k8s.io/kubernetes/pkg/features"
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
2018-01-09 18:57:14 +00:00
"k8s.io/client-go/kubernetes"
corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
2018-11-26 18:23:56 +00:00
cloudprovider "k8s.io/cloud-provider"
2018-01-09 18:57:14 +00:00
"k8s.io/kubernetes/pkg/controller"
)
const initializerName = "pvlabel.kubernetes.io"
// PersistentVolumeLabelController handles adding labels to persistent volumes when they are created
type PersistentVolumeLabelController struct {
cloud cloudprovider . Interface
kubeClient kubernetes . Interface
pvlController cache . Controller
pvlIndexer cache . Indexer
volumeLister corelisters . PersistentVolumeLister
syncHandler func ( key string ) error
// queue is where incoming work is placed to de-dup and to allow "easy" rate limited requeues on errors
queue workqueue . RateLimitingInterface
}
// NewPersistentVolumeLabelController creates a PersistentVolumeLabelController object
func NewPersistentVolumeLabelController (
kubeClient kubernetes . Interface ,
cloud cloudprovider . Interface ) * PersistentVolumeLabelController {
pvlc := & PersistentVolumeLabelController {
cloud : cloud ,
kubeClient : kubeClient ,
queue : workqueue . NewNamedRateLimitingQueue ( workqueue . DefaultControllerRateLimiter ( ) , "pvLabels" ) ,
}
2018-11-26 18:23:56 +00:00
pvlc . syncHandler = pvlc . addLabelsAndAffinity
2018-01-09 18:57:14 +00:00
pvlc . pvlIndexer , pvlc . pvlController = cache . NewIndexerInformer (
& cache . ListWatch {
ListFunc : func ( options metav1 . ListOptions ) ( runtime . Object , error ) {
options . IncludeUninitialized = true
return kubeClient . CoreV1 ( ) . PersistentVolumes ( ) . List ( options )
} ,
WatchFunc : func ( options metav1 . ListOptions ) ( watch . Interface , error ) {
options . IncludeUninitialized = true
return kubeClient . CoreV1 ( ) . PersistentVolumes ( ) . Watch ( options )
} ,
} ,
& v1 . PersistentVolume { } ,
0 ,
cache . ResourceEventHandlerFuncs {
AddFunc : func ( obj interface { } ) {
key , err := cache . MetaNamespaceKeyFunc ( obj )
if err == nil {
pvlc . queue . Add ( key )
}
} ,
} ,
cache . Indexers { } ,
)
pvlc . volumeLister = corelisters . NewPersistentVolumeLister ( pvlc . pvlIndexer )
return pvlc
}
// Run starts a controller that adds labels to persistent volumes
func ( pvlc * PersistentVolumeLabelController ) Run ( threadiness int , stopCh <- chan struct { } ) {
defer utilruntime . HandleCrash ( )
defer pvlc . queue . ShutDown ( )
2018-11-26 18:23:56 +00:00
klog . Infof ( "Starting PersistentVolumeLabelController" )
defer klog . Infof ( "Shutting down PersistentVolumeLabelController" )
2018-01-09 18:57:14 +00:00
go pvlc . pvlController . Run ( stopCh )
if ! controller . WaitForCacheSync ( "persistent volume label" , stopCh , pvlc . pvlController . HasSynced ) {
return
}
// start up your worker threads based on threadiness. Some controllers have multiple kinds of workers
for i := 0 ; i < threadiness ; i ++ {
// runWorker will loop until "something bad" happens. The .Until will then rekick the worker
// after one second
go wait . Until ( pvlc . runWorker , time . Second , stopCh )
}
// wait until we're told to stop
<- stopCh
}
func ( pvlc * PersistentVolumeLabelController ) runWorker ( ) {
// hot loop until we're told to stop. processNextWorkItem will automatically wait until there's work
// available, so we don't worry about secondary waits
for pvlc . processNextWorkItem ( ) {
}
}
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
func ( pvlc * PersistentVolumeLabelController ) processNextWorkItem ( ) bool {
// pull the next work item from queue. It should be a key we use to lookup something in a cache
keyObj , quit := pvlc . queue . Get ( )
if quit {
return false
}
// you always have to indicate to the queue that you've completed a piece of work
defer pvlc . queue . Done ( keyObj )
key := keyObj . ( string )
// do your work on the key. This method will contains your "do stuff" logic
err := pvlc . syncHandler ( key )
if err == nil {
// if you had no error, tell the queue to stop tracking history for your key. This will
// reset things like failure counts for per-item rate limiting
pvlc . queue . Forget ( key )
return true
}
// there was a failure so be sure to report it. This method allows for pluggable error handling
// which can be used for things like cluster-monitoring
utilruntime . HandleError ( fmt . Errorf ( "%v failed with : %v" , key , err ) )
// since we failed, we should requeue the item to work on later. This method will add a backoff
// to avoid hotlooping on particular items (they're probably still not going to work right away)
// and overall controller protection (everything I've done is broken, this controller needs to
// calm down or it can starve other useful work) cases.
pvlc . queue . AddRateLimited ( key )
return true
}
// AddLabels adds appropriate labels to persistent volumes and sets the
// volume as available if successful.
2018-11-26 18:23:56 +00:00
func ( pvlc * PersistentVolumeLabelController ) addLabelsAndAffinity ( key string ) error {
2018-01-09 18:57:14 +00:00
_ , name , err := cache . SplitMetaNamespaceKey ( key )
if err != nil {
return fmt . Errorf ( "error getting name of volume %q to get volume from informer: %v" , key , err )
}
volume , err := pvlc . volumeLister . Get ( name )
if errors . IsNotFound ( err ) {
return nil
} else if err != nil {
return fmt . Errorf ( "error getting volume %s from informer: %v" , name , err )
}
2018-11-26 18:23:56 +00:00
return pvlc . addLabelsAndAffinityToVolume ( volume )
2018-01-09 18:57:14 +00:00
}
2018-11-26 18:23:56 +00:00
func ( pvlc * PersistentVolumeLabelController ) addLabelsAndAffinityToVolume ( vol * v1 . PersistentVolume ) error {
2018-01-09 18:57:14 +00:00
var volumeLabels map [ string ] string
2018-03-06 22:33:18 +00:00
// Only add labels if the next pending initializer.
2018-01-09 18:57:14 +00:00
if needsInitialization ( vol . Initializers , initializerName ) {
if labeler , ok := ( pvlc . cloud ) . ( cloudprovider . PVLabeler ) ; ok {
2018-03-06 22:33:18 +00:00
labels , err := labeler . GetLabelsForVolume ( context . TODO ( ) , vol )
2018-01-09 18:57:14 +00:00
if err != nil {
return fmt . Errorf ( "error querying volume %v: %v" , vol . Spec , err )
}
volumeLabels = labels
} else {
2018-11-26 18:23:56 +00:00
klog . V ( 4 ) . Info ( "cloud provider does not support PVLabeler" )
2018-01-09 18:57:14 +00:00
}
return pvlc . updateVolume ( vol , volumeLabels )
}
return nil
}
func ( pvlc * PersistentVolumeLabelController ) createPatch ( vol * v1 . PersistentVolume , volLabels map [ string ] string ) ( [ ] byte , error ) {
volName := vol . Name
newVolume := vol . DeepCopyObject ( ) . ( * v1 . PersistentVolume )
2018-11-26 18:23:56 +00:00
populateAffinity := utilfeature . DefaultFeatureGate . Enabled ( features . VolumeScheduling ) && len ( volLabels ) != 0
2018-01-09 18:57:14 +00:00
if newVolume . Labels == nil {
newVolume . Labels = make ( map [ string ] string )
}
2018-11-26 18:23:56 +00:00
requirements := make ( [ ] v1 . NodeSelectorRequirement , 0 )
2018-01-09 18:57:14 +00:00
for k , v := range volLabels {
newVolume . Labels [ k ] = v
2018-11-26 18:23:56 +00:00
// Set NodeSelectorRequirements based on the labels
if populateAffinity {
var values [ ] string
if k == kubeletapis . LabelZoneFailureDomain {
zones , err := volumeutil . LabelZonesToSet ( v )
if err != nil {
return nil , fmt . Errorf ( "failed to convert label string for Zone: %s to a Set" , v )
}
values = zones . List ( )
} else {
values = [ ] string { v }
}
requirements = append ( requirements , v1 . NodeSelectorRequirement { Key : k , Operator : v1 . NodeSelectorOpIn , Values : values } )
}
}
if populateAffinity {
if newVolume . Spec . NodeAffinity == nil {
newVolume . Spec . NodeAffinity = new ( v1 . VolumeNodeAffinity )
}
if newVolume . Spec . NodeAffinity . Required == nil {
newVolume . Spec . NodeAffinity . Required = new ( v1 . NodeSelector )
}
if len ( newVolume . Spec . NodeAffinity . Required . NodeSelectorTerms ) == 0 {
// Need at least one term pre-allocated whose MatchExpressions can be appended to
newVolume . Spec . NodeAffinity . Required . NodeSelectorTerms = make ( [ ] v1 . NodeSelectorTerm , 1 )
}
// Populate NodeAffinity with requirements if there are no conflicting keys found
if v1helper . NodeSelectorRequirementKeysExistInNodeSelectorTerms ( requirements , newVolume . Spec . NodeAffinity . Required . NodeSelectorTerms ) {
klog . V ( 4 ) . Infof ( "NodeSelectorRequirements for cloud labels %v conflict with existing NodeAffinity %v. Skipping addition of NodeSelectorRequirements for cloud labels." ,
requirements , newVolume . Spec . NodeAffinity )
} else {
for _ , req := range requirements {
for i := range newVolume . Spec . NodeAffinity . Required . NodeSelectorTerms {
newVolume . Spec . NodeAffinity . Required . NodeSelectorTerms [ i ] . MatchExpressions = append ( newVolume . Spec . NodeAffinity . Required . NodeSelectorTerms [ i ] . MatchExpressions , req )
}
}
}
2018-01-09 18:57:14 +00:00
}
newVolume . Initializers = removeInitializer ( newVolume . Initializers , initializerName )
2018-11-26 18:23:56 +00:00
klog . V ( 4 ) . Infof ( "removed initializer on PersistentVolume %s" , newVolume . Name )
2018-01-09 18:57:14 +00:00
oldData , err := json . Marshal ( vol )
if err != nil {
return nil , fmt . Errorf ( "failed to marshal old persistentvolume %#v for persistentvolume %q: %v" , vol , volName , err )
}
newData , err := json . Marshal ( newVolume )
if err != nil {
return nil , fmt . Errorf ( "failed to marshal new persistentvolume %#v for persistentvolume %q: %v" , newVolume , volName , err )
}
patch , err := strategicpatch . CreateTwoWayMergePatch ( oldData , newData , v1 . PersistentVolume { } )
if err != nil {
return nil , fmt . Errorf ( "failed to create patch for persistentvolume %q: %v" , volName , err )
}
return patch , nil
}
func ( pvlc * PersistentVolumeLabelController ) updateVolume ( vol * v1 . PersistentVolume , volLabels map [ string ] string ) error {
volName := vol . Name
2018-11-26 18:23:56 +00:00
klog . V ( 4 ) . Infof ( "updating PersistentVolume %s" , volName )
2018-01-09 18:57:14 +00:00
patchBytes , err := pvlc . createPatch ( vol , volLabels )
if err != nil {
return err
}
_ , err = pvlc . kubeClient . CoreV1 ( ) . PersistentVolumes ( ) . Patch ( string ( volName ) , types . StrategicMergePatchType , patchBytes )
if err != nil {
return fmt . Errorf ( "failed to update PersistentVolume %s: %v" , volName , err )
}
2018-11-26 18:23:56 +00:00
klog . V ( 4 ) . Infof ( "updated PersistentVolume %s" , volName )
2018-01-09 18:57:14 +00:00
2018-11-26 18:23:56 +00:00
return nil
2018-01-09 18:57:14 +00:00
}
func removeInitializer ( initializers * metav1 . Initializers , name string ) * metav1 . Initializers {
if initializers == nil {
return nil
}
var updated [ ] metav1 . Initializer
for _ , pending := range initializers . Pending {
if pending . Name != name {
updated = append ( updated , pending )
}
}
if len ( updated ) == len ( initializers . Pending ) {
return initializers
}
if len ( updated ) == 0 {
return nil
}
return & metav1 . Initializers { Pending : updated }
}
2018-03-06 22:33:18 +00:00
// needsInitialization checks whether or not the PVL is the next pending initializer.
2018-01-09 18:57:14 +00:00
func needsInitialization ( initializers * metav1 . Initializers , name string ) bool {
2018-03-06 22:33:18 +00:00
if initializers == nil {
return false
}
2018-01-09 18:57:14 +00:00
2018-03-06 22:33:18 +00:00
if len ( initializers . Pending ) == 0 {
return false
2018-01-09 18:57:14 +00:00
}
2018-03-06 22:33:18 +00:00
// There is at least one initializer still pending so check to
// see if the PVL is the next in line.
return initializers . Pending [ 0 ] . Name == name
2018-01-09 18:57:14 +00:00
}