2016-04-30 06:36:27 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-04-30 06:36:27 +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 reconciler implements interfaces that attempt to reconcile the
// desired state of the with the actual state of the world by triggering
// actions.
package reconciler
import (
"time"
"github.com/golang/glog"
2016-07-02 01:50:25 +00:00
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
"k8s.io/kubernetes/pkg/controller/volume/attachdetach/statusupdater"
2016-07-14 05:38:54 +00:00
"k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff"
2016-04-30 06:36:27 +00:00
"k8s.io/kubernetes/pkg/util/wait"
2016-07-14 05:38:54 +00:00
"k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations"
2016-05-30 02:22:22 +00:00
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
2016-04-30 06:36:27 +00:00
)
// Reconciler runs a periodic loop to reconcile the desired state of the with
// the actual state of the world by triggering attach detach operations.
2016-05-30 02:22:22 +00:00
// Note: This is distinct from the Reconciler implemented by the kubelet volume
// manager. This reconciles state for the attach/detach controller. That
// reconciles state for the kubelet volume manager.
2016-04-30 06:36:27 +00:00
type Reconciler interface {
2016-05-23 20:37:30 +00:00
// Starts running the reconciliation loop which executes periodically, checks
2016-04-30 06:36:27 +00:00
// if volumes that should be attached are attached and volumes that should
// be detached are detached. If not, it will trigger attach/detach
// operations to rectify.
Run ( stopCh <- chan struct { } )
}
// NewReconciler returns a new instance of Reconciler that waits loopPeriod
// between successive executions.
2016-05-23 20:37:30 +00:00
// loopPeriod is the amount of time the reconciler loop waits between
2016-04-30 06:36:27 +00:00
// successive executions.
2016-05-23 20:37:30 +00:00
// maxWaitForUnmountDuration is the max amount of time the reconciler will wait
// for the volume to be safely unmounted, after this it will detach the volume
// anyway (to handle crashed/unavailable nodes). If during this time the volume
// becomes used by a new pod, the detach request will be aborted and the timer
// cleared.
2016-04-30 06:36:27 +00:00
func NewReconciler (
loopPeriod time . Duration ,
2016-05-23 20:37:30 +00:00
maxWaitForUnmountDuration time . Duration ,
2016-10-14 21:21:58 +00:00
syncDuration time . Duration ,
2016-04-30 06:36:27 +00:00
desiredStateOfWorld cache . DesiredStateOfWorld ,
actualStateOfWorld cache . ActualStateOfWorld ,
2016-06-16 06:48:04 +00:00
attacherDetacher operationexecutor . OperationExecutor ,
nodeStatusUpdater statusupdater . NodeStatusUpdater ) Reconciler {
2016-04-30 06:36:27 +00:00
return & reconciler {
2016-05-23 20:37:30 +00:00
loopPeriod : loopPeriod ,
maxWaitForUnmountDuration : maxWaitForUnmountDuration ,
2016-10-14 21:21:58 +00:00
syncDuration : syncDuration ,
2016-05-23 20:37:30 +00:00
desiredStateOfWorld : desiredStateOfWorld ,
actualStateOfWorld : actualStateOfWorld ,
attacherDetacher : attacherDetacher ,
2016-06-16 06:48:04 +00:00
nodeStatusUpdater : nodeStatusUpdater ,
2016-10-14 21:21:58 +00:00
timeOfLastSync : time . Now ( ) ,
2016-04-30 06:36:27 +00:00
}
}
type reconciler struct {
2016-05-23 20:37:30 +00:00
loopPeriod time . Duration
maxWaitForUnmountDuration time . Duration
2016-10-14 21:21:58 +00:00
syncDuration time . Duration
2016-05-23 20:37:30 +00:00
desiredStateOfWorld cache . DesiredStateOfWorld
actualStateOfWorld cache . ActualStateOfWorld
2016-05-30 02:22:22 +00:00
attacherDetacher operationexecutor . OperationExecutor
2016-06-16 06:48:04 +00:00
nodeStatusUpdater statusupdater . NodeStatusUpdater
2016-10-14 21:21:58 +00:00
timeOfLastSync time . Time
2016-04-30 06:36:27 +00:00
}
func ( rc * reconciler ) Run ( stopCh <- chan struct { } ) {
wait . Until ( rc . reconciliationLoopFunc ( ) , rc . loopPeriod , stopCh )
}
func ( rc * reconciler ) reconciliationLoopFunc ( ) func ( ) {
return func ( ) {
2016-10-14 21:21:58 +00:00
rc . reconcile ( )
// reconciler periodically checks whether the attached volumes from actual state
// are still attached to the node and udpate the status if they are not.
if time . Since ( rc . timeOfLastSync ) > rc . syncDuration {
rc . sync ( )
}
}
}
2016-09-07 22:30:16 +00:00
2016-10-14 21:21:58 +00:00
func ( rc * reconciler ) sync ( ) {
defer rc . updateSyncTime ( )
rc . syncStates ( )
}
2016-06-22 04:47:52 +00:00
2016-10-14 21:21:58 +00:00
func ( rc * reconciler ) updateSyncTime ( ) {
rc . timeOfLastSync = time . Now ( )
}
2016-06-22 04:47:52 +00:00
2016-10-14 21:21:58 +00:00
func ( rc * reconciler ) syncStates ( ) {
volumesPerNode := rc . actualStateOfWorld . GetAttachedVolumesPerNode ( )
for nodeName , volumes := range volumesPerNode {
err := rc . attacherDetacher . VerifyVolumesAreAttached ( volumes , nodeName , rc . actualStateOfWorld )
if err != nil {
glog . Errorf ( "Error in syncing states for volumes: %v" , err )
}
}
}
func ( rc * reconciler ) reconcile ( ) {
// Detaches are triggered before attaches so that volumes referenced by
// pods that are rescheduled to a different node are detached first.
// Ensure volumes that should be detached are detached.
for _ , attachedVolume := range rc . actualStateOfWorld . GetAttachedVolumes ( ) {
if ! rc . desiredStateOfWorld . VolumeExists (
attachedVolume . VolumeName , attachedVolume . NodeName ) {
// Set the detach request time
elapsedTime , err := rc . actualStateOfWorld . SetDetachRequestTime ( attachedVolume . VolumeName , attachedVolume . NodeName )
if err != nil {
glog . Errorf ( "Cannot trigger detach because it fails to set detach request time with error %v" , err )
continue
}
// Check whether timeout has reached the maximum waiting time
timeout := elapsedTime > rc . maxWaitForUnmountDuration
// Check whether volume is still mounted. Skip detach if it is still mounted unless timeout
if attachedVolume . MountedByNode && ! timeout {
glog . V ( 12 ) . Infof ( "Cannot trigger detach for volume %q on node %q because volume is still mounted" ,
attachedVolume . VolumeName ,
attachedVolume . NodeName )
continue
}
// Before triggering volume detach, mark volume as detached and update the node status
// If it fails to update node status, skip detach volume
rc . actualStateOfWorld . RemoveVolumeFromReportAsAttached ( attachedVolume . VolumeName , attachedVolume . NodeName )
// Update Node Status to indicate volume is no longer safe to mount.
err = rc . nodeStatusUpdater . UpdateNodeStatuses ( )
if err != nil {
// Skip detaching this volume if unable to update node status
glog . Errorf ( "UpdateNodeStatuses failed while attempting to report volume %q as attached to node %q with: %v " ,
attachedVolume . VolumeName ,
attachedVolume . NodeName ,
err )
continue
}
// Trigger detach volume which requires verifing safe to detach step
// If timeout is true, skip verifySafeToDetach check
glog . V ( 5 ) . Infof ( "Attempting to start DetachVolume for volume %q from node %q" , attachedVolume . VolumeName , attachedVolume . NodeName )
verifySafeToDetach := ! timeout
err = rc . attacherDetacher . DetachVolume ( attachedVolume . AttachedVolume , verifySafeToDetach , rc . actualStateOfWorld )
if err == nil {
if ! timeout {
glog . Infof ( "Started DetachVolume for volume %q from node %q" , attachedVolume . VolumeName , attachedVolume . NodeName )
} else {
glog . Infof ( "Started DetachVolume for volume %q from node %q. This volume is not safe to detach, but maxWaitForUnmountDuration %v expired, force detaching" ,
2016-09-07 22:30:16 +00:00
attachedVolume . VolumeName ,
attachedVolume . NodeName ,
2016-10-14 21:21:58 +00:00
rc . maxWaitForUnmountDuration )
2016-09-07 22:30:16 +00:00
}
2016-04-30 06:36:27 +00:00
}
2016-10-14 21:21:58 +00:00
if err != nil &&
! nestedpendingoperations . IsAlreadyExists ( err ) &&
! exponentialbackoff . IsExponentialBackoff ( err ) {
// Ignore nestedpendingoperations.IsAlreadyExists && exponentialbackoff.IsExponentialBackoff errors, they are expected.
// Log all other errors.
glog . Errorf (
"operationExecutor.DetachVolume failed to start for volume %q (spec.Name: %q) from node %q with err: %v" ,
attachedVolume . VolumeName ,
attachedVolume . VolumeSpec . Name ( ) ,
attachedVolume . NodeName ,
err )
}
2016-04-30 06:36:27 +00:00
}
2016-10-14 21:21:58 +00:00
}
2016-05-23 20:37:30 +00:00
2016-10-14 21:21:58 +00:00
// Ensure volumes that should be attached are attached.
for _ , volumeToAttach := range rc . desiredStateOfWorld . GetVolumesToAttach ( ) {
if rc . actualStateOfWorld . VolumeNodeExists (
volumeToAttach . VolumeName , volumeToAttach . NodeName ) {
// Volume/Node exists, touch it to reset detachRequestedTime
2016-11-15 19:54:50 +00:00
glog . V ( 5 ) . Infof ( "Volume %q/Node %q is attached--touching." , volumeToAttach . VolumeName , volumeToAttach . NodeName )
2016-10-14 21:21:58 +00:00
rc . actualStateOfWorld . ResetDetachRequestTime ( volumeToAttach . VolumeName , volumeToAttach . NodeName )
} else {
// Volume/Node doesn't exist, spawn a goroutine to attach it
2016-11-15 19:54:50 +00:00
glog . V ( 5 ) . Infof ( "Attempting to start AttachVolume for volume %q to node %q" , volumeToAttach . VolumeName , volumeToAttach . NodeName )
2016-10-14 21:21:58 +00:00
err := rc . attacherDetacher . AttachVolume ( volumeToAttach . VolumeToAttach , rc . actualStateOfWorld )
if err == nil {
glog . Infof ( "Started AttachVolume for volume %q to node %q" , volumeToAttach . VolumeName , volumeToAttach . NodeName )
}
if err != nil &&
! nestedpendingoperations . IsAlreadyExists ( err ) &&
! exponentialbackoff . IsExponentialBackoff ( err ) {
// Ignore nestedpendingoperations.IsAlreadyExists && exponentialbackoff.IsExponentialBackoff errors, they are expected.
// Log all other errors.
glog . Errorf (
"operationExecutor.AttachVolume failed to start for volume %q (spec.Name: %q) to node %q with err: %v" ,
volumeToAttach . VolumeName ,
volumeToAttach . VolumeSpec . Name ( ) ,
volumeToAttach . NodeName ,
err )
2016-05-23 20:37:30 +00:00
}
}
2016-10-14 21:21:58 +00:00
}
2016-06-16 06:48:04 +00:00
2016-10-14 21:21:58 +00:00
// Update Node Status
err := rc . nodeStatusUpdater . UpdateNodeStatuses ( )
if err != nil {
glog . Infof ( "UpdateNodeStatuses failed with: %v" , err )
2016-04-30 06:36:27 +00:00
}
}