mirror of https://github.com/k3s-io/k3s
implement kubelet side online file system resize for volume
parent
d02cf08e27
commit
ca12c73323
|
@ -99,6 +99,11 @@ const (
|
||||||
// Ability to Expand persistent volumes
|
// Ability to Expand persistent volumes
|
||||||
ExpandPersistentVolumes utilfeature.Feature = "ExpandPersistentVolumes"
|
ExpandPersistentVolumes utilfeature.Feature = "ExpandPersistentVolumes"
|
||||||
|
|
||||||
|
// owner: @mlmhl
|
||||||
|
// alpha: v1.11
|
||||||
|
// Ability to expand persistent volumes' file system without unmounting volumes.
|
||||||
|
ExpandPersistentVolumesFSWithoutUnmounting utilfeature.Feature = "ExpandPersistentVolumesFSWithoutUnmounting"
|
||||||
|
|
||||||
// owner: @verb
|
// owner: @verb
|
||||||
// alpha: v1.10
|
// alpha: v1.10
|
||||||
//
|
//
|
||||||
|
@ -328,28 +333,29 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
||||||
MountPropagation: {Default: true, PreRelease: utilfeature.Beta},
|
MountPropagation: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
QOSReserved: {Default: false, PreRelease: utilfeature.Alpha},
|
QOSReserved: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
ExpandPersistentVolumes: {Default: true, PreRelease: utilfeature.Beta},
|
ExpandPersistentVolumes: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
CPUManager: {Default: true, PreRelease: utilfeature.Beta},
|
ExpandPersistentVolumesFSWithoutUnmounting: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha},
|
CPUManager: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
|
ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
VolumeScheduling: {Default: true, PreRelease: utilfeature.Beta},
|
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
CSIPersistentVolume: {Default: true, PreRelease: utilfeature.Beta},
|
VolumeScheduling: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
CustomPodDNS: {Default: true, PreRelease: utilfeature.Beta},
|
CSIPersistentVolume: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
BlockVolume: {Default: false, PreRelease: utilfeature.Alpha},
|
CustomPodDNS: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
StorageObjectInUseProtection: {Default: true, PreRelease: utilfeature.GA},
|
BlockVolume: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
ResourceLimitsPriorityFunction: {Default: false, PreRelease: utilfeature.Alpha},
|
StorageObjectInUseProtection: {Default: true, PreRelease: utilfeature.GA},
|
||||||
SupportIPVSProxyMode: {Default: true, PreRelease: utilfeature.GA},
|
ResourceLimitsPriorityFunction: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
SupportPodPidsLimit: {Default: false, PreRelease: utilfeature.Alpha},
|
SupportIPVSProxyMode: {Default: true, PreRelease: utilfeature.GA},
|
||||||
HyperVContainer: {Default: false, PreRelease: utilfeature.Alpha},
|
SupportPodPidsLimit: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
ScheduleDaemonSetPods: {Default: false, PreRelease: utilfeature.Alpha},
|
HyperVContainer: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
TokenRequest: {Default: false, PreRelease: utilfeature.Alpha},
|
ScheduleDaemonSetPods: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
TokenRequestProjection: {Default: false, PreRelease: utilfeature.Alpha},
|
TokenRequest: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
CRIContainerLogRotation: {Default: true, PreRelease: utilfeature.Beta},
|
TokenRequestProjection: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
GCERegionalPersistentDisk: {Default: true, PreRelease: utilfeature.Beta},
|
CRIContainerLogRotation: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
RunAsGroup: {Default: false, PreRelease: utilfeature.Alpha},
|
GCERegionalPersistentDisk: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
VolumeSubpath: {Default: true, PreRelease: utilfeature.GA},
|
RunAsGroup: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
BalanceAttachedNodeVolumes: {Default: false, PreRelease: utilfeature.Alpha},
|
VolumeSubpath: {Default: true, PreRelease: utilfeature.GA},
|
||||||
DynamicProvisioningScheduling: {Default: false, PreRelease: utilfeature.Alpha},
|
BalanceAttachedNodeVolumes: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
VolumeSubpathEnvExpansion: {Default: false, PreRelease: utilfeature.Alpha},
|
DynamicProvisioningScheduling: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
|
VolumeSubpathEnvExpansion: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
|
|
||||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||||
// unintentionally on either side:
|
// unintentionally on either side:
|
||||||
|
|
|
@ -14,6 +14,7 @@ go_library(
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache",
|
importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache",
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
"//pkg/volume/util:go_default_library",
|
"//pkg/volume/util:go_default_library",
|
||||||
"//pkg/volume/util/operationexecutor:go_default_library",
|
"//pkg/volume/util/operationexecutor:go_default_library",
|
||||||
|
@ -21,6 +22,7 @@ go_library(
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ import (
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
"k8s.io/kubernetes/pkg/volume/util"
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
|
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
|
||||||
|
@ -148,6 +150,11 @@ type ActualStateOfWorld interface {
|
||||||
// with pod's unique name. This map can be used to determine which pod is currently
|
// with pod's unique name. This map can be used to determine which pod is currently
|
||||||
// in actual state of world.
|
// in actual state of world.
|
||||||
GetPods() map[volumetypes.UniquePodName]bool
|
GetPods() map[volumetypes.UniquePodName]bool
|
||||||
|
|
||||||
|
// MarkFSResizeRequired marks each volume that is successfully attached and
|
||||||
|
// mounted for the specified pod as requiring file system resize (if the plugin for the
|
||||||
|
// volume indicates it requires file system resize).
|
||||||
|
MarkFSResizeRequired(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MountedVolume represents a volume that has successfully been mounted to a pod.
|
// MountedVolume represents a volume that has successfully been mounted to a pod.
|
||||||
|
@ -291,6 +298,10 @@ type mountedPod struct {
|
||||||
|
|
||||||
// volumeGidValue contains the value of the GID annotation, if present.
|
// volumeGidValue contains the value of the GID annotation, if present.
|
||||||
volumeGidValue string
|
volumeGidValue string
|
||||||
|
|
||||||
|
// fsResizeRequired indicates the underlying volume has been successfully
|
||||||
|
// mounted to this pod but its size has been expanded after that.
|
||||||
|
fsResizeRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (asw *actualStateOfWorld) MarkVolumeAsAttached(
|
func (asw *actualStateOfWorld) MarkVolumeAsAttached(
|
||||||
|
@ -444,6 +455,34 @@ func (asw *actualStateOfWorld) AddPodToVolume(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) MarkVolumeAsResized(
|
||||||
|
podName volumetypes.UniquePodName,
|
||||||
|
volumeName v1.UniqueVolumeName) error {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"no volume with the name %q exists in the list of attached volumes",
|
||||||
|
volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
podObj, podExists := volumeObj.mountedPods[podName]
|
||||||
|
if !podExists {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"no pod with the name %q exists in the mounted pods list of volume %s",
|
||||||
|
podName,
|
||||||
|
volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(5).Infof("Volume %s(OuterVolumeSpecName %s) of pod %s has been resized",
|
||||||
|
volumeName, podObj.outerVolumeSpecName, podName)
|
||||||
|
podObj.fsResizeRequired = false
|
||||||
|
asw.attachedVolumes[volumeName].mountedPods[podName] = podObj
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (asw *actualStateOfWorld) MarkRemountRequired(
|
func (asw *actualStateOfWorld) MarkRemountRequired(
|
||||||
podName volumetypes.UniquePodName) {
|
podName volumetypes.UniquePodName) {
|
||||||
asw.Lock()
|
asw.Lock()
|
||||||
|
@ -475,6 +514,46 @@ func (asw *actualStateOfWorld) MarkRemountRequired(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) MarkFSResizeRequired(
|
||||||
|
volumeName v1.UniqueVolumeName,
|
||||||
|
podName volumetypes.UniquePodName) {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
volumeObj, exist := asw.attachedVolumes[volumeName]
|
||||||
|
if !exist {
|
||||||
|
glog.Warningf("MarkFSResizeRequired for volume %s failed as volume not exist", volumeName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
podObj, exist := volumeObj.mountedPods[podName]
|
||||||
|
if !exist {
|
||||||
|
glog.Warningf("MarkFSResizeRequired for volume %s failed "+
|
||||||
|
"as pod(%s) not exist", volumeName, podName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
volumePlugin, err :=
|
||||||
|
asw.volumePluginMgr.FindExpandablePluginBySpec(podObj.volumeSpec)
|
||||||
|
if err != nil || volumePlugin == nil {
|
||||||
|
// Log and continue processing
|
||||||
|
glog.Errorf(
|
||||||
|
"MarkFSResizeRequired failed to find expandable plugin for pod %q volume: %q (volSpecName: %q)",
|
||||||
|
podObj.podName,
|
||||||
|
volumeObj.volumeName,
|
||||||
|
podObj.volumeSpec.Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if volumePlugin.RequiresFSResize() {
|
||||||
|
if !podObj.fsResizeRequired {
|
||||||
|
glog.V(3).Infof("PVC volume %s(OuterVolumeSpecName %s) of pod %s requires file system resize",
|
||||||
|
volumeName, podObj.outerVolumeSpecName, podName)
|
||||||
|
podObj.fsResizeRequired = true
|
||||||
|
}
|
||||||
|
asw.attachedVolumes[volumeName].mountedPods[podName] = podObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (asw *actualStateOfWorld) SetVolumeGloballyMounted(
|
func (asw *actualStateOfWorld) SetVolumeGloballyMounted(
|
||||||
volumeName v1.UniqueVolumeName, globallyMounted bool, devicePath, deviceMountPath string) error {
|
volumeName v1.UniqueVolumeName, globallyMounted bool, devicePath, deviceMountPath string) error {
|
||||||
asw.Lock()
|
asw.Lock()
|
||||||
|
@ -546,8 +625,14 @@ func (asw *actualStateOfWorld) PodExistsInVolume(
|
||||||
}
|
}
|
||||||
|
|
||||||
podObj, podExists := volumeObj.mountedPods[podName]
|
podObj, podExists := volumeObj.mountedPods[podName]
|
||||||
if podExists && podObj.remountRequired {
|
if podExists {
|
||||||
return true, volumeObj.devicePath, newRemountRequiredError(volumeObj.volumeName, podObj.podName)
|
if podObj.remountRequired {
|
||||||
|
return true, volumeObj.devicePath, newRemountRequiredError(volumeObj.volumeName, podObj.podName)
|
||||||
|
}
|
||||||
|
if podObj.fsResizeRequired &&
|
||||||
|
utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumesFSWithoutUnmounting) {
|
||||||
|
return true, volumeObj.devicePath, newFsResizeRequiredError(volumeObj.volumeName, podObj.podName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return podExists, volumeObj.devicePath, nil
|
return podExists, volumeObj.devicePath, nil
|
||||||
|
@ -716,6 +801,35 @@ func newRemountRequiredError(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fsResizeRequiredError is an error returned when PodExistsInVolume() found
|
||||||
|
// volume/pod attached/mounted but fsResizeRequired was true, indicating the
|
||||||
|
// given volume receives an resize request after attached/mounted.
|
||||||
|
type fsResizeRequiredError struct {
|
||||||
|
volumeName v1.UniqueVolumeName
|
||||||
|
podName volumetypes.UniquePodName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err fsResizeRequiredError) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"volumeName %q mounted to %q needs to resize file system",
|
||||||
|
err.volumeName, err.podName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFsResizeRequiredError(
|
||||||
|
volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) error {
|
||||||
|
return fsResizeRequiredError{
|
||||||
|
volumeName: volumeName,
|
||||||
|
podName: podName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFSResizeRequiredError returns true if the specified error is a
|
||||||
|
// fsResizeRequiredError.
|
||||||
|
func IsFSResizeRequiredError(err error) bool {
|
||||||
|
_, ok := err.(fsResizeRequiredError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// getMountedVolume constructs and returns a MountedVolume object from the given
|
// getMountedVolume constructs and returns a MountedVolume object from the given
|
||||||
// mountedPod and attachedVolume objects.
|
// mountedPod and attachedVolume objects.
|
||||||
func getMountedVolume(
|
func getMountedVolume(
|
||||||
|
|
|
@ -25,6 +25,7 @@ go_library(
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
|
@ -49,6 +50,7 @@ go_test(
|
||||||
srcs = ["desired_state_of_world_populator_test.go"],
|
srcs = ["desired_state_of_world_populator_test.go"],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/kubelet/configmap:go_default_library",
|
"//pkg/kubelet/configmap:go_default_library",
|
||||||
"//pkg/kubelet/container/testing:go_default_library",
|
"//pkg/kubelet/container/testing:go_default_library",
|
||||||
"//pkg/kubelet/pod:go_default_library",
|
"//pkg/kubelet/pod:go_default_library",
|
||||||
|
@ -61,6 +63,7 @@ go_test(
|
||||||
"//pkg/volume/util:go_default_library",
|
"//pkg/volume/util:go_default_library",
|
||||||
"//pkg/volume/util/types:go_default_library",
|
"//pkg/volume/util/types:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
@ -182,12 +183,26 @@ func (dswp *desiredStateOfWorldPopulator) isPodTerminated(pod *v1.Pod) bool {
|
||||||
// Iterate through all pods and add to desired state of world if they don't
|
// Iterate through all pods and add to desired state of world if they don't
|
||||||
// exist but should
|
// exist but should
|
||||||
func (dswp *desiredStateOfWorldPopulator) findAndAddNewPods() {
|
func (dswp *desiredStateOfWorldPopulator) findAndAddNewPods() {
|
||||||
|
// Map unique pod name to outer volume name to MountedVolume.
|
||||||
|
mountedVolumesForPod := make(map[volumetypes.UniquePodName]map[string]cache.MountedVolume)
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumesFSWithoutUnmounting) {
|
||||||
|
for _, mountedVolume := range dswp.actualStateOfWorld.GetMountedVolumes() {
|
||||||
|
mountedVolumes, exist := mountedVolumesForPod[mountedVolume.PodName]
|
||||||
|
if !exist {
|
||||||
|
mountedVolumes = make(map[string]cache.MountedVolume)
|
||||||
|
mountedVolumesForPod[mountedVolume.PodName] = mountedVolumes
|
||||||
|
}
|
||||||
|
mountedVolumes[mountedVolume.OuterVolumeSpecName] = mountedVolume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processedVolumesForFSResize := sets.NewString()
|
||||||
for _, pod := range dswp.podManager.GetPods() {
|
for _, pod := range dswp.podManager.GetPods() {
|
||||||
if dswp.isPodTerminated(pod) {
|
if dswp.isPodTerminated(pod) {
|
||||||
// Do not (re)add volumes for terminated pods
|
// Do not (re)add volumes for terminated pods
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dswp.processPodVolumes(pod)
|
dswp.processPodVolumes(pod, mountedVolumesForPod, processedVolumesForFSResize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +274,10 @@ func (dswp *desiredStateOfWorldPopulator) findAndRemoveDeletedPods() {
|
||||||
|
|
||||||
// processPodVolumes processes the volumes in the given pod and adds them to the
|
// processPodVolumes processes the volumes in the given pod and adds them to the
|
||||||
// desired state of the world.
|
// desired state of the world.
|
||||||
func (dswp *desiredStateOfWorldPopulator) processPodVolumes(pod *v1.Pod) {
|
func (dswp *desiredStateOfWorldPopulator) processPodVolumes(
|
||||||
|
pod *v1.Pod,
|
||||||
|
mountedVolumesForPod map[volumetypes.UniquePodName]map[string]cache.MountedVolume,
|
||||||
|
processedVolumesForFSResize sets.String) {
|
||||||
if pod == nil {
|
if pod == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -274,7 +292,7 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes(pod *v1.Pod) {
|
||||||
|
|
||||||
// Process volume spec for each volume defined in pod
|
// Process volume spec for each volume defined in pod
|
||||||
for _, podVolume := range pod.Spec.Volumes {
|
for _, podVolume := range pod.Spec.Volumes {
|
||||||
volumeSpec, volumeGidValue, err :=
|
pvc, volumeSpec, volumeGidValue, err :=
|
||||||
dswp.createVolumeSpec(podVolume, pod.Name, pod.Namespace, mountsMap, devicesMap)
|
dswp.createVolumeSpec(podVolume, pod.Name, pod.Namespace, mountsMap, devicesMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf(
|
glog.Errorf(
|
||||||
|
@ -304,6 +322,11 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes(pod *v1.Pod) {
|
||||||
podVolume.Name,
|
podVolume.Name,
|
||||||
volumeSpec.Name(),
|
volumeSpec.Name(),
|
||||||
uniquePodName)
|
uniquePodName)
|
||||||
|
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumesFSWithoutUnmounting) {
|
||||||
|
dswp.checkVolumeFSResize(pod, podVolume, pvc, volumeSpec,
|
||||||
|
uniquePodName, mountedVolumesForPod, processedVolumesForFSResize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// some of the volume additions may have failed, should not mark this pod as fully processed
|
// some of the volume additions may have failed, should not mark this pod as fully processed
|
||||||
|
@ -316,6 +339,106 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes(pod *v1.Pod) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkVolumeFSResize checks whether a PVC mounted by the pod requires file
|
||||||
|
// system resize or not. If so, marks this volume as fsResizeRequired in ASW.
|
||||||
|
// - mountedVolumesForPod stores all mounted volumes in ASW, because online
|
||||||
|
// volume resize only considers mounted volumes.
|
||||||
|
// - processedVolumesForFSResize stores all volumes we have checked in current loop,
|
||||||
|
// because file system resize operation is a global operation for volume, so
|
||||||
|
// we only need to check it once if more than one pod use it.
|
||||||
|
func (dswp *desiredStateOfWorldPopulator) checkVolumeFSResize(
|
||||||
|
pod *v1.Pod,
|
||||||
|
podVolume v1.Volume,
|
||||||
|
pvc *v1.PersistentVolumeClaim,
|
||||||
|
volumeSpec *volume.Spec,
|
||||||
|
uniquePodName volumetypes.UniquePodName,
|
||||||
|
mountedVolumesForPod map[volumetypes.UniquePodName]map[string]cache.MountedVolume,
|
||||||
|
processedVolumesForFSResize sets.String) {
|
||||||
|
if podVolume.PersistentVolumeClaim == nil {
|
||||||
|
// Only PVC supports resize operation.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uniqueVolumeName, exist := getUniqueVolumeName(uniquePodName, podVolume.Name, mountedVolumesForPod)
|
||||||
|
if !exist {
|
||||||
|
// Volume not exist in ASW, we assume it hasn't been mounted yet. If it needs resize,
|
||||||
|
// it will be handled as offline resize(if it indeed hasn't been mounted yet),
|
||||||
|
// or online resize in subsequent loop(after we confirm it has been mounted).
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fsVolume, err := util.CheckVolumeModeFilesystem(volumeSpec)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Check volume mode failed for volume %s(OuterVolumeSpecName %s): %v",
|
||||||
|
uniqueVolumeName, podVolume.Name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !fsVolume {
|
||||||
|
glog.V(5).Infof("Block mode volume needn't to check file system resize request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if processedVolumesForFSResize.Has(string(uniqueVolumeName)) {
|
||||||
|
// File system resize operation is a global operation for volume,
|
||||||
|
// so we only need to check it once if more than one pod use it.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mountedReadOnlyByPod(podVolume, pod) {
|
||||||
|
// This volume is used as read only by this pod, we don't perform resize for read only volumes.
|
||||||
|
glog.V(5).Infof("Skip file system resize check for volume %s in pod %s/%s "+
|
||||||
|
"as the volume is mounted as readonly", podVolume.Name, pod.Namespace, pod.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if volumeRequiresFSResize(pvc, volumeSpec.PersistentVolume) {
|
||||||
|
dswp.actualStateOfWorld.MarkFSResizeRequired(uniqueVolumeName, uniquePodName)
|
||||||
|
}
|
||||||
|
processedVolumesForFSResize.Insert(string(uniqueVolumeName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func mountedReadOnlyByPod(podVolume v1.Volume, pod *v1.Pod) bool {
|
||||||
|
if podVolume.PersistentVolumeClaim.ReadOnly {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, container := range pod.Spec.InitContainers {
|
||||||
|
if !mountedReadOnlyByContainer(podVolume.Name, &container) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, container := range pod.Spec.Containers {
|
||||||
|
if !mountedReadOnlyByContainer(podVolume.Name, &container) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func mountedReadOnlyByContainer(volumeName string, container *v1.Container) bool {
|
||||||
|
for _, volumeMount := range container.VolumeMounts {
|
||||||
|
if volumeMount.Name == volumeName && !volumeMount.ReadOnly {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUniqueVolumeName(
|
||||||
|
podName volumetypes.UniquePodName,
|
||||||
|
outerVolumeSpecName string,
|
||||||
|
mountedVolumesForPod map[volumetypes.UniquePodName]map[string]cache.MountedVolume) (v1.UniqueVolumeName, bool) {
|
||||||
|
mountedVolumes, exist := mountedVolumesForPod[podName]
|
||||||
|
if !exist {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
mountedVolume, exist := mountedVolumes[outerVolumeSpecName]
|
||||||
|
if !exist {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return mountedVolume.VolumeName, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func volumeRequiresFSResize(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) bool {
|
||||||
|
capacity := pvc.Status.Capacity[v1.ResourceStorage]
|
||||||
|
requested := pv.Spec.Capacity[v1.ResourceStorage]
|
||||||
|
return requested.Cmp(capacity) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// podPreviouslyProcessed returns true if the volumes for this pod have already
|
// podPreviouslyProcessed returns true if the volumes for this pod have already
|
||||||
// been processed by the populator
|
// been processed by the populator
|
||||||
func (dswp *desiredStateOfWorldPopulator) podPreviouslyProcessed(
|
func (dswp *desiredStateOfWorldPopulator) podPreviouslyProcessed(
|
||||||
|
@ -350,7 +473,7 @@ func (dswp *desiredStateOfWorldPopulator) deleteProcessedPod(
|
||||||
// specified volume. It dereference any PVC to get PV objects, if needed.
|
// specified volume. It dereference any PVC to get PV objects, if needed.
|
||||||
// Returns an error if unable to obtain the volume at this time.
|
// Returns an error if unable to obtain the volume at this time.
|
||||||
func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
||||||
podVolume v1.Volume, podName string, podNamespace string, mountsMap map[string]bool, devicesMap map[string]bool) (*volume.Spec, string, error) {
|
podVolume v1.Volume, podName string, podNamespace string, mountsMap map[string]bool, devicesMap map[string]bool) (*v1.PersistentVolumeClaim, *volume.Spec, string, error) {
|
||||||
if pvcSource :=
|
if pvcSource :=
|
||||||
podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
|
podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
|
||||||
glog.V(5).Infof(
|
glog.V(5).Infof(
|
||||||
|
@ -359,15 +482,16 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
||||||
pvcSource.ClaimName)
|
pvcSource.ClaimName)
|
||||||
|
|
||||||
// If podVolume is a PVC, fetch the real PV behind the claim
|
// If podVolume is a PVC, fetch the real PV behind the claim
|
||||||
pvName, pvcUID, err := dswp.getPVCExtractPV(
|
pvc, err := dswp.getPVCExtractPV(
|
||||||
podNamespace, pvcSource.ClaimName)
|
podNamespace, pvcSource.ClaimName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf(
|
return nil, nil, "", fmt.Errorf(
|
||||||
"error processing PVC %q/%q: %v",
|
"error processing PVC %q/%q: %v",
|
||||||
podNamespace,
|
podNamespace,
|
||||||
pvcSource.ClaimName,
|
pvcSource.ClaimName,
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
pvName, pvcUID := pvc.Spec.VolumeName, pvc.UID
|
||||||
|
|
||||||
glog.V(5).Infof(
|
glog.V(5).Infof(
|
||||||
"Found bound PV for PVC (ClaimName %q/%q pvcUID %v): pvName=%q",
|
"Found bound PV for PVC (ClaimName %q/%q pvcUID %v): pvName=%q",
|
||||||
|
@ -380,7 +504,7 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
||||||
volumeSpec, volumeGidValue, err :=
|
volumeSpec, volumeGidValue, err :=
|
||||||
dswp.getPVSpec(pvName, pvcSource.ReadOnly, pvcUID)
|
dswp.getPVSpec(pvName, pvcSource.ReadOnly, pvcUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf(
|
return nil, nil, "", fmt.Errorf(
|
||||||
"error processing PVC %q/%q: %v",
|
"error processing PVC %q/%q: %v",
|
||||||
podNamespace,
|
podNamespace,
|
||||||
pvcSource.ClaimName,
|
pvcSource.ClaimName,
|
||||||
|
@ -399,11 +523,11 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
|
||||||
volumeMode, err := util.GetVolumeMode(volumeSpec)
|
volumeMode, err := util.GetVolumeMode(volumeSpec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, nil, "", err
|
||||||
}
|
}
|
||||||
// Error if a container has volumeMounts but the volumeMode of PVC isn't Filesystem
|
// Error if a container has volumeMounts but the volumeMode of PVC isn't Filesystem
|
||||||
if mountsMap[podVolume.Name] && volumeMode != v1.PersistentVolumeFilesystem {
|
if mountsMap[podVolume.Name] && volumeMode != v1.PersistentVolumeFilesystem {
|
||||||
return nil, "", fmt.Errorf(
|
return nil, nil, "", fmt.Errorf(
|
||||||
"Volume %q has volumeMode %q, but is specified in volumeMounts for pod %q/%q",
|
"Volume %q has volumeMode %q, but is specified in volumeMounts for pod %q/%q",
|
||||||
podVolume.Name,
|
podVolume.Name,
|
||||||
volumeMode,
|
volumeMode,
|
||||||
|
@ -412,7 +536,7 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
||||||
}
|
}
|
||||||
// Error if a container has volumeDevices but the volumeMode of PVC isn't Block
|
// Error if a container has volumeDevices but the volumeMode of PVC isn't Block
|
||||||
if devicesMap[podVolume.Name] && volumeMode != v1.PersistentVolumeBlock {
|
if devicesMap[podVolume.Name] && volumeMode != v1.PersistentVolumeBlock {
|
||||||
return nil, "", fmt.Errorf(
|
return nil, nil, "", fmt.Errorf(
|
||||||
"Volume %q has volumeMode %q, but is specified in volumeDevices for pod %q/%q",
|
"Volume %q has volumeMode %q, but is specified in volumeDevices for pod %q/%q",
|
||||||
podVolume.Name,
|
podVolume.Name,
|
||||||
volumeMode,
|
volumeMode,
|
||||||
|
@ -420,13 +544,13 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
||||||
podName)
|
podName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return volumeSpec, volumeGidValue, nil
|
return pvc, volumeSpec, volumeGidValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not return the original volume object, since the source could mutate it
|
// Do not return the original volume object, since the source could mutate it
|
||||||
clonedPodVolume := podVolume.DeepCopy()
|
clonedPodVolume := podVolume.DeepCopy()
|
||||||
|
|
||||||
return volume.NewSpecFromVolume(clonedPodVolume), "", nil
|
return nil, volume.NewSpecFromVolume(clonedPodVolume), "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPVCExtractPV fetches the PVC object with the given namespace and name from
|
// getPVCExtractPV fetches the PVC object with the given namespace and name from
|
||||||
|
@ -434,11 +558,11 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
|
||||||
// it is pointing to and returns it.
|
// it is pointing to and returns it.
|
||||||
// An error is returned if the PVC object's phase is not "Bound".
|
// An error is returned if the PVC object's phase is not "Bound".
|
||||||
func (dswp *desiredStateOfWorldPopulator) getPVCExtractPV(
|
func (dswp *desiredStateOfWorldPopulator) getPVCExtractPV(
|
||||||
namespace string, claimName string) (string, types.UID, error) {
|
namespace string, claimName string) (*v1.PersistentVolumeClaim, error) {
|
||||||
pvc, err :=
|
pvc, err :=
|
||||||
dswp.kubeClient.CoreV1().PersistentVolumeClaims(namespace).Get(claimName, metav1.GetOptions{})
|
dswp.kubeClient.CoreV1().PersistentVolumeClaims(namespace).Get(claimName, metav1.GetOptions{})
|
||||||
if err != nil || pvc == nil {
|
if err != nil || pvc == nil {
|
||||||
return "", "", fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"failed to fetch PVC %s/%s from API server. err=%v",
|
"failed to fetch PVC %s/%s from API server. err=%v",
|
||||||
namespace,
|
namespace,
|
||||||
claimName,
|
claimName,
|
||||||
|
@ -455,7 +579,7 @@ func (dswp *desiredStateOfWorldPopulator) getPVCExtractPV(
|
||||||
// It should happen only in very rare case when scheduler schedules
|
// It should happen only in very rare case when scheduler schedules
|
||||||
// a pod and user deletes a PVC that's used by it at the same time.
|
// a pod and user deletes a PVC that's used by it at the same time.
|
||||||
if pvc.ObjectMeta.DeletionTimestamp != nil {
|
if pvc.ObjectMeta.DeletionTimestamp != nil {
|
||||||
return "", "", fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"can't start pod because PVC %s/%s is being deleted",
|
"can't start pod because PVC %s/%s is being deleted",
|
||||||
namespace,
|
namespace,
|
||||||
claimName)
|
claimName)
|
||||||
|
@ -464,7 +588,7 @@ func (dswp *desiredStateOfWorldPopulator) getPVCExtractPV(
|
||||||
|
|
||||||
if pvc.Status.Phase != v1.ClaimBound || pvc.Spec.VolumeName == "" {
|
if pvc.Status.Phase != v1.ClaimBound || pvc.Spec.VolumeName == "" {
|
||||||
|
|
||||||
return "", "", fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"PVC %s/%s has non-bound phase (%q) or empty pvc.Spec.VolumeName (%q)",
|
"PVC %s/%s has non-bound phase (%q) or empty pvc.Spec.VolumeName (%q)",
|
||||||
namespace,
|
namespace,
|
||||||
claimName,
|
claimName,
|
||||||
|
@ -472,7 +596,7 @@ func (dswp *desiredStateOfWorldPopulator) getPVCExtractPV(
|
||||||
pvc.Spec.VolumeName)
|
pvc.Spec.VolumeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pvc.Spec.VolumeName, pvc.UID, nil
|
return pvc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPVSpec fetches the PV object with the given name from the API server
|
// getPVSpec fetches the PV object with the given name from the API server
|
||||||
|
|
|
@ -20,12 +20,16 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
core "k8s.io/client-go/testing"
|
core "k8s.io/client-go/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/configmap"
|
"k8s.io/kubernetes/pkg/kubelet/configmap"
|
||||||
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||||
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
|
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
|
||||||
|
@ -294,7 +298,7 @@ func TestCreateVolumeSpec_Valid_File_VolumeMounts(t *testing.T) {
|
||||||
|
|
||||||
fakePodManager.AddPod(pod)
|
fakePodManager.AddPod(pod)
|
||||||
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
||||||
volumeSpec, _, err :=
|
_, volumeSpec, _, err :=
|
||||||
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -343,7 +347,7 @@ func TestCreateVolumeSpec_Valid_Block_VolumeDevices(t *testing.T) {
|
||||||
|
|
||||||
fakePodManager.AddPod(pod)
|
fakePodManager.AddPod(pod)
|
||||||
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
||||||
volumeSpec, _, err :=
|
_, volumeSpec, _, err :=
|
||||||
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -395,7 +399,7 @@ func TestCreateVolumeSpec_Invalid_File_VolumeDevices(t *testing.T) {
|
||||||
|
|
||||||
fakePodManager.AddPod(pod)
|
fakePodManager.AddPod(pod)
|
||||||
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
||||||
volumeSpec, _, err :=
|
_, volumeSpec, _, err :=
|
||||||
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -447,7 +451,7 @@ func TestCreateVolumeSpec_Invalid_Block_VolumeMounts(t *testing.T) {
|
||||||
|
|
||||||
fakePodManager.AddPod(pod)
|
fakePodManager.AddPod(pod)
|
||||||
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
mountsMap, devicesMap := dswp.makeVolumeMap(pod.Spec.Containers)
|
||||||
volumeSpec, _, err :=
|
_, volumeSpec, _, err :=
|
||||||
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -459,6 +463,155 @@ func TestCreateVolumeSpec_Invalid_Block_VolumeMounts(t *testing.T) {
|
||||||
utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
|
utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckVolumeFSResize(t *testing.T) {
|
||||||
|
pv := &v1.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "dswp-test-volume-name",
|
||||||
|
},
|
||||||
|
Spec: v1.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeSource: v1.PersistentVolumeSource{RBD: &v1.RBDPersistentVolumeSource{}},
|
||||||
|
Capacity: volumeCapacity(1),
|
||||||
|
ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "file-bound"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pvc := &v1.PersistentVolumeClaim{
|
||||||
|
Spec: v1.PersistentVolumeClaimSpec{
|
||||||
|
VolumeName: "dswp-test-volume-name",
|
||||||
|
Resources: v1.ResourceRequirements{
|
||||||
|
Requests: volumeCapacity(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: v1.PersistentVolumeClaimStatus{
|
||||||
|
Phase: v1.ClaimBound,
|
||||||
|
Capacity: volumeCapacity(1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dswp, fakePodManager, fakeDSW := createDswpWithVolume(t, pv, pvc)
|
||||||
|
fakeASW := dswp.actualStateOfWorld
|
||||||
|
|
||||||
|
// create pod
|
||||||
|
containers := []v1.Container{
|
||||||
|
{
|
||||||
|
VolumeMounts: []v1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "dswp-test-volume-name",
|
||||||
|
MountPath: "/mnt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "file-bound", containers)
|
||||||
|
uniquePodName := types.UniquePodName(pod.UID)
|
||||||
|
uniqueVolumeName := v1.UniqueVolumeName("fake-plugin/" + pod.Spec.Volumes[0].Name)
|
||||||
|
|
||||||
|
fakePodManager.AddPod(pod)
|
||||||
|
// Fill the dsw to contains volumes and pods.
|
||||||
|
dswp.findAndAddNewPods()
|
||||||
|
reconcileASW(fakeASW, fakeDSW, t)
|
||||||
|
|
||||||
|
// No resize request for volume, volumes in ASW shouldn't be marked as fsResizeRequired.
|
||||||
|
setExpandOnlinePersistentVolumesFeatureGate("true", t)
|
||||||
|
resizeRequiredVolumes := reprocess(dswp, uniquePodName, fakeDSW, fakeASW)
|
||||||
|
if len(resizeRequiredVolumes) > 0 {
|
||||||
|
t.Fatalf("No resize request for any volumes, but found resize required volumes in ASW: %v", resizeRequiredVolumes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a resize request to volume.
|
||||||
|
pv.Spec.Capacity = volumeCapacity(2)
|
||||||
|
pvc.Spec.Resources.Requests = volumeCapacity(2)
|
||||||
|
|
||||||
|
// Disable the feature gate, so volume shouldn't be marked as fsResizeRequired.
|
||||||
|
setExpandOnlinePersistentVolumesFeatureGate("false", t)
|
||||||
|
resizeRequiredVolumes = reprocess(dswp, uniquePodName, fakeDSW, fakeASW)
|
||||||
|
if len(resizeRequiredVolumes) > 0 {
|
||||||
|
t.Fatalf("Feature gate disabled, but found resize required volumes in ASW: %v", resizeRequiredVolumes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make volume used as ReadOnly, so volume shouldn't be marked as fsResizeRequired.
|
||||||
|
setExpandOnlinePersistentVolumesFeatureGate("true", t)
|
||||||
|
pod.Spec.Containers[0].VolumeMounts[0].ReadOnly = true
|
||||||
|
resizeRequiredVolumes = reprocess(dswp, uniquePodName, fakeDSW, fakeASW)
|
||||||
|
if len(resizeRequiredVolumes) > 0 {
|
||||||
|
t.Fatalf("volume mounted as ReadOnly, but found resize required volumes in ASW: %v", resizeRequiredVolumes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear ASW, so volume shouldn't be marked as fsResizeRequired because they are not mounted.
|
||||||
|
pod.Spec.Containers[0].VolumeMounts[0].ReadOnly = false
|
||||||
|
clearASW(fakeASW, fakeDSW, t)
|
||||||
|
resizeRequiredVolumes = reprocess(dswp, uniquePodName, fakeDSW, fakeASW)
|
||||||
|
if len(resizeRequiredVolumes) > 0 {
|
||||||
|
t.Fatalf("volume hasn't been mounted, but found resize required volumes in ASW: %v", resizeRequiredVolumes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// volume in ASW should be marked as fsResizeRequired.
|
||||||
|
reconcileASW(fakeASW, fakeDSW, t)
|
||||||
|
resizeRequiredVolumes = reprocess(dswp, uniquePodName, fakeDSW, fakeASW)
|
||||||
|
if len(resizeRequiredVolumes) == 0 {
|
||||||
|
t.Fatalf("Request resize for volume, but volume in ASW hasn't been marked as fsResizeRequired")
|
||||||
|
}
|
||||||
|
if len(resizeRequiredVolumes) != 1 {
|
||||||
|
t.Fatalf("Some unexpected volumes are marked as fsResizeRequired: %v", resizeRequiredVolumes)
|
||||||
|
}
|
||||||
|
if resizeRequiredVolumes[0] != uniqueVolumeName {
|
||||||
|
t.Fatalf("Mark wrong volume as fsResizeRequired: %s", resizeRequiredVolumes[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func volumeCapacity(size int) v1.ResourceList {
|
||||||
|
return v1.ResourceList{v1.ResourceStorage: resource.MustParse(fmt.Sprintf("%dGi", size))}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setExpandOnlinePersistentVolumesFeatureGate(value string, t *testing.T) {
|
||||||
|
err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=%s", features.ExpandPersistentVolumesFSWithoutUnmounting, value))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Set ExpandPersistentVolumesFSWithoutUnmounting feature gate to %s failed: %v", value, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reconcileASW(asw cache.ActualStateOfWorld, dsw cache.DesiredStateOfWorld, t *testing.T) {
|
||||||
|
for _, volumeToMount := range dsw.GetVolumesToMount() {
|
||||||
|
err := asw.MarkVolumeAsAttached(volumeToMount.VolumeName, volumeToMount.VolumeSpec, "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error when MarkVolumeAsAttached: %v", err)
|
||||||
|
}
|
||||||
|
err = asw.MarkVolumeAsMounted(volumeToMount.PodName, volumeToMount.Pod.UID,
|
||||||
|
volumeToMount.VolumeName, nil, nil, volumeToMount.OuterVolumeSpecName, volumeToMount.VolumeGidValue, volumeToMount.VolumeSpec)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error when MarkVolumeAsMounted: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearASW(asw cache.ActualStateOfWorld, dsw cache.DesiredStateOfWorld, t *testing.T) {
|
||||||
|
for _, volumeToMount := range dsw.GetVolumesToMount() {
|
||||||
|
err := asw.MarkVolumeAsUnmounted(volumeToMount.PodName, volumeToMount.VolumeName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error when MarkVolumeAsUnmounted: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, volumeToMount := range dsw.GetVolumesToMount() {
|
||||||
|
asw.MarkVolumeAsDetached(volumeToMount.VolumeName, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reprocess(dswp *desiredStateOfWorldPopulator, uniquePodName types.UniquePodName,
|
||||||
|
dsw cache.DesiredStateOfWorld, asw cache.ActualStateOfWorld) []v1.UniqueVolumeName {
|
||||||
|
dswp.ReprocessPod(uniquePodName)
|
||||||
|
dswp.findAndAddNewPods()
|
||||||
|
return getResizeRequiredVolumes(dsw, asw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResizeRequiredVolumes(dsw cache.DesiredStateOfWorld, asw cache.ActualStateOfWorld) []v1.UniqueVolumeName {
|
||||||
|
resizeRequiredVolumes := []v1.UniqueVolumeName{}
|
||||||
|
for _, volumeToMount := range dsw.GetVolumesToMount() {
|
||||||
|
_, _, err := asw.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName)
|
||||||
|
if cache.IsFSResizeRequiredError(err) {
|
||||||
|
resizeRequiredVolumes = append(resizeRequiredVolumes, volumeToMount.VolumeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resizeRequiredVolumes
|
||||||
|
}
|
||||||
|
|
||||||
func verifyVolumeExistsInVolumesToMount(t *testing.T, expectedVolumeName v1.UniqueVolumeName, expectReportedInUse bool, dsw cache.DesiredStateOfWorld) {
|
func verifyVolumeExistsInVolumesToMount(t *testing.T, expectedVolumeName v1.UniqueVolumeName, expectReportedInUse bool, dsw cache.DesiredStateOfWorld) {
|
||||||
volumesToMount := dsw.GetVolumesToMount()
|
volumesToMount := dsw.GetVolumesToMount()
|
||||||
for _, volume := range volumesToMount {
|
for _, volume := range volumesToMount {
|
||||||
|
|
|
@ -38,6 +38,7 @@ go_test(
|
||||||
srcs = ["reconciler_test.go"],
|
srcs = ["reconciler_test.go"],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/features:go_default_library",
|
||||||
"//pkg/kubelet/volumemanager/cache:go_default_library",
|
"//pkg/kubelet/volumemanager/cache:go_default_library",
|
||||||
"//pkg/util/mount:go_default_library",
|
"//pkg/util/mount:go_default_library",
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
|
|
|
@ -254,6 +254,22 @@ func (rc *reconciler) reconcile() {
|
||||||
glog.V(5).Infof(volumeToMount.GenerateMsgDetailed("operationExecutor.MountVolume started", remountingLogStr))
|
glog.V(5).Infof(volumeToMount.GenerateMsgDetailed("operationExecutor.MountVolume started", remountingLogStr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if cache.IsFSResizeRequiredError(err) &&
|
||||||
|
utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumesFSWithoutUnmounting) {
|
||||||
|
glog.V(4).Infof(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.ExpandVolumeFSWithoutUnmounting", ""))
|
||||||
|
err := rc.operationExecutor.ExpandVolumeFSWithoutUnmounting(
|
||||||
|
volumeToMount.VolumeToMount,
|
||||||
|
rc.actualStateOfWorld)
|
||||||
|
if err != nil &&
|
||||||
|
!nestedpendingoperations.IsAlreadyExists(err) &&
|
||||||
|
!exponentialbackoff.IsExponentialBackoff(err) {
|
||||||
|
// Ignore nestedpendingoperations.IsAlreadyExists and exponentialbackoff.IsExponentialBackoff errors, they are expected.
|
||||||
|
// Log all other errors.
|
||||||
|
glog.Errorf(volumeToMount.GenerateErrorDetailed("operationExecutor.ExpandVolumeFSWithoutUnmounting failed", err).Error())
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
glog.V(4).Infof(volumeToMount.GenerateMsgDetailed("operationExecutor.ExpandVolumeFSWithoutUnmounting started", ""))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
core "k8s.io/client-go/testing"
|
core "k8s.io/client-go/testing"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/volumemanager/cache"
|
"k8s.io/kubernetes/pkg/kubelet/volumemanager/cache"
|
||||||
"k8s.io/kubernetes/pkg/util/mount"
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
@ -965,6 +966,120 @@ func Test_GenerateUnmapDeviceFunc_Plugin_Not_Found(t *testing.T) {
|
||||||
utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
|
utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populates desiredStateOfWorld cache with one volume/pod.
|
||||||
|
// Enables controllerAttachDetachEnabled.
|
||||||
|
// Calls Run()
|
||||||
|
// Wait for volume mounted.
|
||||||
|
// Mark volume as fsResizeRequired in ASW.
|
||||||
|
// Verifies volume's fsResizeRequired flag is cleared later.
|
||||||
|
func Test_Run_Positive_VolumeFSResizeControllerAttachEnabled(t *testing.T) {
|
||||||
|
utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%s=true", features.ExpandPersistentVolumesFSWithoutUnmounting))
|
||||||
|
pv := &v1.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "pv",
|
||||||
|
UID: "pvuid",
|
||||||
|
},
|
||||||
|
Spec: v1.PersistentVolumeSpec{
|
||||||
|
ClaimRef: &v1.ObjectReference{Name: "pvc"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pvc := &v1.PersistentVolumeClaim{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "pvc",
|
||||||
|
UID: "pvcuid",
|
||||||
|
},
|
||||||
|
Spec: v1.PersistentVolumeClaimSpec{
|
||||||
|
VolumeName: "pv",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod := &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "pod1",
|
||||||
|
UID: "pod1uid",
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Volumes: []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: "volume-name",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
|
||||||
|
ClaimName: pvc.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
|
||||||
|
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
asw := cache.NewActualStateOfWorld(nodeName, volumePluginMgr)
|
||||||
|
kubeClient := createtestClientWithPVPVC(pv, pvc)
|
||||||
|
fakeRecorder := &record.FakeRecorder{}
|
||||||
|
fakeHandler := volumetesting.NewBlockVolumePathHandler()
|
||||||
|
oex := operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator(
|
||||||
|
kubeClient,
|
||||||
|
volumePluginMgr,
|
||||||
|
fakeRecorder,
|
||||||
|
false, /* checkNodeCapabilitiesBeforeMount */
|
||||||
|
fakeHandler))
|
||||||
|
|
||||||
|
reconciler := NewReconciler(
|
||||||
|
kubeClient,
|
||||||
|
true, /* controllerAttachDetachEnabled */
|
||||||
|
reconcilerLoopSleepDuration,
|
||||||
|
reconcilerSyncStatesSleepPeriod,
|
||||||
|
waitForAttachTimeout,
|
||||||
|
nodeName,
|
||||||
|
dsw,
|
||||||
|
asw,
|
||||||
|
hasAddedPods,
|
||||||
|
oex,
|
||||||
|
&mount.FakeMounter{},
|
||||||
|
volumePluginMgr,
|
||||||
|
kubeletPodsDir)
|
||||||
|
|
||||||
|
volumeSpec := &volume.Spec{PersistentVolume: pv}
|
||||||
|
podName := util.GetUniquePodName(pod)
|
||||||
|
volumeName, err := dsw.AddPodToVolume(
|
||||||
|
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
|
||||||
|
// Assert
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{volumeName})
|
||||||
|
|
||||||
|
// Start the reconciler to fill ASW.
|
||||||
|
stopChan, stoppedChan := make(chan struct{}), make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
reconciler.Run(stopChan)
|
||||||
|
close(stoppedChan)
|
||||||
|
}()
|
||||||
|
waitForMount(t, fakePlugin, volumeName, asw)
|
||||||
|
// Stop the reconciler.
|
||||||
|
close(stopChan)
|
||||||
|
<-stoppedChan
|
||||||
|
|
||||||
|
// Mark volume as fsResizeRequired.
|
||||||
|
asw.MarkFSResizeRequired(volumeName, podName)
|
||||||
|
_, _, podExistErr := asw.PodExistsInVolume(podName, volumeName)
|
||||||
|
if !cache.IsFSResizeRequiredError(podExistErr) {
|
||||||
|
t.Fatalf("Volume should be marked as fsResizeRequired, but receive unexpected error: %v", podExistErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the reconciler again, we hope reconciler will perform the
|
||||||
|
// resize operation and clear the fsResizeRequired flag for volume.
|
||||||
|
go reconciler.Run(wait.NeverStop)
|
||||||
|
|
||||||
|
waitErr := retryWithExponentialBackOff(500*time.Millisecond, func() (done bool, err error) {
|
||||||
|
mounted, _, err := asw.PodExistsInVolume(podName, volumeName)
|
||||||
|
return mounted && err == nil, nil
|
||||||
|
})
|
||||||
|
if waitErr != nil {
|
||||||
|
t.Fatal("Volume resize should succeeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func waitForMount(
|
func waitForMount(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
fakePlugin *volumetesting.FakeVolumePlugin,
|
fakePlugin *volumetesting.FakeVolumePlugin,
|
||||||
|
@ -1044,3 +1159,30 @@ func createTestClient() *fake.Clientset {
|
||||||
func runReconciler(reconciler Reconciler) {
|
func runReconciler(reconciler Reconciler) {
|
||||||
go reconciler.Run(wait.NeverStop)
|
go reconciler.Run(wait.NeverStop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createtestClientWithPVPVC(pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) *fake.Clientset {
|
||||||
|
fakeClient := &fake.Clientset{}
|
||||||
|
fakeClient.AddReactor("get", "nodes",
|
||||||
|
func(action core.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, &v1.Node{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: string(nodeName)},
|
||||||
|
Status: v1.NodeStatus{
|
||||||
|
VolumesAttached: []v1.AttachedVolume{
|
||||||
|
{
|
||||||
|
Name: "fake-plugin/pv",
|
||||||
|
DevicePath: "fake/path",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
fakeClient.AddReactor("get", "persistentvolumeclaims", func(action core.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, pvc, nil
|
||||||
|
})
|
||||||
|
fakeClient.AddReactor("get", "persistentvolumes", func(action core.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, pv, nil
|
||||||
|
})
|
||||||
|
fakeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, nil, fmt.Errorf("no reaction implemented for %s", action)
|
||||||
|
})
|
||||||
|
return fakeClient
|
||||||
|
}
|
||||||
|
|
|
@ -439,6 +439,15 @@ func (plugin *FakeVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]st
|
||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expandable volume support
|
||||||
|
func (plugin *FakeVolumePlugin) ExpandVolumeDevice(spec *Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) {
|
||||||
|
return resource.Quantity{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *FakeVolumePlugin) RequiresFSResize() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type FakeFileVolumePlugin struct {
|
type FakeFileVolumePlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,8 @@ type OperationExecutor interface {
|
||||||
IsOperationPending(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool
|
IsOperationPending(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool
|
||||||
// Expand Volume will grow size available to PVC
|
// Expand Volume will grow size available to PVC
|
||||||
ExpandVolume(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) error
|
ExpandVolume(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) error
|
||||||
|
// ExpandVolumeFSWithoutUnmounting will resize volume's file system to expected size without unmounting the volume.
|
||||||
|
ExpandVolumeFSWithoutUnmounting(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error
|
||||||
// ReconstructVolumeOperation construct a new volumeSpec and returns it created by plugin
|
// ReconstructVolumeOperation construct a new volumeSpec and returns it created by plugin
|
||||||
ReconstructVolumeOperation(volumeMode v1.PersistentVolumeMode, plugin volume.VolumePlugin, mapperPlugin volume.BlockVolumePlugin, uid types.UID, podName volumetypes.UniquePodName, volumeSpecName string, mountPath string, pluginName string) (*volume.Spec, error)
|
ReconstructVolumeOperation(volumeMode v1.PersistentVolumeMode, plugin volume.VolumePlugin, mapperPlugin volume.BlockVolumePlugin, uid types.UID, podName volumetypes.UniquePodName, volumeSpecName string, mountPath string, pluginName string) (*volume.Spec, error)
|
||||||
// CheckVolumeExistenceOperation checks volume existence
|
// CheckVolumeExistenceOperation checks volume existence
|
||||||
|
@ -173,6 +175,9 @@ type ActualStateOfWorldMounterUpdater interface {
|
||||||
|
|
||||||
// Marks the specified volume as having its global mount unmounted.
|
// Marks the specified volume as having its global mount unmounted.
|
||||||
MarkDeviceAsUnmounted(volumeName v1.UniqueVolumeName) error
|
MarkDeviceAsUnmounted(volumeName v1.UniqueVolumeName) error
|
||||||
|
|
||||||
|
// Marks the specified volume's file system resize request is finished.
|
||||||
|
MarkVolumeAsResized(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActualStateOfWorldAttacherUpdater defines a set of operations updating the
|
// ActualStateOfWorldAttacherUpdater defines a set of operations updating the
|
||||||
|
@ -817,6 +822,14 @@ func (oe *operationExecutor) ExpandVolume(pvcWithResizeRequest *expandcache.PVCW
|
||||||
return oe.pendingOperations.Run(uniqueVolumeKey, "", generatedOperations)
|
return oe.pendingOperations.Run(uniqueVolumeKey, "", generatedOperations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (oe *operationExecutor) ExpandVolumeFSWithoutUnmounting(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) error {
|
||||||
|
generatedOperations, err := oe.operationGenerator.GenerateExpandVolumeFSWithoutUnmountingFunc(volumeToMount, actualStateOfWorld)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return oe.pendingOperations.Run(volumeToMount.VolumeName, "", generatedOperations)
|
||||||
|
}
|
||||||
|
|
||||||
func (oe *operationExecutor) VerifyControllerAttachedVolume(
|
func (oe *operationExecutor) VerifyControllerAttachedVolume(
|
||||||
volumeToMount VolumeToMount,
|
volumeToMount VolumeToMount,
|
||||||
nodeName types.NodeName,
|
nodeName types.NodeName,
|
||||||
|
|
|
@ -438,6 +438,16 @@ func (fopg *fakeOperationGenerator) GenerateExpandVolumeFunc(pvcWithResizeReques
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fopg *fakeOperationGenerator) GenerateExpandVolumeFSWithoutUnmountingFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) {
|
||||||
|
opFunc := func() (error, error) {
|
||||||
|
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return volumetypes.GeneratedOperations{
|
||||||
|
OperationFunc: opFunc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (fopg *fakeOperationGenerator) GenerateBulkVolumeVerifyFunc(
|
func (fopg *fakeOperationGenerator) GenerateBulkVolumeVerifyFunc(
|
||||||
pluginNodeVolumes map[types.NodeName][]*volume.Spec,
|
pluginNodeVolumes map[types.NodeName][]*volume.Spec,
|
||||||
pluginNane string,
|
pluginNane string,
|
||||||
|
|
|
@ -121,6 +121,9 @@ type OperationGenerator interface {
|
||||||
map[*volume.Spec]v1.UniqueVolumeName, ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error)
|
map[*volume.Spec]v1.UniqueVolumeName, ActualStateOfWorldAttacherUpdater) (volumetypes.GeneratedOperations, error)
|
||||||
|
|
||||||
GenerateExpandVolumeFunc(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) (volumetypes.GeneratedOperations, error)
|
GenerateExpandVolumeFunc(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) (volumetypes.GeneratedOperations, error)
|
||||||
|
|
||||||
|
// Generates the volume file system resize function, which can resize volume's file system to expected size without unmounting the volume.
|
||||||
|
GenerateExpandVolumeFSWithoutUnmountingFunc(volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (og *operationGenerator) GenerateVolumesAreAttachedFunc(
|
func (og *operationGenerator) GenerateVolumesAreAttachedFunc(
|
||||||
|
@ -1306,6 +1309,62 @@ func (og *operationGenerator) GenerateExpandVolumeFunc(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (og *operationGenerator) GenerateExpandVolumeFSWithoutUnmountingFunc(
|
||||||
|
volumeToMount VolumeToMount,
|
||||||
|
actualStateOfWorld ActualStateOfWorldMounterUpdater) (volumetypes.GeneratedOperations, error) {
|
||||||
|
volumePlugin, err :=
|
||||||
|
og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)
|
||||||
|
if err != nil || volumePlugin == nil {
|
||||||
|
return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("VolumeFSResize.FindPluginBySpec failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachableVolumePlugin, err :=
|
||||||
|
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec)
|
||||||
|
if err != nil || attachableVolumePlugin == nil {
|
||||||
|
if attachableVolumePlugin == nil {
|
||||||
|
err = fmt.Errorf("AttachableVolumePlugin is nil")
|
||||||
|
}
|
||||||
|
return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("VolumeFSResize.FindAttachablePluginBySpec failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeAttacher, err := attachableVolumePlugin.NewAttacher()
|
||||||
|
if err != nil || volumeAttacher == nil {
|
||||||
|
if volumeAttacher == nil {
|
||||||
|
err = fmt.Errorf("VolumeAttacher is nil")
|
||||||
|
}
|
||||||
|
return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("VolumeFSResize.NewAttacher failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceMountPath, err := volumeAttacher.GetDeviceMountPath(volumeToMount.VolumeSpec)
|
||||||
|
if err != nil {
|
||||||
|
return volumetypes.GeneratedOperations{}, volumeToMount.GenerateErrorDetailed("VolumeFSResize.GetDeviceMountPath failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fsResizeFunc := func() (error, error) {
|
||||||
|
resizeSimpleError, resizeDetailedError := og.resizeFileSystem(volumeToMount, volumeToMount.DevicePath, deviceMountPath, volumePlugin.GetPluginName())
|
||||||
|
if resizeSimpleError != nil || resizeDetailedError != nil {
|
||||||
|
return resizeSimpleError, resizeDetailedError
|
||||||
|
}
|
||||||
|
markFSResizedErr := actualStateOfWorld.MarkVolumeAsResized(volumeToMount.PodName, volumeToMount.VolumeName)
|
||||||
|
if markFSResizedErr != nil {
|
||||||
|
// On failure, return error. Caller will log and retry.
|
||||||
|
return volumeToMount.GenerateError("VolumeFSResize.MarkVolumeAsResized failed", markFSResizedErr)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
eventRecorderFunc := func(err *error) {
|
||||||
|
if *err != nil {
|
||||||
|
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.VolumeResizeFailed, (*err).Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumetypes.GeneratedOperations{
|
||||||
|
OperationFunc: fsResizeFunc,
|
||||||
|
EventRecorderFunc: eventRecorderFunc,
|
||||||
|
CompleteFunc: util.OperationCompleteHook(volumePlugin.GetPluginName(), "volume_fs_resize"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func checkMountOptionSupport(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error {
|
func checkMountOptionSupport(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error {
|
||||||
mountOptions := util.MountOptionFromSpec(volumeToMount.VolumeSpec)
|
mountOptions := util.MountOptionFromSpec(volumeToMount.VolumeSpec)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue