mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-02 16:29:21 +00:00
Merge pull request #25457 from saad-ali/expectedStateOfWorldDataStructure
Automatic merge from submit-queue Attach Detach Controller Business Logic This PR adds the meat of the attach/detach controller proposed in #20262. The PR splits the in-memory cache into a desired and actual state of the world.
This commit is contained in:
commit
bda0dc88aa
@ -197,9 +197,13 @@ func Run(s *options.CMServer) error {
|
|||||||
func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig *restclient.Config, stop <-chan struct{}) error {
|
func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig *restclient.Config, stop <-chan struct{}) error {
|
||||||
podInformer := informers.CreateSharedPodIndexInformer(clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "pod-informer")), ResyncPeriod(s)())
|
podInformer := informers.CreateSharedPodIndexInformer(clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "pod-informer")), ResyncPeriod(s)())
|
||||||
nodeInformer := informers.CreateSharedNodeIndexInformer(clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "node-informer")), ResyncPeriod(s)())
|
nodeInformer := informers.CreateSharedNodeIndexInformer(clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "node-informer")), ResyncPeriod(s)())
|
||||||
|
pvcInformer := informers.CreateSharedPVCIndexInformer(clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "pvc-informer")), ResyncPeriod(s)())
|
||||||
|
pvInformer := informers.CreateSharedPVIndexInformer(clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "pv-informer")), ResyncPeriod(s)())
|
||||||
informers := map[reflect.Type]framework.SharedIndexInformer{}
|
informers := map[reflect.Type]framework.SharedIndexInformer{}
|
||||||
informers[reflect.TypeOf(&api.Pod{})] = podInformer
|
informers[reflect.TypeOf(&api.Pod{})] = podInformer
|
||||||
informers[reflect.TypeOf(&api.Node{})] = nodeInformer
|
informers[reflect.TypeOf(&api.Node{})] = nodeInformer
|
||||||
|
informers[reflect.TypeOf(&api.PersistentVolumeClaim{})] = pvcInformer
|
||||||
|
informers[reflect.TypeOf(&api.PersistentVolume{})] = pvInformer
|
||||||
|
|
||||||
go endpointcontroller.NewEndpointController(podInformer, clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "endpoint-controller"))).
|
go endpointcontroller.NewEndpointController(podInformer, clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "endpoint-controller"))).
|
||||||
Run(int(s.ConcurrentEndpointSyncs), wait.NeverStop)
|
Run(int(s.ConcurrentEndpointSyncs), wait.NeverStop)
|
||||||
@ -391,9 +395,21 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig
|
|||||||
volumeController.Run()
|
volumeController.Run()
|
||||||
time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter))
|
time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter))
|
||||||
|
|
||||||
go volume.NewAttachDetachController(clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "attachdetach-controller")), podInformer, nodeInformer, ResyncPeriod(s)()).
|
attachDetachController, attachDetachControllerErr :=
|
||||||
Run(wait.NeverStop)
|
volume.NewAttachDetachController(
|
||||||
time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter))
|
clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "attachdetach-controller")),
|
||||||
|
podInformer,
|
||||||
|
nodeInformer,
|
||||||
|
pvcInformer,
|
||||||
|
pvInformer,
|
||||||
|
cloud,
|
||||||
|
ProbeAttachableVolumePlugins(s.VolumeConfiguration))
|
||||||
|
if attachDetachControllerErr != nil {
|
||||||
|
glog.Fatalf("Failed to start attach/detach controller: %v", attachDetachControllerErr)
|
||||||
|
} else {
|
||||||
|
go attachDetachController.Run(wait.NeverStop)
|
||||||
|
time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter))
|
||||||
|
}
|
||||||
|
|
||||||
var rootCA []byte
|
var rootCA []byte
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ func NewCMServer() *CMServer {
|
|||||||
MinimumTimeoutHostPath: 60,
|
MinimumTimeoutHostPath: 60,
|
||||||
IncrementTimeoutHostPath: 30,
|
IncrementTimeoutHostPath: 30,
|
||||||
},
|
},
|
||||||
|
FlexVolumePluginDir: "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/",
|
||||||
},
|
},
|
||||||
ContentType: "application/vnd.kubernetes.protobuf",
|
ContentType: "application/vnd.kubernetes.protobuf",
|
||||||
KubeAPIQPS: 20.0,
|
KubeAPIQPS: 20.0,
|
||||||
@ -122,6 +123,7 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet) {
|
|||||||
fs.Int32Var(&s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath, "pv-recycler-minimum-timeout-hostpath", s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath, "The minimum ActiveDeadlineSeconds to use for a HostPath Recycler pod. This is for development and testing only and will not work in a multi-node cluster.")
|
fs.Int32Var(&s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath, "pv-recycler-minimum-timeout-hostpath", s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath, "The minimum ActiveDeadlineSeconds to use for a HostPath Recycler pod. This is for development and testing only and will not work in a multi-node cluster.")
|
||||||
fs.Int32Var(&s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath, "pv-recycler-timeout-increment-hostpath", s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath, "the increment of time added per Gi to ActiveDeadlineSeconds for a HostPath scrubber pod. This is for development and testing only and will not work in a multi-node cluster.")
|
fs.Int32Var(&s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath, "pv-recycler-timeout-increment-hostpath", s.VolumeConfiguration.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath, "the increment of time added per Gi to ActiveDeadlineSeconds for a HostPath scrubber pod. This is for development and testing only and will not work in a multi-node cluster.")
|
||||||
fs.BoolVar(&s.VolumeConfiguration.EnableHostPathProvisioning, "enable-hostpath-provisioner", s.VolumeConfiguration.EnableHostPathProvisioning, "Enable HostPath PV provisioning when running without a cloud provider. This allows testing and development of provisioning features. HostPath provisioning is not supported in any way, won't work in a multi-node cluster, and should not be used for anything other than testing or development.")
|
fs.BoolVar(&s.VolumeConfiguration.EnableHostPathProvisioning, "enable-hostpath-provisioner", s.VolumeConfiguration.EnableHostPathProvisioning, "Enable HostPath PV provisioning when running without a cloud provider. This allows testing and development of provisioning features. HostPath provisioning is not supported in any way, won't work in a multi-node cluster, and should not be used for anything other than testing or development.")
|
||||||
|
fs.StringVar(&s.VolumeConfiguration.FlexVolumePluginDir, "flex-volume-plugin-dir", s.VolumeConfiguration.FlexVolumePluginDir, "Full path of the directory in which the flex volume plugin should search for additional third party volume plugins.")
|
||||||
fs.Int32Var(&s.TerminatedPodGCThreshold, "terminated-pod-gc-threshold", s.TerminatedPodGCThreshold, "Number of terminated pods that can exist before the terminated pod garbage collector starts deleting terminated pods. If <= 0, the terminated pod garbage collector is disabled.")
|
fs.Int32Var(&s.TerminatedPodGCThreshold, "terminated-pod-gc-threshold", s.TerminatedPodGCThreshold, "Number of terminated pods that can exist before the terminated pod garbage collector starts deleting terminated pods. If <= 0, the terminated pod garbage collector is disabled.")
|
||||||
fs.DurationVar(&s.HorizontalPodAutoscalerSyncPeriod.Duration, "horizontal-pod-autoscaler-sync-period", s.HorizontalPodAutoscalerSyncPeriod.Duration, "The period for syncing the number of pods in horizontal pod autoscaler.")
|
fs.DurationVar(&s.HorizontalPodAutoscalerSyncPeriod.Duration, "horizontal-pod-autoscaler-sync-period", s.HorizontalPodAutoscalerSyncPeriod.Duration, "The period for syncing the number of pods in horizontal pod autoscaler.")
|
||||||
fs.DurationVar(&s.DeploymentControllerSyncPeriod.Duration, "deployment-controller-sync-period", s.DeploymentControllerSyncPeriod.Duration, "Period for syncing the deployments.")
|
fs.DurationVar(&s.DeploymentControllerSyncPeriod.Duration, "deployment-controller-sync-period", s.DeploymentControllerSyncPeriod.Duration, "Period for syncing the deployments.")
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
"k8s.io/kubernetes/pkg/volume/aws_ebs"
|
"k8s.io/kubernetes/pkg/volume/aws_ebs"
|
||||||
"k8s.io/kubernetes/pkg/volume/cinder"
|
"k8s.io/kubernetes/pkg/volume/cinder"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/flexvolume"
|
||||||
"k8s.io/kubernetes/pkg/volume/gce_pd"
|
"k8s.io/kubernetes/pkg/volume/gce_pd"
|
||||||
"k8s.io/kubernetes/pkg/volume/host_path"
|
"k8s.io/kubernetes/pkg/volume/host_path"
|
||||||
"k8s.io/kubernetes/pkg/volume/nfs"
|
"k8s.io/kubernetes/pkg/volume/nfs"
|
||||||
@ -45,6 +46,22 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ProbeAttachableVolumePlugins collects all volume plugins for the attach/
|
||||||
|
// detach controller. VolumeConfiguration is used ot get FlexVolumePluginDir
|
||||||
|
// which specifies the directory to search for additional third party volume
|
||||||
|
// plugins.
|
||||||
|
// The list of plugins is manually compiled. This code and the plugin
|
||||||
|
// initialization code for kubelet really, really need a through refactor.
|
||||||
|
func ProbeAttachableVolumePlugins(config componentconfig.VolumeConfiguration) []volume.VolumePlugin {
|
||||||
|
allPlugins := []volume.VolumePlugin{}
|
||||||
|
|
||||||
|
allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...)
|
||||||
|
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
|
||||||
|
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
|
||||||
|
allPlugins = append(allPlugins, flexvolume.ProbeVolumePlugins(config.FlexVolumePluginDir)...)
|
||||||
|
return allPlugins
|
||||||
|
}
|
||||||
|
|
||||||
// ProbeRecyclableVolumePlugins collects all persistent volume plugins into an easy to use list.
|
// ProbeRecyclableVolumePlugins collects all persistent volume plugins into an easy to use list.
|
||||||
func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) []volume.VolumePlugin {
|
func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) []volume.VolumePlugin {
|
||||||
allPlugins := []volume.VolumePlugin{}
|
allPlugins := []volume.VolumePlugin{}
|
||||||
|
@ -73,6 +73,7 @@ kube-controller-manager
|
|||||||
--deleting-pods-qps=0.1: Number of nodes per second on which pods are deleted in case of node failure.
|
--deleting-pods-qps=0.1: Number of nodes per second on which pods are deleted in case of node failure.
|
||||||
--deployment-controller-sync-period=30s: Period for syncing the deployments.
|
--deployment-controller-sync-period=30s: Period for syncing the deployments.
|
||||||
--enable-hostpath-provisioner[=false]: Enable HostPath PV provisioning when running without a cloud provider. This allows testing and development of provisioning features. HostPath provisioning is not supported in any way, won't work in a multi-node cluster, and should not be used for anything other than testing or development.
|
--enable-hostpath-provisioner[=false]: Enable HostPath PV provisioning when running without a cloud provider. This allows testing and development of provisioning features. HostPath provisioning is not supported in any way, won't work in a multi-node cluster, and should not be used for anything other than testing or development.
|
||||||
|
--flex-volume-plugin-dir="/usr/libexec/kubernetes/kubelet-plugins/volume/exec/": Full path of the directory in which the flex volume plugin should search for additional third party volume plugins.
|
||||||
--google-json-key="": The Google Cloud Platform Service Account JSON Key to use for authentication.
|
--google-json-key="": The Google Cloud Platform Service Account JSON Key to use for authentication.
|
||||||
--horizontal-pod-autoscaler-sync-period=30s: The period for syncing the number of pods in horizontal pod autoscaler.
|
--horizontal-pod-autoscaler-sync-period=30s: The period for syncing the number of pods in horizontal pod autoscaler.
|
||||||
--kube-api-burst=30: Burst to use while talking with kubernetes apiserver
|
--kube-api-burst=30: Burst to use while talking with kubernetes apiserver
|
||||||
|
@ -146,6 +146,7 @@ federated-api-qps
|
|||||||
file-check-frequency
|
file-check-frequency
|
||||||
file-suffix
|
file-suffix
|
||||||
file_content_in_loop
|
file_content_in_loop
|
||||||
|
flex-volume-plugin-dir
|
||||||
forward-services
|
forward-services
|
||||||
framework-name
|
framework-name
|
||||||
framework-store-uri
|
framework-store-uri
|
||||||
|
@ -359,5 +359,6 @@ func DeepCopy_componentconfig_VolumeConfiguration(in VolumeConfiguration, out *V
|
|||||||
if err := DeepCopy_componentconfig_PersistentVolumeRecyclerConfiguration(in.PersistentVolumeRecyclerConfiguration, &out.PersistentVolumeRecyclerConfiguration, c); err != nil {
|
if err := DeepCopy_componentconfig_PersistentVolumeRecyclerConfiguration(in.PersistentVolumeRecyclerConfiguration, &out.PersistentVolumeRecyclerConfiguration, c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
out.FlexVolumePluginDir = in.FlexVolumePluginDir
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -8709,14 +8709,14 @@ func (x *VolumeConfiguration) CodecEncodeSelf(e *codec1978.Encoder) {
|
|||||||
} else {
|
} else {
|
||||||
yysep2 := !z.EncBinary()
|
yysep2 := !z.EncBinary()
|
||||||
yy2arr2 := z.EncBasicHandle().StructToArray
|
yy2arr2 := z.EncBasicHandle().StructToArray
|
||||||
var yyq2 [2]bool
|
var yyq2 [3]bool
|
||||||
_, _, _ = yysep2, yyq2, yy2arr2
|
_, _, _ = yysep2, yyq2, yy2arr2
|
||||||
const yyr2 bool = false
|
const yyr2 bool = false
|
||||||
var yynn2 int
|
var yynn2 int
|
||||||
if yyr2 || yy2arr2 {
|
if yyr2 || yy2arr2 {
|
||||||
r.EncodeArrayStart(2)
|
r.EncodeArrayStart(3)
|
||||||
} else {
|
} else {
|
||||||
yynn2 = 2
|
yynn2 = 3
|
||||||
for _, b := range yyq2 {
|
for _, b := range yyq2 {
|
||||||
if b {
|
if b {
|
||||||
yynn2++
|
yynn2++
|
||||||
@ -8755,6 +8755,25 @@ func (x *VolumeConfiguration) CodecEncodeSelf(e *codec1978.Encoder) {
|
|||||||
yy9 := &x.PersistentVolumeRecyclerConfiguration
|
yy9 := &x.PersistentVolumeRecyclerConfiguration
|
||||||
yy9.CodecEncodeSelf(e)
|
yy9.CodecEncodeSelf(e)
|
||||||
}
|
}
|
||||||
|
if yyr2 || yy2arr2 {
|
||||||
|
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
|
||||||
|
yym12 := z.EncBinary()
|
||||||
|
_ = yym12
|
||||||
|
if false {
|
||||||
|
} else {
|
||||||
|
r.EncodeString(codecSelferC_UTF81234, string(x.FlexVolumePluginDir))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
z.EncSendContainerState(codecSelfer_containerMapKey1234)
|
||||||
|
r.EncodeString(codecSelferC_UTF81234, string("flexVolumePluginDir"))
|
||||||
|
z.EncSendContainerState(codecSelfer_containerMapValue1234)
|
||||||
|
yym13 := z.EncBinary()
|
||||||
|
_ = yym13
|
||||||
|
if false {
|
||||||
|
} else {
|
||||||
|
r.EncodeString(codecSelferC_UTF81234, string(x.FlexVolumePluginDir))
|
||||||
|
}
|
||||||
|
}
|
||||||
if yyr2 || yy2arr2 {
|
if yyr2 || yy2arr2 {
|
||||||
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
|
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
|
||||||
} else {
|
} else {
|
||||||
@ -8829,6 +8848,12 @@ func (x *VolumeConfiguration) codecDecodeSelfFromMap(l int, d *codec1978.Decoder
|
|||||||
yyv5 := &x.PersistentVolumeRecyclerConfiguration
|
yyv5 := &x.PersistentVolumeRecyclerConfiguration
|
||||||
yyv5.CodecDecodeSelf(d)
|
yyv5.CodecDecodeSelf(d)
|
||||||
}
|
}
|
||||||
|
case "flexVolumePluginDir":
|
||||||
|
if r.TryDecodeAsNil() {
|
||||||
|
x.FlexVolumePluginDir = ""
|
||||||
|
} else {
|
||||||
|
x.FlexVolumePluginDir = string(r.DecodeString())
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
z.DecStructFieldNotFound(-1, yys3)
|
z.DecStructFieldNotFound(-1, yys3)
|
||||||
} // end switch yys3
|
} // end switch yys3
|
||||||
@ -8840,16 +8865,16 @@ func (x *VolumeConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Decod
|
|||||||
var h codecSelfer1234
|
var h codecSelfer1234
|
||||||
z, r := codec1978.GenHelperDecoder(d)
|
z, r := codec1978.GenHelperDecoder(d)
|
||||||
_, _, _ = h, z, r
|
_, _, _ = h, z, r
|
||||||
var yyj6 int
|
var yyj7 int
|
||||||
var yyb6 bool
|
var yyb7 bool
|
||||||
var yyhl6 bool = l >= 0
|
var yyhl7 bool = l >= 0
|
||||||
yyj6++
|
yyj7++
|
||||||
if yyhl6 {
|
if yyhl7 {
|
||||||
yyb6 = yyj6 > l
|
yyb7 = yyj7 > l
|
||||||
} else {
|
} else {
|
||||||
yyb6 = r.CheckBreak()
|
yyb7 = r.CheckBreak()
|
||||||
}
|
}
|
||||||
if yyb6 {
|
if yyb7 {
|
||||||
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
|
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -8859,13 +8884,13 @@ func (x *VolumeConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Decod
|
|||||||
} else {
|
} else {
|
||||||
x.EnableHostPathProvisioning = bool(r.DecodeBool())
|
x.EnableHostPathProvisioning = bool(r.DecodeBool())
|
||||||
}
|
}
|
||||||
yyj6++
|
yyj7++
|
||||||
if yyhl6 {
|
if yyhl7 {
|
||||||
yyb6 = yyj6 > l
|
yyb7 = yyj7 > l
|
||||||
} else {
|
} else {
|
||||||
yyb6 = r.CheckBreak()
|
yyb7 = r.CheckBreak()
|
||||||
}
|
}
|
||||||
if yyb6 {
|
if yyb7 {
|
||||||
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
|
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -8873,21 +8898,37 @@ func (x *VolumeConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Decod
|
|||||||
if r.TryDecodeAsNil() {
|
if r.TryDecodeAsNil() {
|
||||||
x.PersistentVolumeRecyclerConfiguration = PersistentVolumeRecyclerConfiguration{}
|
x.PersistentVolumeRecyclerConfiguration = PersistentVolumeRecyclerConfiguration{}
|
||||||
} else {
|
} else {
|
||||||
yyv8 := &x.PersistentVolumeRecyclerConfiguration
|
yyv9 := &x.PersistentVolumeRecyclerConfiguration
|
||||||
yyv8.CodecDecodeSelf(d)
|
yyv9.CodecDecodeSelf(d)
|
||||||
|
}
|
||||||
|
yyj7++
|
||||||
|
if yyhl7 {
|
||||||
|
yyb7 = yyj7 > l
|
||||||
|
} else {
|
||||||
|
yyb7 = r.CheckBreak()
|
||||||
|
}
|
||||||
|
if yyb7 {
|
||||||
|
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
|
||||||
|
if r.TryDecodeAsNil() {
|
||||||
|
x.FlexVolumePluginDir = ""
|
||||||
|
} else {
|
||||||
|
x.FlexVolumePluginDir = string(r.DecodeString())
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
yyj6++
|
yyj7++
|
||||||
if yyhl6 {
|
if yyhl7 {
|
||||||
yyb6 = yyj6 > l
|
yyb7 = yyj7 > l
|
||||||
} else {
|
} else {
|
||||||
yyb6 = r.CheckBreak()
|
yyb7 = r.CheckBreak()
|
||||||
}
|
}
|
||||||
if yyb6 {
|
if yyb7 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
|
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
|
||||||
z.DecStructFieldNotFound(yyj6-1, "")
|
z.DecStructFieldNotFound(yyj7-1, "")
|
||||||
}
|
}
|
||||||
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
|
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
|
||||||
}
|
}
|
||||||
|
@ -564,6 +564,9 @@ type VolumeConfiguration struct {
|
|||||||
EnableHostPathProvisioning bool `json:"enableHostPathProvisioning"`
|
EnableHostPathProvisioning bool `json:"enableHostPathProvisioning"`
|
||||||
// persistentVolumeRecyclerConfiguration holds configuration for persistent volume plugins.
|
// persistentVolumeRecyclerConfiguration holds configuration for persistent volume plugins.
|
||||||
PersistentVolumeRecyclerConfiguration PersistentVolumeRecyclerConfiguration `json:"persitentVolumeRecyclerConfiguration"`
|
PersistentVolumeRecyclerConfiguration PersistentVolumeRecyclerConfiguration `json:"persitentVolumeRecyclerConfiguration"`
|
||||||
|
// volumePluginDir is the full path of the directory in which the flex
|
||||||
|
// volume plugin should search for additional third party volume plugins
|
||||||
|
FlexVolumePluginDir string `json:"flexVolumePluginDir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PersistentVolumeRecyclerConfiguration struct {
|
type PersistentVolumeRecyclerConfiguration struct {
|
||||||
|
@ -82,3 +82,39 @@ func CreateSharedNodeIndexInformer(client clientset.Interface, resyncPeriod time
|
|||||||
|
|
||||||
return sharedIndexInformer
|
return sharedIndexInformer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSharedPVCIndexInformer returns a SharedIndexInformer that lists and watches all PVCs
|
||||||
|
func CreateSharedPVCIndexInformer(client clientset.Interface, resyncPeriod time.Duration) framework.SharedIndexInformer {
|
||||||
|
sharedIndexInformer := framework.NewSharedIndexInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
|
||||||
|
return client.Core().PersistentVolumeClaims(api.NamespaceAll).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
|
||||||
|
return client.Core().PersistentVolumeClaims(api.NamespaceAll).Watch(options)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&api.PersistentVolumeClaim{},
|
||||||
|
resyncPeriod,
|
||||||
|
cache.Indexers{})
|
||||||
|
|
||||||
|
return sharedIndexInformer
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSharedPVIndexInformer returns a SharedIndexInformer that lists and watches all PVs
|
||||||
|
func CreateSharedPVIndexInformer(client clientset.Interface, resyncPeriod time.Duration) framework.SharedIndexInformer {
|
||||||
|
sharedIndexInformer := framework.NewSharedIndexInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
|
||||||
|
return client.Core().PersistentVolumes().List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
|
||||||
|
return client.Core().PersistentVolumes().Watch(options)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&api.PersistentVolume{},
|
||||||
|
resyncPeriod,
|
||||||
|
cache.Indexers{})
|
||||||
|
|
||||||
|
return sharedIndexInformer
|
||||||
|
}
|
||||||
|
@ -19,13 +19,45 @@ limitations under the License.
|
|||||||
package volume
|
package volume
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||||
"k8s.io/kubernetes/pkg/controller/framework"
|
"k8s.io/kubernetes/pkg/controller/framework"
|
||||||
"k8s.io/kubernetes/pkg/controller/framework/informers"
|
"k8s.io/kubernetes/pkg/controller/volume/attacherdetacher"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/volume/cache"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/volume/reconciler"
|
||||||
|
"k8s.io/kubernetes/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/util/io"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
"k8s.io/kubernetes/pkg/util/runtime"
|
"k8s.io/kubernetes/pkg/util/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ControllerManagedAnnotation is the key of the annotation on Node objects
|
||||||
|
// that indicates attach/detach operations for the node should be managed
|
||||||
|
// by the attach/detach controller
|
||||||
|
ControllerManagedAnnotation string = "volumes.kubernetes.io/controller-managed-attach"
|
||||||
|
|
||||||
|
// SafeToDetachAnnotation is the annotation added to the Node object by
|
||||||
|
// kubelet in the format "volumes.kubernetes.io/safetodetach/{volumename}"
|
||||||
|
// to indicate the volume has been unmounted and is safe to detach.
|
||||||
|
SafeToDetachAnnotation string = "volumes.kubernetes.io/safetodetach-"
|
||||||
|
|
||||||
|
// loopPeriod is the ammount of time the reconciler loop waits between
|
||||||
|
// successive executions
|
||||||
|
reconcilerLoopPeriod time.Duration = 100 * time.Millisecond
|
||||||
|
|
||||||
|
// reconcilerMaxSafeToDetachDuration is the maximum amount of time the
|
||||||
|
// attach detach controller will wait for a volume to be safely detached
|
||||||
|
// from its node. Once this time has expired, the controller will assume the
|
||||||
|
// node or kubelet are unresponsive and will detach the volume anyway.
|
||||||
|
reconcilerMaxSafeToDetachDuration time.Duration = 10 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
// AttachDetachController defines the operations supported by this controller.
|
// AttachDetachController defines the operations supported by this controller.
|
||||||
@ -33,49 +65,34 @@ type AttachDetachController interface {
|
|||||||
Run(stopCh <-chan struct{})
|
Run(stopCh <-chan struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type attachDetachController struct {
|
|
||||||
// internalPodInformer is the shared pod informer used to fetch and store
|
|
||||||
// pod objects from the API server. It is shared with other controllers and
|
|
||||||
// therefore the pod objects in its store should be treated as immutable.
|
|
||||||
internalPodInformer framework.SharedInformer
|
|
||||||
|
|
||||||
// selfCreatedPodInformer is true if the internalPodInformer was created
|
|
||||||
// during initialization, not passed in.
|
|
||||||
selfCreatedPodInformer bool
|
|
||||||
|
|
||||||
// internalNodeInformer is the shared node informer used to fetch and store
|
|
||||||
// node objects from the API server. It is shared with other controllers
|
|
||||||
// and therefore the node objects in its store should be treated as
|
|
||||||
// immutable.
|
|
||||||
internalNodeInformer framework.SharedInformer
|
|
||||||
|
|
||||||
// selfCreatedNodeInformer is true if the internalNodeInformer was created
|
|
||||||
// during initialization, not passed in.
|
|
||||||
selfCreatedNodeInformer bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAttachDetachController returns a new instance of AttachDetachController.
|
// NewAttachDetachController returns a new instance of AttachDetachController.
|
||||||
func NewAttachDetachController(
|
func NewAttachDetachController(
|
||||||
kubeClient internalclientset.Interface,
|
kubeClient internalclientset.Interface,
|
||||||
podInformer framework.SharedInformer,
|
podInformer framework.SharedInformer,
|
||||||
nodeInformer framework.SharedInformer,
|
nodeInformer framework.SharedInformer,
|
||||||
resyncPeriod time.Duration) AttachDetachController {
|
pvcInformer framework.SharedInformer,
|
||||||
selfCreatedPodInformer := false
|
pvInformer framework.SharedInformer,
|
||||||
selfCreatedNodeInformer := false
|
cloud cloudprovider.Interface,
|
||||||
if podInformer == nil {
|
plugins []volume.VolumePlugin) (AttachDetachController, error) {
|
||||||
podInformer = informers.CreateSharedPodInformer(kubeClient, resyncPeriod)
|
// TODO: The default resyncPeriod for shared informers is 12 hours, this is
|
||||||
selfCreatedPodInformer = true
|
// unacceptable for the attach/detach controller. For example, if a pod is
|
||||||
}
|
// skipped because the node it is scheduled to didn't set its annotation in
|
||||||
if nodeInformer == nil {
|
// time, we don't want to have to wait 12hrs before processing the pod
|
||||||
nodeInformer = informers.CreateSharedNodeIndexInformer(kubeClient, resyncPeriod)
|
// again.
|
||||||
selfCreatedNodeInformer = true
|
// Luckily https://github.com/kubernetes/kubernetes/issues/23394 is being
|
||||||
}
|
// worked on and will split resync in to resync and relist. Once that
|
||||||
|
// happens the resync period can be set to something much faster (30
|
||||||
|
// seconds).
|
||||||
|
// If that issue is not resolved in time, then this controller will have to
|
||||||
|
// consider some unappealing alternate options: use a non-shared informer
|
||||||
|
// and set a faster resync period even if it causes relist, or requeue
|
||||||
|
// dropped pods so they are continuously processed until it is accepted or
|
||||||
|
// deleted (probably can't do this with sharedInformer), etc.
|
||||||
adc := &attachDetachController{
|
adc := &attachDetachController{
|
||||||
internalPodInformer: podInformer,
|
kubeClient: kubeClient,
|
||||||
selfCreatedPodInformer: selfCreatedPodInformer,
|
pvcInformer: pvcInformer,
|
||||||
internalNodeInformer: nodeInformer,
|
pvInformer: pvInformer,
|
||||||
selfCreatedNodeInformer: selfCreatedNodeInformer,
|
cloud: cloud,
|
||||||
}
|
}
|
||||||
|
|
||||||
podInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{
|
podInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{
|
||||||
@ -90,46 +107,426 @@ func NewAttachDetachController(
|
|||||||
DeleteFunc: adc.nodeDelete,
|
DeleteFunc: adc.nodeDelete,
|
||||||
})
|
})
|
||||||
|
|
||||||
return adc
|
if err := adc.volumePluginMgr.InitPlugins(plugins, adc); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not initialize volume plugins for Attach/Detach Controller: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
adc.desiredStateOfWorld = cache.NewDesiredStateOfWorld(&adc.volumePluginMgr)
|
||||||
|
adc.actualStateOfWorld = cache.NewActualStateOfWorld(&adc.volumePluginMgr)
|
||||||
|
adc.attacherDetacher = attacherdetacher.NewAttacherDetacher(&adc.volumePluginMgr)
|
||||||
|
adc.reconciler = reconciler.NewReconciler(
|
||||||
|
reconcilerLoopPeriod,
|
||||||
|
reconcilerMaxSafeToDetachDuration,
|
||||||
|
adc.desiredStateOfWorld,
|
||||||
|
adc.actualStateOfWorld,
|
||||||
|
adc.attacherDetacher)
|
||||||
|
|
||||||
|
return adc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type attachDetachController struct {
|
||||||
|
// kubeClient is the kube API client used by volumehost to communicate with
|
||||||
|
// the API server.
|
||||||
|
kubeClient internalclientset.Interface
|
||||||
|
|
||||||
|
// pvcInformer is the shared PVC informer used to fetch and store PVC
|
||||||
|
// objects from the API server. It is shared with other controllers and
|
||||||
|
// therefore the PVC objects in its store should be treated as immutable.
|
||||||
|
pvcInformer framework.SharedInformer
|
||||||
|
|
||||||
|
// pvInformer is the shared PV informer used to fetch and store PV objects
|
||||||
|
// from the API server. It is shared with other controllers and therefore
|
||||||
|
// the PV objects in its store should be treated as immutable.
|
||||||
|
pvInformer framework.SharedInformer
|
||||||
|
|
||||||
|
// cloud provider used by volume host
|
||||||
|
cloud cloudprovider.Interface
|
||||||
|
|
||||||
|
// volumePluginMgr used to initialize and fetch volume plugins
|
||||||
|
volumePluginMgr volume.VolumePluginMgr
|
||||||
|
|
||||||
|
// desiredStateOfWorld is a data structure containing the desired state of
|
||||||
|
// the world according to this controller: i.e. what nodes the controller
|
||||||
|
// is managing, what volumes it wants be attached to these nodes, and which
|
||||||
|
// pods are scheduled to those nodes referencing the volumes.
|
||||||
|
// The data structure is populated by the controller using a stream of node
|
||||||
|
// and pod API server objects fetched by the informers.
|
||||||
|
desiredStateOfWorld cache.DesiredStateOfWorld
|
||||||
|
|
||||||
|
// actualStateOfWorld is a data structure containing the actual state of
|
||||||
|
// the world according to this controller: i.e. which volumes are attached
|
||||||
|
// to which nodes.
|
||||||
|
// The data structure is populated upon successful completion of attach and
|
||||||
|
// detach actions triggered by the controller and a periodic sync with
|
||||||
|
// storage providers for the "true" state of the world.
|
||||||
|
actualStateOfWorld cache.ActualStateOfWorld
|
||||||
|
|
||||||
|
// attacherDetacher is used to start asynchronous attach and operations
|
||||||
|
attacherDetacher attacherdetacher.AttacherDetacher
|
||||||
|
|
||||||
|
// reconciler is used to run an asynchronous periodic loop to reconcile the
|
||||||
|
// desiredStateOfWorld with the actualStateOfWorld by triggering attach
|
||||||
|
// detach operations using the attacherDetacher.
|
||||||
|
reconciler reconciler.Reconciler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) Run(stopCh <-chan struct{}) {
|
func (adc *attachDetachController) Run(stopCh <-chan struct{}) {
|
||||||
defer runtime.HandleCrash()
|
defer runtime.HandleCrash()
|
||||||
glog.Infof("Starting Attach Detach Controller")
|
glog.Infof("Starting Attach Detach Controller")
|
||||||
|
|
||||||
// Start self-created shared informers
|
go adc.reconciler.Run(stopCh)
|
||||||
if adc.selfCreatedPodInformer {
|
|
||||||
go adc.internalPodInformer.Run(stopCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
if adc.selfCreatedNodeInformer {
|
|
||||||
go adc.internalNodeInformer.Run(stopCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
<-stopCh
|
<-stopCh
|
||||||
glog.Infof("Shutting down Attach Detach Controller")
|
glog.Infof("Shutting down Attach Detach Controller")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) podAdd(obj interface{}) {
|
func (adc *attachDetachController) podAdd(obj interface{}) {
|
||||||
// No op for now
|
pod, ok := obj.(*api.Pod)
|
||||||
|
if pod == nil || !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pod.Spec.NodeName == "" {
|
||||||
|
// Ignore pods without NodeName, indicating they are not scheduled.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
adc.processPodVolumes(pod, true /* addVolumes */)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) podUpdate(oldObj, newObj interface{}) {
|
func (adc *attachDetachController) podUpdate(oldObj, newObj interface{}) {
|
||||||
// No op for now
|
// The flow for update is the same as add.
|
||||||
|
adc.podAdd(newObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) podDelete(obj interface{}) {
|
func (adc *attachDetachController) podDelete(obj interface{}) {
|
||||||
// No op for now
|
pod, ok := obj.(*api.Pod)
|
||||||
|
if pod == nil || !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
adc.processPodVolumes(pod, false /* addVolumes */)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) nodeAdd(obj interface{}) {
|
func (adc *attachDetachController) nodeAdd(obj interface{}) {
|
||||||
// No op for now
|
node, ok := obj.(*api.Node)
|
||||||
|
if node == nil || !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeName := node.Name
|
||||||
|
if _, exists := node.Annotations[ControllerManagedAnnotation]; exists {
|
||||||
|
// Node specifies annotation indicating it should be managed by attach
|
||||||
|
// detach controller. Add it to desired state of world.
|
||||||
|
adc.desiredStateOfWorld.AddNode(nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
adc.processSafeToDetachAnnotations(nodeName, node.Annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) nodeUpdate(oldObj, newObj interface{}) {
|
func (adc *attachDetachController) nodeUpdate(oldObj, newObj interface{}) {
|
||||||
// No op for now
|
// The flow for update is the same as add.
|
||||||
|
adc.nodeAdd(newObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) nodeDelete(obj interface{}) {
|
func (adc *attachDetachController) nodeDelete(obj interface{}) {
|
||||||
// No op for now
|
node, ok := obj.(*api.Node)
|
||||||
|
if node == nil || !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeName := node.Name
|
||||||
|
if err := adc.desiredStateOfWorld.DeleteNode(nodeName); err != nil {
|
||||||
|
glog.V(10).Infof("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
adc.processSafeToDetachAnnotations(nodeName, node.Annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// processPodVolumes processes the volumes in the given pod and adds them to the
|
||||||
|
// desired state of the world if addVolumes is true, otherwise it removes them.
|
||||||
|
func (adc *attachDetachController) processPodVolumes(
|
||||||
|
pod *api.Pod, addVolumes bool) {
|
||||||
|
if pod == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pod.Spec.Volumes) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !adc.desiredStateOfWorld.NodeExists(pod.Spec.NodeName) {
|
||||||
|
// If the node the pod is scheduled to does not exist in the desired
|
||||||
|
// state of the world data structure, that indicates the node is not
|
||||||
|
// yet managed by the controller. Therefore, ignore the pod.
|
||||||
|
// If the node is added to the list of managed nodes in the future,
|
||||||
|
// future adds and updates to the pod will be processed.
|
||||||
|
glog.V(10).Infof(
|
||||||
|
"Skipping processing of pod %q/%q: it is scheduled to node %q which is not managed by the controller.",
|
||||||
|
pod.Namespace,
|
||||||
|
pod.Name,
|
||||||
|
pod.Spec.NodeName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process volume spec for each volume defined in pod
|
||||||
|
for _, podVolume := range pod.Spec.Volumes {
|
||||||
|
volumeSpec, err := adc.createVolumeSpec(podVolume, pod.Namespace)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(10).Infof(
|
||||||
|
"Error processing volume %q for pod %q/%q: %v",
|
||||||
|
podVolume.Name,
|
||||||
|
pod.Namespace,
|
||||||
|
pod.Name,
|
||||||
|
err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
attachableVolumePlugin, err :=
|
||||||
|
adc.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
|
||||||
|
if err != nil || attachableVolumePlugin == nil {
|
||||||
|
glog.V(10).Infof(
|
||||||
|
"Skipping volume %q for pod %q/%q: it does not implement attacher interface. err=%v",
|
||||||
|
podVolume.Name,
|
||||||
|
pod.Namespace,
|
||||||
|
pod.Name,
|
||||||
|
err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if addVolumes {
|
||||||
|
// Add volume to desired state of world
|
||||||
|
_, err := adc.desiredStateOfWorld.AddPod(
|
||||||
|
getUniquePodName(pod), volumeSpec, pod.Spec.NodeName)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(10).Infof(
|
||||||
|
"Failed to add volume %q for pod %q/%q to desiredStateOfWorld. %v",
|
||||||
|
podVolume.Name,
|
||||||
|
pod.Namespace,
|
||||||
|
pod.Name,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Remove volume from desired state of world
|
||||||
|
uniqueVolumeName, err := attachableVolumePlugin.GetUniqueVolumeName(volumeSpec)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(10).Infof(
|
||||||
|
"Failed to delete volume %q for pod %q/%q from desiredStateOfWorld. GetUniqueVolumeName failed with %v",
|
||||||
|
podVolume.Name,
|
||||||
|
pod.Namespace,
|
||||||
|
pod.Name,
|
||||||
|
err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
adc.desiredStateOfWorld.DeletePod(
|
||||||
|
getUniquePodName(pod), uniqueVolumeName, pod.Spec.NodeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// createVolumeSpec creates and returns a mutatable volume.Spec object for the
|
||||||
|
// specified volume. It dereference any PVC to get PV objects, if needed.
|
||||||
|
func (adc *attachDetachController) createVolumeSpec(
|
||||||
|
podVolume api.Volume, podNamespace string) (*volume.Spec, error) {
|
||||||
|
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
|
||||||
|
// If podVolume is a PVC, fetch the real PV behind the claim
|
||||||
|
pvName, pvcUID, err := adc.getPVCFromCacheExtractPV(
|
||||||
|
podNamespace, pvcSource.ClaimName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error processing PVC %q: %v", pvcSource.ClaimName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch actual PV object
|
||||||
|
volumeSpec, err := adc.getPVSpecFromCache(
|
||||||
|
pvName, pvcSource.ReadOnly, pvcUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error processing PVC %q: %v", pvcSource.ClaimName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumeSpec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not return the original volume object, since it's from the shared
|
||||||
|
// informer it may be mutated by another consumer.
|
||||||
|
clonedPodVolumeObj, err := api.Scheme.DeepCopy(podVolume)
|
||||||
|
if err != nil || clonedPodVolumeObj == nil {
|
||||||
|
return nil, fmt.Errorf("failed to deep copy %q volume object", podVolume.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedPodVolume, ok := clonedPodVolumeObj.(api.Volume)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to cast clonedPodVolume %#v to api.Volume", clonedPodVolumeObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return volume.NewSpecFromVolume(&clonedPodVolume), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPVCFromCacheExtractPV fetches the PVC object with the given namespace and
|
||||||
|
// name from the shared internal PVC store extracts the name of the PV it is
|
||||||
|
// pointing to and returns it.
|
||||||
|
// This method returns an error if a PVC object does not exist in the cache
|
||||||
|
// with the given namespace/name.
|
||||||
|
// This method returns an error if the PVC object's phase is not "Bound".
|
||||||
|
func (adc *attachDetachController) getPVCFromCacheExtractPV(
|
||||||
|
namespace string, name string) (string, types.UID, error) {
|
||||||
|
key := name
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
key = namespace + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
pvcObj, exists, err := adc.pvcInformer.GetStore().Get(key)
|
||||||
|
if pvcObj == nil || !exists || err != nil {
|
||||||
|
return "", "", fmt.Errorf(
|
||||||
|
"failed to find PVC %q in PVCInformer cache. %v",
|
||||||
|
key,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pvc, ok := pvcObj.(*api.PersistentVolumeClaim)
|
||||||
|
if ok || pvc == nil {
|
||||||
|
return "", "", fmt.Errorf(
|
||||||
|
"failed to cast %q object %#v to PersistentVolumeClaim",
|
||||||
|
key,
|
||||||
|
pvcObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pvc.Status.Phase != api.ClaimBound || pvc.Spec.VolumeName == "" {
|
||||||
|
return "", "", fmt.Errorf(
|
||||||
|
"PVC %q has non-bound phase (%q) or empty pvc.Spec.VolumeName (%q)",
|
||||||
|
key,
|
||||||
|
pvc.Status.Phase,
|
||||||
|
pvc.Spec.VolumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pvc.Spec.VolumeName, pvc.UID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPVSpecFromCache fetches the PV object with the given name from the shared
|
||||||
|
// internal PV store and returns a volume.Spec representing it.
|
||||||
|
// This method returns an error if a PV object does not exist in the cache with
|
||||||
|
// the given name.
|
||||||
|
// This method deep copies the PV object so the caller may use the returned
|
||||||
|
// volume.Spec object without worrying about it mutating unexpectedly.
|
||||||
|
func (adc *attachDetachController) getPVSpecFromCache(
|
||||||
|
name string,
|
||||||
|
pvcReadOnly bool,
|
||||||
|
expectedClaimUID types.UID) (*volume.Spec, error) {
|
||||||
|
pvObj, exists, err := adc.pvInformer.GetStore().Get(name)
|
||||||
|
if pvObj == nil || !exists || err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to find PV %q in PVInformer cache. %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pv, ok := pvObj.(*api.PersistentVolume)
|
||||||
|
if ok || pv == nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to cast %q object %#v to PersistentVolume", name, pvObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pv.Spec.ClaimRef == nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"found PV object %q but it has a nil pv.Spec.ClaimRef indicating it is not yet bound to the claim",
|
||||||
|
name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pv.Spec.ClaimRef.UID != expectedClaimUID {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"found PV object %q but its pv.Spec.ClaimRef.UID (%q) does not point to claim.UID (%q)",
|
||||||
|
name,
|
||||||
|
pv.Spec.ClaimRef.UID,
|
||||||
|
expectedClaimUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not return the object from the informer, since the store is shared it
|
||||||
|
// may be mutated by another consumer.
|
||||||
|
clonedPVObj, err := api.Scheme.DeepCopy(pv)
|
||||||
|
if err != nil || clonedPVObj == nil {
|
||||||
|
return nil, fmt.Errorf("failed to deep copy %q PV object", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
clonedPV, ok := clonedPVObj.(api.PersistentVolume)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to cast %q clonedPV %#v to PersistentVolume", name, pvObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return volume.NewSpecFromPersistentVolume(&clonedPV, pvcReadOnly), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSafeToDetachAnnotations processes the "safe to detach" annotations for
|
||||||
|
// the given node. It makes calls to delete any annotations referring to volumes
|
||||||
|
// it is not aware of. For volumes it is aware of, it marks them safe to detach
|
||||||
|
// in the "actual state of world" data structure.
|
||||||
|
func (adc *attachDetachController) processSafeToDetachAnnotations(
|
||||||
|
nodeName string, annotations map[string]string) {
|
||||||
|
var annotationsToRemove []string
|
||||||
|
for annotation := range annotations {
|
||||||
|
// Check annotations for "safe to detach" volumes
|
||||||
|
annotation = strings.ToLower(annotation)
|
||||||
|
if strings.HasPrefix(annotation, SafeToDetachAnnotation) {
|
||||||
|
// If volume exists in "actual state of world" mark it as safe to detach
|
||||||
|
safeToAttachVolume := strings.TrimPrefix(annotation, SafeToDetachAnnotation)
|
||||||
|
if err := adc.actualStateOfWorld.MarkVolumeNodeSafeToDetach(safeToAttachVolume, nodeName); err != nil {
|
||||||
|
// If volume doesn't exist in "actual state of world" remove
|
||||||
|
// the "safe to detach" annotation from the node
|
||||||
|
annotationsToRemove = append(annotationsToRemove, annotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Call out to API server to delete annotationsToRemove from Node
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUniquePodName returns a unique name to reference pod by in memory caches
|
||||||
|
func getUniquePodName(pod *api.Pod) string {
|
||||||
|
return types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeHost implementation
|
||||||
|
// This is an unfortunate requirement of the current factoring of volume plugin
|
||||||
|
// initializing code. It requires kubelet specific methods used by the mounting
|
||||||
|
// code to be implemented by all initializers even if the initializer does not
|
||||||
|
// do mounting (like this attach/detach controller).
|
||||||
|
// Issue kubernetes/kubernetes/issues/14217 to fix this.
|
||||||
|
func (adc *attachDetachController) GetPluginDir(podUID string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) GetKubeClient() internalclientset.Interface {
|
||||||
|
return adc.kubeClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) NewWrapperMounter(volName string, spec volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
|
||||||
|
return nil, fmt.Errorf("NewWrapperMounter not supported by Attach/Detach controller's VolumeHost implementation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) {
|
||||||
|
return nil, fmt.Errorf("NewWrapperUnmounter not supported by Attach/Detach controller's VolumeHost implementation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) GetCloudProvider() cloudprovider.Interface {
|
||||||
|
return adc.cloud
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) GetMounter() mount.Interface {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) GetWriter() io.Writer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adc *attachDetachController) GetHostName() string {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
114
pkg/controller/volume/attach_detach_controller_test.go
Normal file
114
pkg/controller/volume/attach_detach_controller_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 volume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||||
|
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/framework/informers"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/watch"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_NewAttachDetachController_Positive(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
fakeKubeClient := createTestClient()
|
||||||
|
resyncPeriod := 5 * time.Minute
|
||||||
|
podInformer := informers.CreateSharedPodIndexInformer(fakeKubeClient, resyncPeriod)
|
||||||
|
nodeInformer := informers.CreateSharedNodeIndexInformer(fakeKubeClient, resyncPeriod)
|
||||||
|
pvcInformer := informers.CreateSharedPVCIndexInformer(fakeKubeClient, resyncPeriod)
|
||||||
|
pvInformer := informers.CreateSharedPVIndexInformer(fakeKubeClient, resyncPeriod)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
_, err := NewAttachDetachController(
|
||||||
|
fakeKubeClient,
|
||||||
|
podInformer,
|
||||||
|
nodeInformer,
|
||||||
|
pvcInformer,
|
||||||
|
pvInformer,
|
||||||
|
nil, /* cloud */
|
||||||
|
nil /* plugins */)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run failed with error. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestClient() *fake.Clientset {
|
||||||
|
fakeClient := &fake.Clientset{}
|
||||||
|
|
||||||
|
fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
|
obj := &api.PodList{}
|
||||||
|
podNamePrefix := "mypod"
|
||||||
|
namespace := "mynamespace"
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
|
||||||
|
pod := api.Pod{
|
||||||
|
Status: api.PodStatus{
|
||||||
|
Phase: api.PodRunning,
|
||||||
|
},
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: podName,
|
||||||
|
Namespace: namespace,
|
||||||
|
Labels: map[string]string{
|
||||||
|
"name": podName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Containers: []api.Container{
|
||||||
|
{
|
||||||
|
Name: "containerName",
|
||||||
|
Image: "containerImage",
|
||||||
|
VolumeMounts: []api.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "volumeMountName",
|
||||||
|
ReadOnly: false,
|
||||||
|
MountPath: "/mnt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Volumes: []api.Volume{
|
||||||
|
{
|
||||||
|
Name: "volumeName",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
|
||||||
|
PDName: "pdName",
|
||||||
|
FSType: "ext4",
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
obj.Items = append(obj.Items, pod)
|
||||||
|
}
|
||||||
|
return true, obj, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
fakeWatch := watch.NewFake()
|
||||||
|
fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil))
|
||||||
|
|
||||||
|
return fakeClient
|
||||||
|
}
|
183
pkg/controller/volume/attacherdetacher/attacher_detacher.go
Normal file
183
pkg/controller/volume/attacherdetacher/attacher_detacher.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 attacherdetacher implements interfaces that enable triggering attach
|
||||||
|
// and detach operations on volumes.
|
||||||
|
package attacherdetacher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/controller/volume/cache"
|
||||||
|
"k8s.io/kubernetes/pkg/util/goroutinemap"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AttacherDetacher defines a set of operations for attaching or detaching a
|
||||||
|
// volume from a node.
|
||||||
|
type AttacherDetacher interface {
|
||||||
|
// Spawns a new goroutine to execute volume-specific logic to attach the
|
||||||
|
// volume to the node specified in the volumeToAttach.
|
||||||
|
// Once attachment completes successfully, the actualStateOfWorld is updated
|
||||||
|
// to indicate the volume is attached to the node.
|
||||||
|
// If there is an error indicating the volume is already attached to the
|
||||||
|
// specified node, attachment is assumed to be successful (plugins are
|
||||||
|
// responsible for implmenting this behavior).
|
||||||
|
// All other errors are logged and the goroutine terminates without updating
|
||||||
|
// actualStateOfWorld (caller is responsible for retrying as needed).
|
||||||
|
AttachVolume(volumeToAttach *cache.VolumeToAttach, actualStateOfWorld cache.ActualStateOfWorld) error
|
||||||
|
|
||||||
|
// Spawns a new goroutine to execute volume-specific logic to detach the
|
||||||
|
// volume from the node specified in volumeToDetach.
|
||||||
|
// Once detachment completes successfully, the actualStateOfWorld is updated
|
||||||
|
// to remove the volume/node combo.
|
||||||
|
// If there is an error indicating the volume is already detached from the
|
||||||
|
// specified node, detachment is assumed to be successful (plugins are
|
||||||
|
// responsible for implmenting this behavior).
|
||||||
|
// All other errors are logged and the goroutine terminates without updating
|
||||||
|
// actualStateOfWorld (caller is responsible for retrying as needed).
|
||||||
|
DetachVolume(volumeToDetach *cache.AttachedVolume, actualStateOfWorld cache.ActualStateOfWorld) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAttacherDetacher returns a new instance of AttacherDetacher.
|
||||||
|
func NewAttacherDetacher(volumePluginMgr *volume.VolumePluginMgr) AttacherDetacher {
|
||||||
|
return &attacherDetacher{
|
||||||
|
volumePluginMgr: volumePluginMgr,
|
||||||
|
pendingOperations: goroutinemap.NewGoRoutineMap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type attacherDetacher struct {
|
||||||
|
// volumePluginMgr is the volume plugin manager used to create volume
|
||||||
|
// plugin objects.
|
||||||
|
volumePluginMgr *volume.VolumePluginMgr
|
||||||
|
// pendingOperations keeps track of pending attach and detach operations so
|
||||||
|
// multiple operations are not started on the same volume
|
||||||
|
pendingOperations goroutinemap.GoRoutineMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *attacherDetacher) AttachVolume(
|
||||||
|
volumeToAttach *cache.VolumeToAttach,
|
||||||
|
actualStateOfWorld cache.ActualStateOfWorld) error {
|
||||||
|
attachFunc, err := ad.generateAttachVolumeFunc(volumeToAttach, actualStateOfWorld)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad.pendingOperations.Run(volumeToAttach.VolumeName, attachFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *attacherDetacher) DetachVolume(
|
||||||
|
volumeToDetach *cache.AttachedVolume,
|
||||||
|
actualStateOfWorld cache.ActualStateOfWorld) error {
|
||||||
|
detachFunc, err := ad.generateDetachVolumeFunc(volumeToDetach, actualStateOfWorld)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad.pendingOperations.Run(volumeToDetach.VolumeName, detachFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *attacherDetacher) generateAttachVolumeFunc(
|
||||||
|
volumeToAttach *cache.VolumeToAttach,
|
||||||
|
actualStateOfWorld cache.ActualStateOfWorld) (func() error, error) {
|
||||||
|
// Get attacher plugin
|
||||||
|
attachableVolumePlugin, err := ad.volumePluginMgr.FindAttachablePluginBySpec(volumeToAttach.VolumeSpec)
|
||||||
|
if err != nil || attachableVolumePlugin == nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to get AttachablePlugin from volumeSpec for volume %q err=%v",
|
||||||
|
volumeToAttach.VolumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeAttacher, newAttacherErr := attachableVolumePlugin.NewAttacher()
|
||||||
|
if newAttacherErr != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to get NewAttacher from volumeSpec for volume %q err=%v",
|
||||||
|
volumeToAttach.VolumeSpec.Name(),
|
||||||
|
newAttacherErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() error {
|
||||||
|
// Execute attach
|
||||||
|
attachErr := volumeAttacher.Attach(volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
|
||||||
|
|
||||||
|
if attachErr != nil {
|
||||||
|
// On failure, just log and exit. The controller will retry
|
||||||
|
glog.Errorf("Attach operation for %q failed with: %v", volumeToAttach.VolumeName, attachErr)
|
||||||
|
return attachErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update actual state of world
|
||||||
|
_, addVolumeNodeErr := actualStateOfWorld.AddVolumeNode(volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
|
||||||
|
if addVolumeNodeErr != nil {
|
||||||
|
// On failure, just log and exit. The controller will retry
|
||||||
|
glog.Errorf("Attach operation for %q succeeded but updating actualStateOfWorld failed with: %v", volumeToAttach.VolumeName, addVolumeNodeErr)
|
||||||
|
return addVolumeNodeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ad *attacherDetacher) generateDetachVolumeFunc(
|
||||||
|
volumeToDetach *cache.AttachedVolume,
|
||||||
|
actualStateOfWorld cache.ActualStateOfWorld) (func() error, error) {
|
||||||
|
// Get attacher plugin
|
||||||
|
attachableVolumePlugin, err := ad.volumePluginMgr.FindAttachablePluginBySpec(volumeToDetach.VolumeSpec)
|
||||||
|
if err != nil || attachableVolumePlugin == nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to get AttachablePlugin from volumeSpec for volume %q err=%v",
|
||||||
|
volumeToDetach.VolumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceName, err := attachableVolumePlugin.GetDeviceName(volumeToDetach.VolumeSpec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to GetUniqueVolumeName from AttachablePlugin for volumeSpec %q err=%v",
|
||||||
|
volumeToDetach.VolumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeDetacher, err := attachableVolumePlugin.NewDetacher()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to get NewDetacher from volumeSpec for volume %q err=%v",
|
||||||
|
volumeToDetach.VolumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() error {
|
||||||
|
// Execute detach
|
||||||
|
detachErr := volumeDetacher.Detach(deviceName, volumeToDetach.NodeName)
|
||||||
|
|
||||||
|
if detachErr != nil {
|
||||||
|
// On failure, just log and exit. The controller will retry
|
||||||
|
glog.Errorf("Detach operation for %q failed with: %v", volumeToDetach.VolumeName, detachErr)
|
||||||
|
return detachErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Reset "safe to detach" annotation on Node
|
||||||
|
|
||||||
|
// Update actual state of world
|
||||||
|
actualStateOfWorld.DeleteVolumeNode(volumeToDetach.VolumeName, volumeToDetach.NodeName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
314
pkg/controller/volume/cache/actual_state_of_world.go
vendored
Normal file
314
pkg/controller/volume/cache/actual_state_of_world.go
vendored
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 cache implements data structures used by the attach/detach controller
|
||||||
|
to keep track of volumes, the nodes they are attached to, and the pods that
|
||||||
|
reference them.
|
||||||
|
*/
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActualStateOfWorld defines a set of thread-safe operations supported on
|
||||||
|
// the attach/detach controller's actual state of the world cache.
|
||||||
|
// This cache contains volumes->nodes i.e. a set of all volumes and the nodes
|
||||||
|
// the attach/detach controller believes are successfully attached.
|
||||||
|
type ActualStateOfWorld interface {
|
||||||
|
// AddVolumeNode adds the given volume and node to the underlying store
|
||||||
|
// indicating the specified volume is attached to the specified node.
|
||||||
|
// A unique volumeName is generated from the volumeSpec and returned on
|
||||||
|
// success.
|
||||||
|
// If the volume/node combo already exists, this is a no-op.
|
||||||
|
// If volumeSpec is not an attachable volume plugin, an error is returned.
|
||||||
|
// If no volume with the name volumeName exists in the store, the volume is
|
||||||
|
// added.
|
||||||
|
// If no node with the name nodeName exists in list of attached nodes for
|
||||||
|
// the specified volume, the node is added.
|
||||||
|
AddVolumeNode(volumeSpec *volume.Spec, nodeName string) (string, error)
|
||||||
|
|
||||||
|
// MarkVolumeNodeSafeToDetach marks the given volume as safe to detach from
|
||||||
|
// the given node.
|
||||||
|
// If no volume with the name volumeName exists in the store, an error is
|
||||||
|
// returned.
|
||||||
|
// If no node with the name nodeName exists in list of attached nodes for
|
||||||
|
// the specified volume, an error is returned.
|
||||||
|
MarkVolumeNodeSafeToDetach(volumeName, nodeName string) error
|
||||||
|
|
||||||
|
// MarkDesireToDetach returns the difference between the current time and
|
||||||
|
// the DetachRequestedTime for the given volume/node combo. If the
|
||||||
|
// DetachRequestedTime is zero, it is set to the current time.
|
||||||
|
// If no volume with the name volumeName exists in the store, an error is
|
||||||
|
// returned.
|
||||||
|
// If no node with the name nodeName exists in list of attached nodes for
|
||||||
|
// the specified volume, an error is returned.
|
||||||
|
MarkDesireToDetach(volumeName, nodeName string) (time.Duration, error)
|
||||||
|
|
||||||
|
// DeleteVolumeNode removes the given volume and node from the underlying
|
||||||
|
// store indicating the specified volume is no longer attached to the
|
||||||
|
// specified node.
|
||||||
|
// If the volume/node combo does not exist, this is a no-op.
|
||||||
|
// If after deleting the node, the specified volume contains no other child
|
||||||
|
// nodes, the volume is also deleted.
|
||||||
|
DeleteVolumeNode(volumeName, nodeName string)
|
||||||
|
|
||||||
|
// VolumeNodeExists returns true if the specified volume/node combo exists
|
||||||
|
// in the underlying store indicating the specified volume is attached to
|
||||||
|
// the specified node.
|
||||||
|
VolumeNodeExists(volumeName, nodeName string) bool
|
||||||
|
|
||||||
|
// GetAttachedVolumes generates and returns a list of volumes/node pairs
|
||||||
|
// reflecting which volumes are attached to which nodes based on the
|
||||||
|
// current actual state of the world.
|
||||||
|
GetAttachedVolumes() []AttachedVolume
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachedVolume represents a volume that is attached to a node.
|
||||||
|
type AttachedVolume struct {
|
||||||
|
// VolumeName is the unique identifier for the volume that is attached.
|
||||||
|
VolumeName string
|
||||||
|
|
||||||
|
// VolumeSpec is the volume spec containing the specification for the
|
||||||
|
// volume that is attached.
|
||||||
|
VolumeSpec *volume.Spec
|
||||||
|
|
||||||
|
// NodeName is the identifier for the node that the volume is attached to.
|
||||||
|
NodeName string
|
||||||
|
|
||||||
|
// SafeToDetach indicates that this volume has been been unmounted from the
|
||||||
|
// node and is safe to detach.
|
||||||
|
// The value is set by MarkVolumeNodeSafeToDetach(...) and reset on
|
||||||
|
// AddVolumeNode(...) calls.
|
||||||
|
SafeToDetach bool
|
||||||
|
|
||||||
|
// DetachRequestedTime is used to capture the desire to detach this volume.
|
||||||
|
// When the volume is newly created this value is set to time zero.
|
||||||
|
// It is set to current time, when MarkDesireToDetach(...) is called, if it
|
||||||
|
// was previously set to zero (other wise its value remains the same).
|
||||||
|
// It is reset to zero on AddVolumeNode(...) calls.
|
||||||
|
DetachRequestedTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewActualStateOfWorld returns a new instance of ActualStateOfWorld.
|
||||||
|
func NewActualStateOfWorld(volumePluginMgr *volume.VolumePluginMgr) ActualStateOfWorld {
|
||||||
|
return &actualStateOfWorld{
|
||||||
|
attachedVolumes: make(map[string]attachedVolume),
|
||||||
|
volumePluginMgr: volumePluginMgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type actualStateOfWorld struct {
|
||||||
|
// attachedVolumes is a map containing the set of volumes the attach/detach
|
||||||
|
// controller believes to be successfully attached to the nodes it is
|
||||||
|
// managing. The key in this map is the name of the volume and the value is
|
||||||
|
// an object containing more information about the attached volume.
|
||||||
|
attachedVolumes map[string]attachedVolume
|
||||||
|
// volumePluginMgr is the volume plugin manager used to create volume
|
||||||
|
// plugin objects.
|
||||||
|
volumePluginMgr *volume.VolumePluginMgr
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// The volume object represents a volume the the attach/detach controller
|
||||||
|
// believes to be succesfully attached to a node it is managing.
|
||||||
|
type attachedVolume struct {
|
||||||
|
// volumeName contains the unique identifier for this volume.
|
||||||
|
volumeName string
|
||||||
|
|
||||||
|
// spec is the volume spec containing the specification for this volume.
|
||||||
|
// Used to generate the volume plugin object, and passed to attach/detach
|
||||||
|
// methods.
|
||||||
|
spec *volume.Spec
|
||||||
|
|
||||||
|
// nodesAttachedTo is a map containing the set of nodes this volume has
|
||||||
|
// successfully been attached to. The key in this map is the name of the
|
||||||
|
// node and the value is a node object containing more information about
|
||||||
|
// the node.
|
||||||
|
nodesAttachedTo map[string]nodeAttachedTo
|
||||||
|
}
|
||||||
|
|
||||||
|
// The nodeAttachedTo object represents a node that .
|
||||||
|
type nodeAttachedTo struct {
|
||||||
|
// nodeName contains the name of this node.
|
||||||
|
nodeName string
|
||||||
|
|
||||||
|
// safeToDetach indicates that this node/volume combo has been unmounted
|
||||||
|
// by the node and is safe to detach
|
||||||
|
safeToDetach bool
|
||||||
|
|
||||||
|
// detachRequestedTime used to capture the desire to detach this volume
|
||||||
|
detachRequestedTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) AddVolumeNode(volumeSpec *volume.Spec, nodeName string) (string, error) {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
|
||||||
|
attachableVolumePlugin, err := asw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
|
||||||
|
if err != nil || attachableVolumePlugin == nil {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"failed to get AttachablePlugin from volumeSpec for volume %q err=%v",
|
||||||
|
volumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeName, err := attachableVolumePlugin.GetUniqueVolumeName(volumeSpec)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"failed to GetUniqueVolumeName from AttachablePlugin for volumeSpec %q err=%v",
|
||||||
|
volumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
volumeObj = attachedVolume{
|
||||||
|
volumeName: volumeName,
|
||||||
|
spec: volumeSpec,
|
||||||
|
nodesAttachedTo: make(map[string]nodeAttachedTo),
|
||||||
|
}
|
||||||
|
asw.attachedVolumes[volumeName] = volumeObj
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeObj, nodeExists := volumeObj.nodesAttachedTo[nodeName]
|
||||||
|
if !nodeExists || nodeObj.safeToDetach || !nodeObj.detachRequestedTime.IsZero() {
|
||||||
|
// Create object if it doesn't exist.
|
||||||
|
// Reset safeToDeatch and detachRequestedTime values if it does.
|
||||||
|
volumeObj.nodesAttachedTo[nodeName] = nodeAttachedTo{
|
||||||
|
nodeName: nodeName,
|
||||||
|
safeToDetach: false,
|
||||||
|
detachRequestedTime: time.Time{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumeName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) MarkVolumeNodeSafeToDetach(
|
||||||
|
volumeName, nodeName string) error {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to MarkVolumeNodeSafeToDetach(volumeName=%q, nodeName=%q) volumeName does not exist",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeObj, nodeExists := volumeObj.nodesAttachedTo[nodeName]
|
||||||
|
if !nodeExists {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to MarkVolumeNodeSafeToDetach(volumeName=%q, nodeName=%q) nodeName does not exist",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset safe to detach
|
||||||
|
nodeObj.safeToDetach = true
|
||||||
|
volumeObj.nodesAttachedTo[nodeName] = nodeObj
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) MarkDesireToDetach(
|
||||||
|
volumeName, nodeName string) (time.Duration, error) {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
return time.Millisecond * 0, fmt.Errorf(
|
||||||
|
"failed to MarkVolumeNodeSafeToDetach(volumeName=%q, nodeName=%q) volumeName does not exist",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeObj, nodeExists := volumeObj.nodesAttachedTo[nodeName]
|
||||||
|
if !nodeExists {
|
||||||
|
return time.Millisecond * 0, fmt.Errorf(
|
||||||
|
"failed to MarkVolumeNodeSafeToDetach(volumeName=%q, nodeName=%q) nodeName does not exist",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeObj.detachRequestedTime.IsZero() {
|
||||||
|
nodeObj.detachRequestedTime = time.Now()
|
||||||
|
volumeObj.nodesAttachedTo[nodeName] = nodeObj
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Since(volumeObj.nodesAttachedTo[nodeName].detachRequestedTime), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) DeleteVolumeNode(volumeName, nodeName string) {
|
||||||
|
asw.Lock()
|
||||||
|
defer asw.Unlock()
|
||||||
|
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, nodeExists := volumeObj.nodesAttachedTo[nodeName]
|
||||||
|
if nodeExists {
|
||||||
|
delete(asw.attachedVolumes[volumeName].nodesAttachedTo, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(volumeObj.nodesAttachedTo) == 0 {
|
||||||
|
delete(asw.attachedVolumes, volumeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) VolumeNodeExists(volumeName, nodeName string) bool {
|
||||||
|
asw.RLock()
|
||||||
|
defer asw.RUnlock()
|
||||||
|
|
||||||
|
volumeObj, volumeExists := asw.attachedVolumes[volumeName]
|
||||||
|
if volumeExists {
|
||||||
|
if _, nodeExists := volumeObj.nodesAttachedTo[nodeName]; nodeExists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asw *actualStateOfWorld) GetAttachedVolumes() []AttachedVolume {
|
||||||
|
asw.RLock()
|
||||||
|
defer asw.RUnlock()
|
||||||
|
|
||||||
|
attachedVolumes := make([]AttachedVolume, 0 /* len */, len(asw.attachedVolumes) /* cap */)
|
||||||
|
for volumeName, volumeObj := range asw.attachedVolumes {
|
||||||
|
for nodeName, nodeObj := range volumeObj.nodesAttachedTo {
|
||||||
|
attachedVolumes = append(
|
||||||
|
attachedVolumes,
|
||||||
|
AttachedVolume{
|
||||||
|
NodeName: nodeName,
|
||||||
|
VolumeName: volumeName,
|
||||||
|
VolumeSpec: volumeObj.spec,
|
||||||
|
SafeToDetach: nodeObj.safeToDetach,
|
||||||
|
DetachRequestedTime: nodeObj.detachRequestedTime})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachedVolumes
|
||||||
|
}
|
682
pkg/controller/volume/cache/actual_state_of_world_test.go
vendored
Normal file
682
pkg/controller/volume/cache/actual_state_of_world_test.go
vendored
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AddVolumeNode_Positive_NewVolumeNewNode(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
|
||||||
|
nodeName := "node-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName, err := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo does not exist, it should.", generatedVolumeName, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddVolumeNode_Positive_ExistingVolumeNewNode(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
node2Name := "node2-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName1, add1Err := asw.AddVolumeNode(volumeSpec, node1Name)
|
||||||
|
generatedVolumeName2, add2Err := asw.AddVolumeNode(volumeSpec, node2Name)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if add1Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add1Err)
|
||||||
|
}
|
||||||
|
if add2Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add2Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if generatedVolumeName1 != generatedVolumeName2 {
|
||||||
|
t.Fatalf(
|
||||||
|
"Generated volume names for the same volume should be the same but they are not: %q and %q",
|
||||||
|
generatedVolumeName1,
|
||||||
|
generatedVolumeName2)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeNode1ComboExists := asw.VolumeNodeExists(generatedVolumeName1, node1Name)
|
||||||
|
if !volumeNode1ComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo does not exist, it should.", generatedVolumeName1, node1Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeNode2ComboExists := asw.VolumeNodeExists(generatedVolumeName1, node2Name)
|
||||||
|
if !volumeNode2ComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo does not exist, it should.", generatedVolumeName1, node2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 2 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <2> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName1, volumeName, node1Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName1, volumeName, node2Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddVolumeNode_Positive_ExistingVolumeExistingNode(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName1, add1Err := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
generatedVolumeName2, add2Err := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if add1Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add1Err)
|
||||||
|
}
|
||||||
|
if add2Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add2Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if generatedVolumeName1 != generatedVolumeName2 {
|
||||||
|
t.Fatalf(
|
||||||
|
"Generated volume names for the same volume should be the same but they are not: %q and %q",
|
||||||
|
generatedVolumeName1,
|
||||||
|
generatedVolumeName2)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(generatedVolumeName1, nodeName)
|
||||||
|
if !volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo does not exist, it should.", generatedVolumeName1, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName1, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteVolumeNode_Positive_VolumeExistsNodeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
asw.DeleteVolumeNode(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(generatedVolumeName, nodeName)
|
||||||
|
if volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo exists, it should not.", generatedVolumeName, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 0 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <0> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteVolumeNode_Positive_VolumeDoesntExistNodeDoesntExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
nodeName := "node-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
asw.DeleteVolumeNode(volumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(volumeName, nodeName)
|
||||||
|
if volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo exists, it should not.", volumeName, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 0 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <0> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteVolumeNode_Positive_TwoNodesOneDeleted(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
node2Name := "node2-name"
|
||||||
|
generatedVolumeName1, add1Err := asw.AddVolumeNode(volumeSpec, node1Name)
|
||||||
|
if add1Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add1Err)
|
||||||
|
}
|
||||||
|
generatedVolumeName2, add2Err := asw.AddVolumeNode(volumeSpec, node2Name)
|
||||||
|
if add2Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add2Err)
|
||||||
|
}
|
||||||
|
if generatedVolumeName1 != generatedVolumeName2 {
|
||||||
|
t.Fatalf(
|
||||||
|
"Generated volume names for the same volume should be the same but they are not: %q and %q",
|
||||||
|
generatedVolumeName1,
|
||||||
|
generatedVolumeName2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
asw.DeleteVolumeNode(generatedVolumeName1, node1Name)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(generatedVolumeName1, node1Name)
|
||||||
|
if volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo exists, it should not.", generatedVolumeName1, node1Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeNodeComboExists = asw.VolumeNodeExists(generatedVolumeName1, node2Name)
|
||||||
|
if !volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo does not exist, it should.", generatedVolumeName1, node2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName1, volumeName, node2Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_VolumeNodeExists_Positive_VolumeExistsNodeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if !volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo does not exist, it should.", generatedVolumeName, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_VolumeNodeExists_Positive_VolumeExistsNodeDoesntExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
node2Name := "node2-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, node1Name)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(generatedVolumeName, node2Name)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo exists, it should not.", generatedVolumeName, node2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, node1Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_VolumeNodeExists_Positive_VolumeAndNodeDontExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
nodeName := "node-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumeNodeComboExists := asw.VolumeNodeExists(volumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if volumeNodeComboExists {
|
||||||
|
t.Fatalf("%q/%q volume/node combo exists, it should not.", volumeName, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 0 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <0> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetAttachedVolumes_Positive_NoVolumesOrNodes(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(attachedVolumes) != 0 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <0> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetAttachedVolumes_Positive_OneVolumeOneNode(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetAttachedVolumes_Positive_TwoVolumeTwoNodes(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volume1Name := "volume1-name"
|
||||||
|
volume1Spec := controllervolumetesting.GetTestVolumeSpec(volume1Name, volume1Name)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
generatedVolumeName1, add1Err := asw.AddVolumeNode(volume1Spec, node1Name)
|
||||||
|
if add1Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add1Err)
|
||||||
|
}
|
||||||
|
volume2Name := "volume2-name"
|
||||||
|
volume2Spec := controllervolumetesting.GetTestVolumeSpec(volume2Name, volume2Name)
|
||||||
|
node2Name := "node2-name"
|
||||||
|
generatedVolumeName2, add2Err := asw.AddVolumeNode(volume2Spec, node2Name)
|
||||||
|
if add2Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add2Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(attachedVolumes) != 2 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <2> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName1, volume1Name, node1Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName2, volume2Name, node2Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetAttachedVolumes_Positive_OneVolumeTwoNodes(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
generatedVolumeName1, add1Err := asw.AddVolumeNode(volumeSpec, node1Name)
|
||||||
|
if add1Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add1Err)
|
||||||
|
}
|
||||||
|
node2Name := "node2-name"
|
||||||
|
generatedVolumeName2, add2Err := asw.AddVolumeNode(volumeSpec, node2Name)
|
||||||
|
if add2Err != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", add2Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if generatedVolumeName1 != generatedVolumeName2 {
|
||||||
|
t.Fatalf(
|
||||||
|
"Generated volume names for the same volume should be the same but they are not: %q and %q",
|
||||||
|
generatedVolumeName1,
|
||||||
|
generatedVolumeName2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(attachedVolumes) != 2 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <2> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName1, volumeName, node1Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName1, volumeName, node2Name, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkVolumeNodeSafeToDetach_Positive_NotMarked(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act: do not mark -- test default value
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkVolumeNodeSafeToDetach_Positive_Marked(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
markSafeToDetachErr := asw.MarkVolumeNodeSafeToDetach(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if markSafeToDetachErr != nil {
|
||||||
|
t.Fatalf("MarkVolumeNodeSafeToDetach failed. Expected <no error> Actual: <%v>", markSafeToDetachErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, true /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkVolumeNodeSafeToDetach_Positive_MarkedAddVolumeNodeReset(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
markSafeToDetachErr := asw.MarkVolumeNodeSafeToDetach(generatedVolumeName, nodeName)
|
||||||
|
generatedVolumeName, addErr = asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if markSafeToDetachErr != nil {
|
||||||
|
t.Fatalf("MarkVolumeNodeSafeToDetach failed. Expected <no error> Actual: <%v>", markSafeToDetachErr)
|
||||||
|
}
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkVolumeNodeSafeToDetach_Positive_MarkedVerifyDetachRequestedTimePerserved(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
_, err := asw.MarkDesireToDetach(generatedVolumeName, nodeName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MarkDesireToDetach failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
expectedDetachRequestedTime := asw.GetAttachedVolumes()[0].DetachRequestedTime
|
||||||
|
|
||||||
|
// Act
|
||||||
|
markSafeToDetachErr := asw.MarkVolumeNodeSafeToDetach(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if markSafeToDetachErr != nil {
|
||||||
|
t.Fatalf("MarkVolumeNodeSafeToDetach failed. Expected <no error> Actual: <%v>", markSafeToDetachErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, true /* expectedSafeToDetach */, true /* expectNonZeroDetachRequestedTime */)
|
||||||
|
if !expectedDetachRequestedTime.Equal(attachedVolumes[0].DetachRequestedTime) {
|
||||||
|
t.Fatalf("DetachRequestedTime changed. Expected: <%v> Actual: <%v>", expectedDetachRequestedTime, attachedVolumes[0].DetachRequestedTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkDesireToDetach_Positive_NotMarked(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act: do not mark -- test default value
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkDesireToDetach_Positive_Marked(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
_, markDesireToDetachErr := asw.MarkDesireToDetach(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if markDesireToDetachErr != nil {
|
||||||
|
t.Fatalf("MarkDesireToDetach failed. Expected: <no error> Actual: <%v>", markDesireToDetachErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, true /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkDesireToDetach_Positive_MarkedAddVolumeNodeReset(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
_, markDesireToDetachErr := asw.MarkDesireToDetach(generatedVolumeName, nodeName)
|
||||||
|
generatedVolumeName, addErr = asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if markDesireToDetachErr != nil {
|
||||||
|
t.Fatalf("MarkDesireToDetach failed. Expected: <no error> Actual: <%v>", markDesireToDetachErr)
|
||||||
|
}
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, false /* expectedSafeToDetach */, false /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarkDesireToDetach_Positive_MarkedVerifySafeToDetachPreserved(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
asw := NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
generatedVolumeName, addErr := asw.AddVolumeNode(volumeSpec, nodeName)
|
||||||
|
if addErr != nil {
|
||||||
|
t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", addErr)
|
||||||
|
}
|
||||||
|
markSafeToDetachErr := asw.MarkVolumeNodeSafeToDetach(generatedVolumeName, nodeName)
|
||||||
|
if markSafeToDetachErr != nil {
|
||||||
|
t.Fatalf("MarkVolumeNodeSafeToDetach failed. Expected <no error> Actual: <%v>", markSafeToDetachErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
_, markDesireToDetachErr := asw.MarkDesireToDetach(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if markDesireToDetachErr != nil {
|
||||||
|
t.Fatalf("MarkDesireToDetach failed. Expected: <no error> Actual: <%v>", markDesireToDetachErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attachedVolumes := asw.GetAttachedVolumes()
|
||||||
|
if len(attachedVolumes) != 1 {
|
||||||
|
t.Fatalf("len(attachedVolumes) Expected: <1> Actual: <%v>", len(attachedVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyAttachedVolume(t, attachedVolumes, generatedVolumeName, volumeName, nodeName, true /* expectedSafeToDetach */, true /* expectNonZeroDetachRequestedTime */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyAttachedVolume(
|
||||||
|
t *testing.T,
|
||||||
|
attachedVolumes []AttachedVolume,
|
||||||
|
expectedVolumeName,
|
||||||
|
expectedVolumeSpecName,
|
||||||
|
expectedNodeName string,
|
||||||
|
expectedSafeToDetach,
|
||||||
|
expectNonZeroDetachRequestedTime bool) {
|
||||||
|
for _, attachedVolume := range attachedVolumes {
|
||||||
|
if attachedVolume.VolumeName == expectedVolumeName &&
|
||||||
|
attachedVolume.VolumeSpec.Name() == expectedVolumeSpecName &&
|
||||||
|
attachedVolume.NodeName == expectedNodeName &&
|
||||||
|
attachedVolume.SafeToDetach == expectedSafeToDetach &&
|
||||||
|
attachedVolume.DetachRequestedTime.IsZero() == !expectNonZeroDetachRequestedTime {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf(
|
||||||
|
"attachedVolumes (%v) should contain the volume/node combo %q/%q with SafeToDetach=%v and NonZeroDetachRequestedTime=%v. It does not.",
|
||||||
|
attachedVolumes,
|
||||||
|
expectedVolumeName,
|
||||||
|
expectedNodeName,
|
||||||
|
expectedSafeToDetach,
|
||||||
|
expectNonZeroDetachRequestedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Logf("attachedVolumes: %v", asw.GetAttachedVolumes()) // TEMP
|
@ -1,412 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 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 cache implements a data structure used by the attach/detach controller
|
|
||||||
to keep track of volumes, the nodes they are attached to, and the pods that
|
|
||||||
reference them. It is thread-safe.
|
|
||||||
*/
|
|
||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AttachDetachVolumeCache defines the set of operations the volume cache
|
|
||||||
// supports.
|
|
||||||
type AttachDetachVolumeCache interface {
|
|
||||||
// AddVolume adds the given volume to the list of volumes managed by the
|
|
||||||
// attach detach controller.
|
|
||||||
// If the volume already exists, this is a no-op.
|
|
||||||
AddVolume(volumeName string)
|
|
||||||
|
|
||||||
// AddNode adds the given node to the list of nodes the specified volume is
|
|
||||||
// attached to.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
// If the node already exists for the specified volume, this is a no-op.
|
|
||||||
AddNode(nodeName, volumeName string) error
|
|
||||||
|
|
||||||
// AddPod adds the given pod to the list of pods that are scheduled to
|
|
||||||
// the specified node and referencing the specified volume.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes,
|
|
||||||
// an error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
// If the pod already exists for the specified volume, this is a no-op.
|
|
||||||
AddPod(podName, nodeName, volumeName string) error
|
|
||||||
|
|
||||||
// DeleteVolume removes the given volume from the list of volumes managed
|
|
||||||
// by the attach detach controller.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
// All attachedNodes must be deleted from the volume before it is deleted.
|
|
||||||
// If the specified volume contains 1 or more attachedNodes, an error is
|
|
||||||
// returned.
|
|
||||||
DeleteVolume(volumeName string) error
|
|
||||||
|
|
||||||
// DeleteNode removes the given node from the list of nodes the specified
|
|
||||||
// volume is attached to.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes,
|
|
||||||
// an error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
// All scheduledPods must be deleted from the node before it is deleted.
|
|
||||||
// If the specified node contains 1 or more scheduledPods, an error is
|
|
||||||
// returned.
|
|
||||||
DeleteNode(nodeName, volumeName string) error
|
|
||||||
|
|
||||||
// DeletePod removes the given pod from the list of pods that are scheduled
|
|
||||||
// to the specified node and referencing the specified volume.
|
|
||||||
// If no pod with the name podName exists for the specified volume/node, an
|
|
||||||
// error is returned.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes,
|
|
||||||
// an error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
DeletePod(podName, nodeName, volumeName string) error
|
|
||||||
|
|
||||||
// VolumeExists returns true if the volume with the specified name exists
|
|
||||||
// in the list of volumes managed by the attach detach controller.
|
|
||||||
VolumeExists(volumeName string) bool
|
|
||||||
|
|
||||||
// NodeExists returns true if the node with the specified name exists in
|
|
||||||
// the list of nodes the specified volume is attached to.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
NodeExists(nodeName, volumeName string) (bool, error)
|
|
||||||
|
|
||||||
// PodExists returns true if the pod with the specified name exists in the
|
|
||||||
// list of pods that are scheduled to the specified node and referencing
|
|
||||||
// the specified volume.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes,
|
|
||||||
// an error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
PodExists(podName, nodeName, volumeName string) (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAttachDetachVolumeCache returns a new instance of the
|
|
||||||
// AttachDetachVolumeCache.
|
|
||||||
func NewAttachDetachVolumeCache() AttachDetachVolumeCache {
|
|
||||||
return &attachDetachVolumeCache{
|
|
||||||
volumesManaged: make(map[string]volume),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type attachDetachVolumeCache struct {
|
|
||||||
// volumesManaged is a map containing the set of volumes managed by the
|
|
||||||
// attach/detach controller. The key in this map is the name of the unique
|
|
||||||
// volume identifier and the value is a volume object containing more
|
|
||||||
// information about the volume.
|
|
||||||
volumesManaged map[string]volume
|
|
||||||
sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// The volume object represents a volume that is being tracked by the attach
|
|
||||||
// detach controller.
|
|
||||||
type volume struct {
|
|
||||||
// name contains the unique identifer for this volume.
|
|
||||||
name string
|
|
||||||
|
|
||||||
// attachedNodes is a map containing the set of nodes this volume has
|
|
||||||
// successfully been attached to. The key in this map is the name of the
|
|
||||||
// node and the value is a node object containing more information about
|
|
||||||
// the node.
|
|
||||||
attachedNodes map[string]node
|
|
||||||
}
|
|
||||||
|
|
||||||
// The node object represents a node that a volume is attached to.
|
|
||||||
type node struct {
|
|
||||||
// name contains the name of this node.
|
|
||||||
name string
|
|
||||||
|
|
||||||
// scheduledPods is a map containing the set of pods that are scheduled to
|
|
||||||
// this node and referencing the underlying volume. The key in the map is
|
|
||||||
// the name of the pod and the value is a pod object containing more
|
|
||||||
// information about the pod.
|
|
||||||
scheduledPods map[string]pod
|
|
||||||
}
|
|
||||||
|
|
||||||
// The pod object represents a pod that is scheduled to a node and referncing
|
|
||||||
// the underlying volume.
|
|
||||||
type pod struct {
|
|
||||||
// name contains the name of this pod.
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddVolume adds the given volume to the list of volumes managed by the attach
|
|
||||||
// detach controller.
|
|
||||||
// If the volume already exists, this is a no-op.
|
|
||||||
func (vc *attachDetachVolumeCache) AddVolume(volumeName string) {
|
|
||||||
vc.Lock()
|
|
||||||
defer vc.Unlock()
|
|
||||||
if _, exists := vc.volumesManaged[volumeName]; !exists {
|
|
||||||
vc.volumesManaged[volumeName] = volume{
|
|
||||||
name: volumeName,
|
|
||||||
attachedNodes: make(map[string]node),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddNode adds the given node to the list of nodes the specified volume is
|
|
||||||
// attached to.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed volumes,
|
|
||||||
// an error is returned.
|
|
||||||
// If the node already exists for the specified volume, this is a no-op.
|
|
||||||
func (vc *attachDetachVolumeCache) AddNode(nodeName, volumeName string) error {
|
|
||||||
vc.Lock()
|
|
||||||
defer vc.Unlock()
|
|
||||||
|
|
||||||
vol, volExists := vc.volumesManaged[volumeName]
|
|
||||||
if !volExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to add node %q to volume %q--no volume with that name exists in the list of managed volumes",
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, nodeExists := vol.attachedNodes[nodeName]; !nodeExists {
|
|
||||||
vc.volumesManaged[volumeName].attachedNodes[nodeName] = node{
|
|
||||||
name: nodeName,
|
|
||||||
scheduledPods: make(map[string]pod),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPod adds the given pod to the list of pods that are scheduled to the
|
|
||||||
// specified node and referencing the specified volume.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes,
|
|
||||||
// an error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
// If the pod already exists for the specified volume, this is a no-op.
|
|
||||||
func (vc *attachDetachVolumeCache) AddPod(podName, nodeName, volumeName string) error {
|
|
||||||
vc.Lock()
|
|
||||||
defer vc.Unlock()
|
|
||||||
|
|
||||||
volObj, volExists := vc.volumesManaged[volumeName]
|
|
||||||
if !volExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to add pod %q to node %q volume %q--no volume with that name exists in the list of managed volumes",
|
|
||||||
podName,
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeObj, nodeExists := volObj.attachedNodes[nodeName]
|
|
||||||
if !nodeExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to add pod %q to node %q volume %q--no node with that name exists in the list of attached nodes for that volume",
|
|
||||||
podName,
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, podExists := nodeObj.scheduledPods[podName]; !podExists {
|
|
||||||
vc.volumesManaged[volumeName].attachedNodes[nodeName].scheduledPods[podName] =
|
|
||||||
pod{
|
|
||||||
name: podName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteVolume removes the given volume from the list of volumes managed by
|
|
||||||
// the attach detach controller.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed volumes,
|
|
||||||
// an error is returned.
|
|
||||||
// All attachedNodes must be deleted from the volume before it is deleted.
|
|
||||||
// If the specified volume contains 1 or more attachedNodes, an error is
|
|
||||||
// returned.
|
|
||||||
func (vc *attachDetachVolumeCache) DeleteVolume(volumeName string) error {
|
|
||||||
vc.Lock()
|
|
||||||
defer vc.Unlock()
|
|
||||||
|
|
||||||
volObj, volExists := vc.volumesManaged[volumeName]
|
|
||||||
if !volExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to delete volume %q--no volume with that name exists in the list of managed volumes",
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(volObj.attachedNodes) > 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to remove volume %q from list of managed volumes--the volume still contains %v nodes in its list of attached nodes",
|
|
||||||
volumeName,
|
|
||||||
len(volObj.attachedNodes))
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(
|
|
||||||
vc.volumesManaged,
|
|
||||||
volumeName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteNode removes the given node from the list of nodes the specified
|
|
||||||
// volume is attached to.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes, an
|
|
||||||
// error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
// All scheduledPods must be deleted from the node before it is deleted.
|
|
||||||
// If the specified node contains 1 or more scheduledPods, an error is
|
|
||||||
// returned.
|
|
||||||
func (vc *attachDetachVolumeCache) DeleteNode(nodeName, volumeName string) error {
|
|
||||||
vc.Lock()
|
|
||||||
defer vc.Unlock()
|
|
||||||
|
|
||||||
volObj, volExists := vc.volumesManaged[volumeName]
|
|
||||||
if !volExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to delete node %q from volume %q--no volume with that name exists in the list of managed volumes",
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeObj, nodeExists := volObj.attachedNodes[nodeName]
|
|
||||||
if !nodeExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to delete node %q from volume %q--no node with the that name exists in the list of attached nodes for that volume",
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nodeObj.scheduledPods) > 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to remove node %q from volume %q--the node still contains %v pods in its list of scheduled pods",
|
|
||||||
nodeName,
|
|
||||||
volumeName,
|
|
||||||
len(nodeObj.scheduledPods))
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(
|
|
||||||
vc.volumesManaged[volumeName].attachedNodes,
|
|
||||||
nodeName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePod removes the given pod from the list of pods that are scheduled
|
|
||||||
// to the specified node and referencing the specified volume.
|
|
||||||
// If no pod with the name podName exists for the specified volume/node, an
|
|
||||||
// error is returned.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes,
|
|
||||||
// an error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
func (vc *attachDetachVolumeCache) DeletePod(podName, nodeName, volumeName string) error {
|
|
||||||
vc.Lock()
|
|
||||||
defer vc.Unlock()
|
|
||||||
|
|
||||||
volObj, volExists := vc.volumesManaged[volumeName]
|
|
||||||
if !volExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to delete pod %q from node %q volume %q--no volume with that name exists in the list of managed volumes",
|
|
||||||
podName,
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeObj, nodeExists := volObj.attachedNodes[nodeName]
|
|
||||||
if !nodeExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to delete pod %q from node %q volume %q--no node with that name exists in the list of attached nodes for that volume",
|
|
||||||
podName,
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, podExists := nodeObj.scheduledPods[podName]; !podExists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"failed to delete pod %q from node %q volume %q--no pod with that name exists in the list of scheduled pods under that node/volume",
|
|
||||||
podName,
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(
|
|
||||||
vc.volumesManaged[volumeName].attachedNodes[nodeName].scheduledPods,
|
|
||||||
podName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumeExists returns true if the volume with the specified name exists in
|
|
||||||
// the list of volumes managed by the attach detach controller.
|
|
||||||
func (vc *attachDetachVolumeCache) VolumeExists(volumeName string) bool {
|
|
||||||
vc.RLock()
|
|
||||||
defer vc.RUnlock()
|
|
||||||
|
|
||||||
_, volExists := vc.volumesManaged[volumeName]
|
|
||||||
return volExists
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeExists returns true if the node with the specified name exists in the
|
|
||||||
// list of nodes the specified volume is attached to.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed
|
|
||||||
// volumes, an error is returned.
|
|
||||||
func (vc *attachDetachVolumeCache) NodeExists(nodeName, volumeName string) (bool, error) {
|
|
||||||
vc.RLock()
|
|
||||||
defer vc.RUnlock()
|
|
||||||
|
|
||||||
volObj, volExists := vc.volumesManaged[volumeName]
|
|
||||||
if !volExists {
|
|
||||||
return false,
|
|
||||||
fmt.Errorf(
|
|
||||||
"failed to check if node %q exists under volume %q--no volume with that name exists in the list of managed volumes",
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, nodeExists := volObj.attachedNodes[nodeName]
|
|
||||||
return nodeExists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PodExists returns true if the pod with the specified name exists in the list
|
|
||||||
// of pods that are scheduled to the specified node and referencing the
|
|
||||||
// specified volume.
|
|
||||||
// If no node with the name nodeName exists in the list of attached nodes, an
|
|
||||||
// error is returned.
|
|
||||||
// If no volume with the name volumeName exists in the list of managed volumes,
|
|
||||||
// an error is returned.
|
|
||||||
func (vc *attachDetachVolumeCache) PodExists(podName, nodeName, volumeName string) (bool, error) {
|
|
||||||
vc.RLock()
|
|
||||||
defer vc.RUnlock()
|
|
||||||
|
|
||||||
volObj, volExists := vc.volumesManaged[volumeName]
|
|
||||||
if !volExists {
|
|
||||||
return false,
|
|
||||||
fmt.Errorf(
|
|
||||||
"failed to check if node %q exists under volume %q--no volume with that name exists in the list of managed volumes",
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeObj, nodeExists := volObj.attachedNodes[nodeName]
|
|
||||||
if !nodeExists {
|
|
||||||
return false, fmt.Errorf(
|
|
||||||
"failed to check if pod %q exists under node %q volume %q--no node with that name exists in the list of attached nodes for that volume",
|
|
||||||
podName,
|
|
||||||
nodeName,
|
|
||||||
volumeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, podExists := nodeObj.scheduledPods[podName]
|
|
||||||
return podExists, nil
|
|
||||||
}
|
|
@ -1,579 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 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 cache
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func Test_AddVolume_Positive_NewVolume(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
volumeExists := vc.VolumeExists(volumeName)
|
|
||||||
if !volumeExists {
|
|
||||||
t.Fatalf("Added volume %q does not exist, it should.", volumeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddVolume_Positive_ExistingVolume(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
volumeExists := vc.VolumeExists(volumeName)
|
|
||||||
if !volumeExists {
|
|
||||||
t.Fatalf("Added volume %q does not exist, it should.", volumeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddNode_Positive_NewNodeVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
nodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if nodeErr != nil {
|
|
||||||
t.Fatalf("AddNode failed. Expected: <no error> Actual: <%v>", nodeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeExists, nodeExistsErr := vc.NodeExists(nodeName, volumeName)
|
|
||||||
if nodeExistsErr != nil {
|
|
||||||
t.Fatalf("NodeExists failed. Expected: <no error> Actual: <%v>", nodeExistsErr)
|
|
||||||
}
|
|
||||||
if !nodeExists {
|
|
||||||
t.Fatalf("Added node %q does not exist, it should.", nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddNode_Positive_NodeExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
nodeErr1 := vc.AddNode(nodeName, volumeName)
|
|
||||||
if nodeErr1 != nil {
|
|
||||||
t.Fatalf("First call to AddNode failed. Expected: <no error> Actual: <%v>", nodeErr1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
nodeErr2 := vc.AddNode(nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if nodeErr2 != nil {
|
|
||||||
t.Fatalf("Second call to AddNode failed. Expected: <no error> Actual: <%v>", nodeErr2)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeExists, nodeExistsErr := vc.NodeExists(nodeName, volumeName)
|
|
||||||
if nodeExistsErr != nil {
|
|
||||||
t.Fatalf("NodeExists failed. Expected: <no error> Actual: <%v>", nodeExistsErr)
|
|
||||||
}
|
|
||||||
if !nodeExists {
|
|
||||||
t.Fatalf("Added node %q does not exist, it should.", nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddNode_Negative_NewNodeVolumeDoesntExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
nodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if nodeErr == nil {
|
|
||||||
t.Fatalf("AddNode did not fail. Expected: <\"failed to add node...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeExists, nodeExistsErr := vc.NodeExists(nodeName, volumeName)
|
|
||||||
if nodeExistsErr == nil {
|
|
||||||
t.Fatalf("NodeExists did not fail. Expected: <failed to check if node...no volume with that name exists in the list of managed volumes> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if nodeExists {
|
|
||||||
t.Fatalf("Added node %q exists, it should not.", nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddPod_Positive_NewPodNodeExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
nodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
if nodeErr != nil {
|
|
||||||
t.Fatalf("AddNode failed. Expected: <no error> Actual: <%v>", nodeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
podErr := vc.AddPod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if podErr != nil {
|
|
||||||
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr != nil {
|
|
||||||
t.Fatalf("PodExists failed. Expected: <no error> Actual: <%v>", podExistsErr)
|
|
||||||
}
|
|
||||||
if !podExists {
|
|
||||||
t.Fatalf("Added pod %q does not exist, it should.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddPod_Positive_PodExistsNodeExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
nodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
if nodeErr != nil {
|
|
||||||
t.Fatalf("AddNode failed. Expected: <no error> Actual: <%v>", nodeErr)
|
|
||||||
}
|
|
||||||
podErr1 := vc.AddPod(podName, nodeName, volumeName)
|
|
||||||
if podErr1 != nil {
|
|
||||||
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
podErr2 := vc.AddPod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if podErr2 != nil {
|
|
||||||
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr2)
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr != nil {
|
|
||||||
t.Fatalf("PodExists failed. Expected: <no error> Actual: <%v>", podExistsErr)
|
|
||||||
}
|
|
||||||
if !podExists {
|
|
||||||
t.Fatalf("Added pod %q does not exist, it should.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddPod_Negative_NewPodNodeDoesntExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
podErr := vc.AddPod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if podErr == nil {
|
|
||||||
t.Fatalf("AddPod did not fail. Expected: <\"failed to add pod...no node with that name exists in the list of attached nodes for that volume\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr == nil {
|
|
||||||
t.Fatalf("PodExists did not fail. Expected: <\"failed to check if pod exists...no node with that name exists in the list of attached nodes for that volume\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if podExists {
|
|
||||||
t.Fatalf("Added pod %q exists, it should not.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AddPod_Negative_NewPodNodeDoesntExistsVolumeDoesntExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
podErr := vc.AddPod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if podErr == nil {
|
|
||||||
t.Fatalf("AddPod did not fail. Expected: <\"failed to add pod...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr == nil {
|
|
||||||
t.Fatalf("PodExists did not fail. Expected: <\"failed to check if node...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if podExists {
|
|
||||||
t.Fatalf("Added pod %q exists, it should not.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_VolumeExists_Positive_NonExistantVolume(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
notAddedVolumeName := "volume-not-added-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
notAddedVolumeExists := vc.VolumeExists(notAddedVolumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if notAddedVolumeExists {
|
|
||||||
t.Fatalf("Not added volume %q exists, it should not.", notAddedVolumeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_NodeExists_Positive_NonExistantNodeVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
notAddedNodeName := "node-not-added-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
notAddedNodeExists, notAddedNodeExistsErr := vc.NodeExists(notAddedNodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if notAddedNodeExistsErr != nil {
|
|
||||||
t.Fatalf("NodeExists failed. Expected: <no error> Actual: <%v>", notAddedNodeExistsErr)
|
|
||||||
}
|
|
||||||
if notAddedNodeExists {
|
|
||||||
t.Fatalf("Not added node %q exists, it should not.", notAddedNodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_NodeExists_Negative_NonExistantNodeVolumeDoesntExist(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
notAddedNodeName := "node-not-added-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
notAddedNodeExists, notAddedNodeExistsErr := vc.NodeExists(notAddedNodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if notAddedNodeExistsErr == nil {
|
|
||||||
t.Fatalf("NodeExists did not fail. Expected: <failed to check if node...no volume with that name exists in the list of managed volumes> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if notAddedNodeExists {
|
|
||||||
t.Fatalf("Added node %q exists, it should not.", notAddedNodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_PodExists_Positive_NonExistantPodNodeExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
notAddedPodName := "pod-not-added-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
addNodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
if addNodeErr != nil {
|
|
||||||
t.Fatalf("AddNode for node %q failed. Expected: <no error> Actual: <%v>", nodeName, addNodeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
notAddedPodExists, notAddedPodExistsErr := vc.PodExists(notAddedPodName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if notAddedPodExistsErr != nil {
|
|
||||||
t.Fatalf("PodExists failed. Expected: <no error> Actual: <%v>", notAddedPodExistsErr)
|
|
||||||
}
|
|
||||||
if notAddedPodExists {
|
|
||||||
t.Fatalf("Not added pod %q exists, it should not.", notAddedPodName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_PodExists_Negative_NonExistantPodNodeDoesntExistVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
notAddedPodName := "pod-not-added-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
notAddedPodExists, notAddedPodExistsErr := vc.PodExists(notAddedPodName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if notAddedPodExistsErr == nil {
|
|
||||||
t.Fatalf("PodExists did not fail. Expected: <\"failed to check if pod exists...no node with that name exists in the list of attached nodes for that volume\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if notAddedPodExists {
|
|
||||||
t.Fatalf("Added pod %q exists, it should not.", notAddedPodName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_PodExists_Negative_NonExistantPodNodeDoesntExistVolumeDoesntExist(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
notAddedPodName := "pod-not-added-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
notAddedPodExists, notAddedPodExistsErr := vc.PodExists(notAddedPodName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if notAddedPodExistsErr == nil {
|
|
||||||
t.Fatalf("PodExists did not fail. Expected: <\"failed to check if node...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if notAddedPodExists {
|
|
||||||
t.Fatalf("Added pod %q exists, it should not.", notAddedPodName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeleteVolume_Positive_VolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deleteVolumeErr := vc.DeleteVolume(volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deleteVolumeErr != nil {
|
|
||||||
t.Fatalf("DeleteVolume failed. Expected: <no error> Actual: <%v>", deleteVolumeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeExists := vc.VolumeExists(volumeName)
|
|
||||||
if volumeExists {
|
|
||||||
t.Fatalf("Deleted volume %q still exists, it should not.", volumeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeleteVolume_Negative_VolumeDoesntExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
notAddedVolumeName := "volume-not-added-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deleteVolumeErr := vc.DeleteVolume(notAddedVolumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deleteVolumeErr == nil {
|
|
||||||
t.Fatalf("DeleteVolume did not fail. Expected: <\"failed to delete volume...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
notAddedVolumeExists := vc.VolumeExists(notAddedVolumeName)
|
|
||||||
if notAddedVolumeExists {
|
|
||||||
t.Fatalf("Not added volume %q exists, it should not.", notAddedVolumeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeleteNode_Positive_NodeExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
nodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
if nodeErr != nil {
|
|
||||||
t.Fatalf("AddNode failed. Expected: <no error> Actual: <%v>", nodeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deleteNodeErr := vc.DeleteNode(nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deleteNodeErr != nil {
|
|
||||||
t.Fatalf("DeleteNode failed. Expected: <no error> Actual: <%v>", deleteNodeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeExists, nodeExistsErr := vc.NodeExists(nodeName, volumeName)
|
|
||||||
if nodeExistsErr != nil {
|
|
||||||
t.Fatalf("NodeExists failed. Expected: <no error> Actual: <%v>", nodeExistsErr)
|
|
||||||
}
|
|
||||||
if nodeExists {
|
|
||||||
t.Fatalf("Deleted node %q still exists, it should not.", nodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeleteNode_Negative_NodeDoesntExistVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
notAddedNodeName := "node-not-added-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deleteNodeErr := vc.DeleteNode(notAddedNodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deleteNodeErr == nil {
|
|
||||||
t.Fatalf("DeleteNode did not fail. Expected: <\"failed to delete node...no node with the that name exists in the list of attached nodes for that volume\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
notAddedNodeExists, notAddedNodeExistsErr := vc.NodeExists(notAddedNodeName, volumeName)
|
|
||||||
if notAddedNodeExistsErr != nil {
|
|
||||||
t.Fatalf("NodeExists failed. Expected: <no error> Actual: <%v>", notAddedNodeExistsErr)
|
|
||||||
}
|
|
||||||
if notAddedNodeExists {
|
|
||||||
t.Fatalf("Not added node %q exists, it should not.", notAddedNodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeleteNode_Negative_NodeDoesntExistVolumeDoesntExist(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
notAddedNodeName := "node-not-added-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deleteNodeErr := vc.DeleteNode(notAddedNodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deleteNodeErr == nil {
|
|
||||||
t.Fatalf("DeleteNode did not fail. Expected: <\"failed to delete node...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
notAddedNodeExists, notAddedNodeExistsErr := vc.NodeExists(notAddedNodeName, volumeName)
|
|
||||||
if notAddedNodeExistsErr == nil {
|
|
||||||
t.Fatalf("NodeExists did not fail. Expected: <\failed to check if node...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if notAddedNodeExists {
|
|
||||||
t.Fatalf("Not added node %q exists, it should not.", notAddedNodeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeletePod_Positive_PodExistsNodeExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
nodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
if nodeErr != nil {
|
|
||||||
t.Fatalf("AddNode failed. Expected: <no error> Actual: <%v>", nodeErr)
|
|
||||||
}
|
|
||||||
podErr := vc.AddPod(podName, nodeName, volumeName)
|
|
||||||
if podErr != nil {
|
|
||||||
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deletePodErr := vc.DeletePod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deletePodErr != nil {
|
|
||||||
t.Fatalf("DeletePod failed. Expected: <no error> Actual: <%v>", podName)
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr != nil {
|
|
||||||
t.Fatalf("PodExists failed. Expected: <no error> Actual: <%v>", podExistsErr)
|
|
||||||
}
|
|
||||||
if podExists {
|
|
||||||
t.Fatalf("Deleted pod %q still exists, it should not.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeletePod_Positive_PodDoesntExistNodeExistsVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
nodeErr := vc.AddNode(nodeName, volumeName)
|
|
||||||
if nodeErr != nil {
|
|
||||||
t.Fatalf("AddNode failed. Expected: <no error> Actual: <%v>", nodeErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deletePodErr := vc.DeletePod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deletePodErr == nil {
|
|
||||||
t.Fatalf("DeletePod did not fail. Expected: <\"failed to delete pod...no pod with that name exists in the list of scheduled pods under that node/volume\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr != nil {
|
|
||||||
t.Fatalf("PodExists failed. Expected: <no error> Actual: <%v>", podExistsErr)
|
|
||||||
}
|
|
||||||
if podExists {
|
|
||||||
t.Fatalf("Deleted pod %q still exists, it should not.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeletePod_Positive_PodDoesntExistNodeDoesntExistVolumeExists(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
vc.AddVolume(volumeName)
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deletePodErr := vc.DeletePod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deletePodErr == nil {
|
|
||||||
t.Fatalf("DeletePod did not fail. Expected: <\"failed to delete pod...no node with that name exists in the list of attached nodes for that volume\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr == nil {
|
|
||||||
t.Fatalf("PodExists did not fail. Expected: <\failed to check if pod...no node with that name exists in the list of attached nodes for that volume\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if podExists {
|
|
||||||
t.Fatalf("Deleted pod %q still exists, it should not.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DeletePod_Positive_PodDoesntExistNodeDoesntExistVolumeDoesntExist(t *testing.T) {
|
|
||||||
// Arrange
|
|
||||||
vc := NewAttachDetachVolumeCache()
|
|
||||||
volumeName := "volume-name"
|
|
||||||
nodeName := "node-name"
|
|
||||||
podName := "pod-name"
|
|
||||||
|
|
||||||
// Act
|
|
||||||
deletePodErr := vc.DeletePod(podName, nodeName, volumeName)
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
if deletePodErr == nil {
|
|
||||||
t.Fatalf("DeletePod did not fail. Expected: <\"failed to delete pod...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
|
|
||||||
podExists, podExistsErr := vc.PodExists(podName, nodeName, volumeName)
|
|
||||||
if podExistsErr == nil {
|
|
||||||
t.Fatalf("PodExists did not fail. Expected: <\failed to check if pod...no volume with that name exists in the list of managed volumes\"> Actual: <no error>")
|
|
||||||
}
|
|
||||||
if podExists {
|
|
||||||
t.Fatalf("Deleted pod %q still exists, it should not.", podName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
t.Fatalf("%q", notAddedNodeExistsErr)
|
|
||||||
*/
|
|
301
pkg/controller/volume/cache/desired_state_of_world.go
vendored
Normal file
301
pkg/controller/volume/cache/desired_state_of_world.go
vendored
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 cache implements data structures used by the attach/detach controller
|
||||||
|
to keep track of volumes, the nodes they are attached to, and the pods that
|
||||||
|
reference them.
|
||||||
|
*/
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DesiredStateOfWorld defines a set of thread-safe operations supported on
|
||||||
|
// the attach/detach controller's desired state of the world cache.
|
||||||
|
// This cache contains nodes->volumes->pods where nodes are all the nodes
|
||||||
|
// managed by the attach/detach controller, volumes are all the volumes that
|
||||||
|
// should be attached to the specified node, and pods are the pods that
|
||||||
|
// reference the volume and are scheduled to that node.
|
||||||
|
type DesiredStateOfWorld interface {
|
||||||
|
// AddNode adds the given node to the list of nodes managed by the attach/
|
||||||
|
// detach controller.
|
||||||
|
// If the node already exists this is a no-op.
|
||||||
|
AddNode(nodeName string)
|
||||||
|
|
||||||
|
// AddPod adds the given pod to the list of pods that reference the
|
||||||
|
// specified volume and is scheduled to the specified node.
|
||||||
|
// A unique volumeName is generated from the volumeSpec and returned on
|
||||||
|
// success.
|
||||||
|
// If the pod already exists under the specified volume, this is a no-op.
|
||||||
|
// If volumeSpec is not an attachable volume plugin, an error is returned.
|
||||||
|
// If no volume with the name volumeName exists in the list of volumes that
|
||||||
|
// should be attached to the specified node, the volume is implicitly added.
|
||||||
|
// If no node with the name nodeName exists in list of nodes managed by the
|
||||||
|
// attach/detach attached controller, an error is returned.
|
||||||
|
AddPod(podName string, volumeSpec *volume.Spec, nodeName string) (string, error)
|
||||||
|
|
||||||
|
// DeleteNode removes the given node from the list of nodes managed by the
|
||||||
|
// attach/detach controller.
|
||||||
|
// If the node does not exist this is a no-op.
|
||||||
|
// If the node exists but has 1 or more child volumes, an error is returned.
|
||||||
|
DeleteNode(nodeName string) error
|
||||||
|
|
||||||
|
// DeletePod removes the given pod from the list of pods that reference the
|
||||||
|
// specified volume and are scheduled to the specified node.
|
||||||
|
// If no pod exists in the list of pods that reference the specified volume
|
||||||
|
// and are scheduled to the specified node, this is a no-op.
|
||||||
|
// If a node with the name nodeName does not exist in the list of nodes
|
||||||
|
// managed by the attach/detach attached controller, this is a no-op.
|
||||||
|
// If no volume with the name volumeName exists in the list of managed
|
||||||
|
// volumes under the specified node, this is a no-op.
|
||||||
|
// If after deleting the pod, the specified volume contains no other child
|
||||||
|
// pods, the volume is also deleted.
|
||||||
|
DeletePod(podName, volumeName, nodeName string)
|
||||||
|
|
||||||
|
// NodeExists returns true if the node with the specified name exists in
|
||||||
|
// the list of nodes managed by the attach/detach controller.
|
||||||
|
NodeExists(nodeName string) bool
|
||||||
|
|
||||||
|
// VolumeExists returns true if the volume with the specified name exists
|
||||||
|
// in the list of volumes that should be attached to the specified node by
|
||||||
|
// the attach detach controller.
|
||||||
|
VolumeExists(volumeName, nodeName string) bool
|
||||||
|
|
||||||
|
// GetVolumesToAttach generates and returns a list of volumes to attach
|
||||||
|
// and the nodes they should be attached to based on the current desired
|
||||||
|
// state of the world.
|
||||||
|
GetVolumesToAttach() []VolumeToAttach
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeToAttach represents a volume that should be attached to a node.
|
||||||
|
type VolumeToAttach struct {
|
||||||
|
// VolumeName is the unique identifier for the volume that should be
|
||||||
|
// attached.
|
||||||
|
VolumeName string
|
||||||
|
|
||||||
|
// VolumeSpec is a volume spec containing the specification for the volume
|
||||||
|
// that should be attached.
|
||||||
|
VolumeSpec *volume.Spec
|
||||||
|
|
||||||
|
// NodeName is the identifier for the node that the volume should be
|
||||||
|
// attached to.
|
||||||
|
NodeName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDesiredStateOfWorld returns a new instance of DesiredStateOfWorld.
|
||||||
|
func NewDesiredStateOfWorld(volumePluginMgr *volume.VolumePluginMgr) DesiredStateOfWorld {
|
||||||
|
return &desiredStateOfWorld{
|
||||||
|
nodesManaged: make(map[string]nodeManaged),
|
||||||
|
volumePluginMgr: volumePluginMgr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type desiredStateOfWorld struct {
|
||||||
|
// nodesManaged is a map containing the set of nodes managed by the attach/
|
||||||
|
// detach controller. The key in this map is the name of the node and the
|
||||||
|
// value is a node object containing more information about the node.
|
||||||
|
nodesManaged map[string]nodeManaged
|
||||||
|
// volumePluginMgr is the volume plugin manager used to create volume
|
||||||
|
// plugin objects.
|
||||||
|
volumePluginMgr *volume.VolumePluginMgr
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeManaged represents a node that is being managed by the attach/detach
|
||||||
|
// controller.
|
||||||
|
type nodeManaged struct {
|
||||||
|
// nodName contains the name of this node.
|
||||||
|
nodeName string
|
||||||
|
|
||||||
|
// volumesToAttach is a map containing the set of volumes that should be
|
||||||
|
// attached to this node. The key in the map is the name of the volume and
|
||||||
|
// the value is a pod object containing more information about the volume.
|
||||||
|
volumesToAttach map[string]volumeToAttach
|
||||||
|
}
|
||||||
|
|
||||||
|
// The volume object represents a volume that should be attached to a node.
|
||||||
|
type volumeToAttach struct {
|
||||||
|
// volumeName contains the unique identifier for this volume.
|
||||||
|
volumeName string
|
||||||
|
|
||||||
|
// spec is the volume spec containing the specification for this volume.
|
||||||
|
// Used to generate the volume plugin object, and passed to attach/detach
|
||||||
|
// methods.
|
||||||
|
spec *volume.Spec
|
||||||
|
|
||||||
|
// scheduledPods is a map containing the set of pods that reference this
|
||||||
|
// volume and are scheduled to the underlying node. The key in the map is
|
||||||
|
// the name of the pod and the value is a pod object containing more
|
||||||
|
// information about the pod.
|
||||||
|
scheduledPods map[string]pod
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pod object represents a pod that references the underlying volume and is
|
||||||
|
// scheduled to the underlying node.
|
||||||
|
type pod struct {
|
||||||
|
// podName contains the name of this pod.
|
||||||
|
podName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsw *desiredStateOfWorld) AddNode(nodeName string) {
|
||||||
|
dsw.Lock()
|
||||||
|
defer dsw.Unlock()
|
||||||
|
|
||||||
|
if _, nodeExists := dsw.nodesManaged[nodeName]; !nodeExists {
|
||||||
|
dsw.nodesManaged[nodeName] = nodeManaged{
|
||||||
|
nodeName: nodeName,
|
||||||
|
volumesToAttach: make(map[string]volumeToAttach),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsw *desiredStateOfWorld) AddPod(podName string, volumeSpec *volume.Spec, nodeName string) (string, error) {
|
||||||
|
dsw.Lock()
|
||||||
|
defer dsw.Unlock()
|
||||||
|
|
||||||
|
nodeObj, nodeExists := dsw.nodesManaged[nodeName]
|
||||||
|
if !nodeExists {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"no node with the name %q exists in the list of managed nodes",
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
attachableVolumePlugin, err := dsw.volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
|
||||||
|
if err != nil || attachableVolumePlugin == nil {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"failed to get AttachablePlugin from volumeSpec for volume %q err=%v",
|
||||||
|
volumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeName, err := attachableVolumePlugin.GetUniqueVolumeName(volumeSpec)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"failed to GetUniqueVolumeName from AttachablePlugin for volumeSpec %q err=%v",
|
||||||
|
volumeSpec.Name(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeObj, volumeExists := nodeObj.volumesToAttach[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
volumeObj = volumeToAttach{
|
||||||
|
volumeName: volumeName,
|
||||||
|
spec: volumeSpec,
|
||||||
|
scheduledPods: make(map[string]pod),
|
||||||
|
}
|
||||||
|
dsw.nodesManaged[nodeName].volumesToAttach[volumeName] = volumeObj
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, podExists := volumeObj.scheduledPods[podName]; !podExists {
|
||||||
|
dsw.nodesManaged[nodeName].volumesToAttach[volumeName].scheduledPods[podName] =
|
||||||
|
pod{
|
||||||
|
podName: podName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumeName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsw *desiredStateOfWorld) DeleteNode(nodeName string) error {
|
||||||
|
dsw.Lock()
|
||||||
|
defer dsw.Unlock()
|
||||||
|
|
||||||
|
nodeObj, nodeExists := dsw.nodesManaged[nodeName]
|
||||||
|
if !nodeExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nodeObj.volumesToAttach) > 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to delete node %q from list of nodes managed by attach/detach controller--the node still contains %v volumes in its list of volumes to attach",
|
||||||
|
nodeName,
|
||||||
|
len(nodeObj.volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(
|
||||||
|
dsw.nodesManaged,
|
||||||
|
nodeName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsw *desiredStateOfWorld) DeletePod(podName, volumeName, nodeName string) {
|
||||||
|
dsw.Lock()
|
||||||
|
defer dsw.Unlock()
|
||||||
|
|
||||||
|
nodeObj, nodeExists := dsw.nodesManaged[nodeName]
|
||||||
|
if !nodeExists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeObj, volumeExists := nodeObj.volumesToAttach[volumeName]
|
||||||
|
if !volumeExists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, podExists := volumeObj.scheduledPods[podName]; !podExists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(
|
||||||
|
dsw.nodesManaged[nodeName].volumesToAttach[volumeName].scheduledPods,
|
||||||
|
podName)
|
||||||
|
|
||||||
|
if len(volumeObj.scheduledPods) == 0 {
|
||||||
|
delete(
|
||||||
|
dsw.nodesManaged[nodeName].volumesToAttach,
|
||||||
|
volumeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsw *desiredStateOfWorld) NodeExists(nodeName string) bool {
|
||||||
|
dsw.RLock()
|
||||||
|
defer dsw.RUnlock()
|
||||||
|
|
||||||
|
_, nodeExists := dsw.nodesManaged[nodeName]
|
||||||
|
return nodeExists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsw *desiredStateOfWorld) VolumeExists(volumeName, nodeName string) bool {
|
||||||
|
dsw.RLock()
|
||||||
|
defer dsw.RUnlock()
|
||||||
|
|
||||||
|
nodeObj, nodeExists := dsw.nodesManaged[nodeName]
|
||||||
|
if nodeExists {
|
||||||
|
if _, volumeExists := nodeObj.volumesToAttach[volumeName]; volumeExists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsw *desiredStateOfWorld) GetVolumesToAttach() []VolumeToAttach {
|
||||||
|
dsw.RLock()
|
||||||
|
defer dsw.RUnlock()
|
||||||
|
|
||||||
|
volumesToAttach := make([]VolumeToAttach, 0 /* len */, len(dsw.nodesManaged) /* cap */)
|
||||||
|
for nodeName, nodeObj := range dsw.nodesManaged {
|
||||||
|
for volumeName, volumeObj := range nodeObj.volumesToAttach {
|
||||||
|
volumesToAttach = append(volumesToAttach, VolumeToAttach{NodeName: nodeName, VolumeName: volumeName, VolumeSpec: volumeObj.spec})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumesToAttach
|
||||||
|
}
|
974
pkg/controller/volume/cache/desired_state_of_world_test.go
vendored
Normal file
974
pkg/controller/volume/cache/desired_state_of_world_test.go
vendored
Normal file
@ -0,0 +1,974 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AddNode_Positive_NewNode(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
nodeExists := dsw.NodeExists(nodeName)
|
||||||
|
if !nodeExists {
|
||||||
|
t.Fatalf("Added node %q does not exist, it should.", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddNode_Positive_ExistingVolume(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
nodeExists := dsw.NodeExists(nodeName)
|
||||||
|
if !nodeExists {
|
||||||
|
t.Fatalf("Added node %q does not exist, it should.", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func Test_AddNode_Positive_ExistingNode(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
nodeExists := dsw.NodeExists(nodeName)
|
||||||
|
if !nodeExists {
|
||||||
|
t.Fatalf("Added node %q does not exist, it should.", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
nodeExists = dsw.NodeExists(nodeName)
|
||||||
|
if !nodeExists {
|
||||||
|
t.Fatalf("Added node %q does not exist, it should.", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddPod_Positive_NewPodNodeExistsVolumeDoesntExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName, podErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if podErr != nil {
|
||||||
|
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddPod_Positive_NewPodNodeExistsVolumeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
pod1Name := "pod1-name"
|
||||||
|
pod2Name := "pod2-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName, podErr := dsw.AddPod(pod1Name, volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if podErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod1Name,
|
||||||
|
podErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
pod1Name,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName, podErr = dsw.AddPod(pod2Name, volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if podErr != nil {
|
||||||
|
t.Fatalf("AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod2Name,
|
||||||
|
podErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
pod1Name,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddPod_Positive_PodExistsNodeExistsVolumeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName, podErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if podErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
podName,
|
||||||
|
podErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
generatedVolumeName, podErr = dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if podErr != nil {
|
||||||
|
t.Fatalf("AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
podName,
|
||||||
|
podErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddPod_Negative_NewPodNodeDoesntExistVolumeDoesntExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
_, podErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if podErr == nil {
|
||||||
|
t.Fatalf("AddPod did not fail. Expected: <\"failed to add pod...no node with that name exists in the list of managed nodes\"> Actual: <no error>")
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeExists = dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteNode_Positive_NodeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
err := dsw.DeleteNode(nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeleteNode failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeExists := dsw.NodeExists(nodeName)
|
||||||
|
if nodeExists {
|
||||||
|
t.Fatalf("Deleted node %q still exists, it should not.", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteNode_Positive_NodeDoesntExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
notAddedNodeName := "node-not-added-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
err := dsw.DeleteNode(notAddedNodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DeleteNode failed. Expected: <no error> Actual: <%v>", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeExists := dsw.NodeExists(notAddedNodeName)
|
||||||
|
if nodeExists {
|
||||||
|
t.Fatalf("Deleted node %q still exists, it should not.", notAddedNodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteNode_Negative_NodeExistsHasChildVolumes(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
generatedVolumeName, podAddErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
podName,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
err := dsw.DeleteNode(nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("DeleteNode did not fail. Expected: <\"\"> Actual: <no error>")
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeExists := dsw.NodeExists(nodeName)
|
||||||
|
if !nodeExists {
|
||||||
|
t.Fatalf("Node %q no longer exists, it should.", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeletePod_Positive_PodExistsNodeExistsVolumeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
generatedVolumeName, podAddErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
podName,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
volumeExists := dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.DeletePod(podName, generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Deleted pod %q from volume %q/node %q. Volume should also be deleted but it still exists.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeletePod_Positive_2PodsExistNodeExistsVolumesExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
pod1Name := "pod1-name"
|
||||||
|
pod2Name := "pod2-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
generatedVolumeName1, pod1AddErr := dsw.AddPod(pod1Name, volumeSpec, nodeName)
|
||||||
|
if pod1AddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod1Name,
|
||||||
|
pod1AddErr)
|
||||||
|
}
|
||||||
|
generatedVolumeName2, pod2AddErr := dsw.AddPod(pod2Name, volumeSpec, nodeName)
|
||||||
|
if pod2AddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod2Name,
|
||||||
|
pod2AddErr)
|
||||||
|
}
|
||||||
|
if generatedVolumeName1 != generatedVolumeName2 {
|
||||||
|
t.Fatalf(
|
||||||
|
"Generated volume names for the same volume should be the same but they are not: %q and %q",
|
||||||
|
generatedVolumeName1,
|
||||||
|
generatedVolumeName2)
|
||||||
|
}
|
||||||
|
volumeExists := dsw.VolumeExists(generatedVolumeName1, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q does not exist under node %q, it should.",
|
||||||
|
generatedVolumeName1,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.DeletePod(pod1Name, generatedVolumeName1, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName1, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q under node %q should still exist, but it does not.",
|
||||||
|
generatedVolumeName1,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName1, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeletePod_Positive_PodDoesNotExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
pod1Name := "pod1-name"
|
||||||
|
pod2Name := "pod2-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
generatedVolumeName, pod1AddErr := dsw.AddPod(pod1Name, volumeSpec, nodeName)
|
||||||
|
if pod1AddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod1Name,
|
||||||
|
pod1AddErr)
|
||||||
|
}
|
||||||
|
volumeExists := dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
pod1Name,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.DeletePod(pod2Name, generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q does not exist, it should.",
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeletePod_Positive_NodeDoesNotExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
dsw.AddNode(node1Name)
|
||||||
|
generatedVolumeName, podAddErr := dsw.AddPod(podName, volumeSpec, node1Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
podName,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
volumeExists := dsw.VolumeExists(generatedVolumeName, node1Name)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
node1Name)
|
||||||
|
}
|
||||||
|
node2Name := "node2-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.DeletePod(podName, generatedVolumeName, node2Name)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, node1Name)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q does not exist, it should.",
|
||||||
|
generatedVolumeName,
|
||||||
|
node1Name)
|
||||||
|
}
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, node2Name)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"node %q exists, it should not.",
|
||||||
|
node2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolumeName, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeletePod_Positive_VolumeDoesNotExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
podName := "pod-name"
|
||||||
|
volume1Name := "volume1-name"
|
||||||
|
volume1Spec := controllervolumetesting.GetTestVolumeSpec(volume1Name, volume1Name)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
generatedVolume1Name, podAddErr := dsw.AddPod(podName, volume1Spec, nodeName)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
podName,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
volumeExists := dsw.VolumeExists(generatedVolume1Name, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Added pod %q to volume %q/node %q. Volume does not exist, it should.",
|
||||||
|
podName,
|
||||||
|
generatedVolume1Name,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
volume2Name := "volume2-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.DeletePod(podName, volume2Name, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolume1Name, nodeName)
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q does not exist, it should.",
|
||||||
|
generatedVolume1Name,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
volumeExists = dsw.VolumeExists(volume2Name, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"volume %q exists, it should not.",
|
||||||
|
volume2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolume1Name, volume1Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NodeExists_Positive_NodeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
notAddedNodeName := "node-not-added-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
notAddedNodeExists := dsw.NodeExists(notAddedNodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if notAddedNodeExists {
|
||||||
|
t.Fatalf("Node %q exists, it should not.", notAddedNodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NodeExists_Positive_NodeDoesntExist(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
nodeExists := dsw.NodeExists(nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if !nodeExists {
|
||||||
|
t.Fatalf("Node %q does not exist, it should.", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_VolumeExists_Positive_VolumeExistsNodeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
generatedVolumeName, _ := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumeExists := dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if !volumeExists {
|
||||||
|
t.Fatalf("Volume %q does not exist, it should.", generatedVolumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolumeName, volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_VolumeExists_Positive_VolumeDoesntExistNodeExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
podName := "pod-name"
|
||||||
|
volume1Name := "volume1-name"
|
||||||
|
volume1Spec := controllervolumetesting.GetTestVolumeSpec(volume1Name, volume1Name)
|
||||||
|
generatedVolume1Name, podAddErr := dsw.AddPod(podName, volume1Spec, nodeName)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
podName,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
volume2Name := "volume2-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumeExists := dsw.VolumeExists(volume2Name, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf("Volume %q exists, it should not.", volume2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 1 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <1> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, nodeName, generatedVolume1Name, volume1Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_VolumeExists_Positive_VolumeDoesntExistNodeDoesntExists(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
nodeName := "node-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf("Volume %q exists, it should not.", volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetVolumesToAttach_Positive_NoNodes(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(volumesToAttach) > 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetVolumesToAttach_Positive_TwoNodes(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
node2Name := "node2-name"
|
||||||
|
dsw.AddNode(node1Name)
|
||||||
|
dsw.AddNode(node2Name)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(volumesToAttach) != 0 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <0> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetVolumesToAttach_Positive_TwoNodesOneVolumeEach(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
pod1Name := "pod1-name"
|
||||||
|
volume1Name := "volume1-name"
|
||||||
|
volume1Spec := controllervolumetesting.GetTestVolumeSpec(volume1Name, volume1Name)
|
||||||
|
dsw.AddNode(node1Name)
|
||||||
|
generatedVolume1Name, podAddErr := dsw.AddPod(pod1Name, volume1Spec, node1Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod1Name,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
node2Name := "node2-name"
|
||||||
|
pod2Name := "pod2-name"
|
||||||
|
volume2Name := "volume2-name"
|
||||||
|
volume2Spec := controllervolumetesting.GetTestVolumeSpec(volume2Name, volume2Name)
|
||||||
|
dsw.AddNode(node2Name)
|
||||||
|
generatedVolume2Name, podAddErr := dsw.AddPod(pod2Name, volume2Spec, node2Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod2Name,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(volumesToAttach) != 2 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <2> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolume1Name, volume1Name)
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node2Name, generatedVolume2Name, volume2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetVolumesToAttach_Positive_TwoNodesOneVolumeEachExtraPod(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
pod1Name := "pod1-name"
|
||||||
|
volume1Name := "volume1-name"
|
||||||
|
volume1Spec := controllervolumetesting.GetTestVolumeSpec(volume1Name, volume1Name)
|
||||||
|
dsw.AddNode(node1Name)
|
||||||
|
generatedVolume1Name, podAddErr := dsw.AddPod(pod1Name, volume1Spec, node1Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod1Name,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
node2Name := "node2-name"
|
||||||
|
pod2Name := "pod2-name"
|
||||||
|
volume2Name := "volume2-name"
|
||||||
|
volume2Spec := controllervolumetesting.GetTestVolumeSpec(volume2Name, volume2Name)
|
||||||
|
dsw.AddNode(node2Name)
|
||||||
|
generatedVolume2Name, podAddErr := dsw.AddPod(pod2Name, volume2Spec, node2Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod2Name,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
pod3Name := "pod3-name"
|
||||||
|
dsw.AddPod(pod3Name, volume2Spec, node2Name)
|
||||||
|
_, podAddErr = dsw.AddPod(pod3Name, volume2Spec, node2Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod3Name,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(volumesToAttach) != 2 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <2> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolume1Name, volume1Name)
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node2Name, generatedVolume2Name, volume2Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetVolumesToAttach_Positive_TwoNodesThreeVolumes(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, _ := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
node1Name := "node1-name"
|
||||||
|
pod1Name := "pod1-name"
|
||||||
|
volume1Name := "volume1-name"
|
||||||
|
volume1Spec := controllervolumetesting.GetTestVolumeSpec(volume1Name, volume1Name)
|
||||||
|
dsw.AddNode(node1Name)
|
||||||
|
generatedVolume1Name, podAddErr := dsw.AddPod(pod1Name, volume1Spec, node1Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod1Name,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
node2Name := "node2-name"
|
||||||
|
pod2aName := "pod2a-name"
|
||||||
|
volume2Name := "volume2-name"
|
||||||
|
volume2Spec := controllervolumetesting.GetTestVolumeSpec(volume2Name, volume2Name)
|
||||||
|
dsw.AddNode(node2Name)
|
||||||
|
generatedVolume2Name1, podAddErr := dsw.AddPod(pod2aName, volume2Spec, node2Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod2aName,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
pod2bName := "pod2b-name"
|
||||||
|
generatedVolume2Name2, podAddErr := dsw.AddPod(pod2bName, volume2Spec, node2Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod2bName,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
if generatedVolume2Name1 != generatedVolume2Name2 {
|
||||||
|
t.Fatalf(
|
||||||
|
"Generated volume names for the same volume should be the same but they are not: %q and %q",
|
||||||
|
generatedVolume2Name1,
|
||||||
|
generatedVolume2Name2)
|
||||||
|
}
|
||||||
|
pod3Name := "pod3-name"
|
||||||
|
volume3Name := "volume3-name"
|
||||||
|
volume3Spec := controllervolumetesting.GetTestVolumeSpec(volume3Name, volume3Name)
|
||||||
|
generatedVolume3Name, podAddErr := dsw.AddPod(pod3Name, volume3Spec, node1Name)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"AddPod failed for pod %q. Expected: <no error> Actual: <%v>",
|
||||||
|
pod3Name,
|
||||||
|
podAddErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
volumesToAttach := dsw.GetVolumesToAttach()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
if len(volumesToAttach) != 3 {
|
||||||
|
t.Fatalf("len(volumesToAttach) Expected: <3> Actual: <%v>", len(volumesToAttach))
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolume1Name, volume1Name)
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node2Name, generatedVolume2Name1, volume2Name)
|
||||||
|
verifyVolumeToAttach(t, volumesToAttach, node1Name, generatedVolume3Name, volume3Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyVolumeToAttach(
|
||||||
|
t *testing.T,
|
||||||
|
volumesToAttach []VolumeToAttach,
|
||||||
|
expectedNodeName,
|
||||||
|
expectedVolumeName,
|
||||||
|
expectedVolumeSpecName string) {
|
||||||
|
for _, volumeToAttach := range volumesToAttach {
|
||||||
|
if volumeToAttach.NodeName == expectedNodeName &&
|
||||||
|
volumeToAttach.VolumeName == expectedVolumeName &&
|
||||||
|
volumeToAttach.VolumeSpec.Name() == expectedVolumeSpecName {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("volumesToAttach (%v) should contain %q/%q. It does not.", volumesToAttach, expectedVolumeName, expectedNodeName)
|
||||||
|
}
|
118
pkg/controller/volume/reconciler/reconciler.go
Normal file
118
pkg/controller/volume/reconciler/reconciler.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 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"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/volume/attacherdetacher"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/volume/cache"
|
||||||
|
"k8s.io/kubernetes/pkg/util/wait"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reconciler runs a periodic loop to reconcile the desired state of the with
|
||||||
|
// the actual state of the world by triggering attach detach operations.
|
||||||
|
type Reconciler interface {
|
||||||
|
// Starts running the reconcilation loop which executes periodically, checks
|
||||||
|
// 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.
|
||||||
|
// loopPeriod is the ammount of time the reconciler loop waits between
|
||||||
|
// successive executions.
|
||||||
|
// maxSafeToDetachDuration is the max ammount of time the reconciler will wait
|
||||||
|
// for the volume to deatch, after this it will detach the volume anyway
|
||||||
|
// assuming the node is unavilable. If during this time the volume becomes used
|
||||||
|
// by a new pod, the detach request will be aborted and the timer cleared.
|
||||||
|
func NewReconciler(
|
||||||
|
loopPeriod time.Duration,
|
||||||
|
maxSafeToDetachDuration time.Duration,
|
||||||
|
desiredStateOfWorld cache.DesiredStateOfWorld,
|
||||||
|
actualStateOfWorld cache.ActualStateOfWorld,
|
||||||
|
attacherDetacher attacherdetacher.AttacherDetacher) Reconciler {
|
||||||
|
return &reconciler{
|
||||||
|
loopPeriod: loopPeriod,
|
||||||
|
maxSafeToDetachDuration: maxSafeToDetachDuration,
|
||||||
|
desiredStateOfWorld: desiredStateOfWorld,
|
||||||
|
actualStateOfWorld: actualStateOfWorld,
|
||||||
|
attacherDetacher: attacherDetacher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type reconciler struct {
|
||||||
|
loopPeriod time.Duration
|
||||||
|
maxSafeToDetachDuration time.Duration
|
||||||
|
desiredStateOfWorld cache.DesiredStateOfWorld
|
||||||
|
actualStateOfWorld cache.ActualStateOfWorld
|
||||||
|
attacherDetacher attacherdetacher.AttacherDetacher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reconciler) Run(stopCh <-chan struct{}) {
|
||||||
|
wait.Until(rc.reconciliationLoopFunc(), rc.loopPeriod, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *reconciler) reconciliationLoopFunc() func() {
|
||||||
|
return func() {
|
||||||
|
// 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 "safe to detach"
|
||||||
|
glog.V(12).Infof("Volume %q/Node %q is attached--touching.", volumeToAttach.VolumeName, volumeToAttach.NodeName)
|
||||||
|
_, err := rc.actualStateOfWorld.AddVolumeNode(
|
||||||
|
volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Unexpected error on actualStateOfWorld.AddVolumeNode(): %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Volume/Node doesn't exist, spawn a goroutine to attach it
|
||||||
|
glog.V(5).Infof("Triggering AttachVolume for volume %q to node %q", volumeToAttach.VolumeName, volumeToAttach.NodeName)
|
||||||
|
rc.attacherDetacher.AttachVolume(&volumeToAttach, rc.actualStateOfWorld)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure volumes that should be detached are detached.
|
||||||
|
for _, attachedVolume := range rc.actualStateOfWorld.GetAttachedVolumes() {
|
||||||
|
if !rc.desiredStateOfWorld.VolumeExists(
|
||||||
|
attachedVolume.VolumeName, attachedVolume.NodeName) {
|
||||||
|
// Volume exists in actual state of world but not desired
|
||||||
|
if attachedVolume.SafeToDetach {
|
||||||
|
glog.V(5).Infof("Triggering DetachVolume for volume %q to node %q", attachedVolume.VolumeName, attachedVolume.NodeName)
|
||||||
|
rc.attacherDetacher.DetachVolume(&attachedVolume, rc.actualStateOfWorld)
|
||||||
|
} else {
|
||||||
|
// If volume is not safe to detach wait a max amount of time before detaching any way.
|
||||||
|
timeElapsed, err := rc.actualStateOfWorld.MarkDesireToDetach(attachedVolume.VolumeName, attachedVolume.NodeName)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Unexpected error actualStateOfWorld.MarkDesireToDetach(): %v", err)
|
||||||
|
}
|
||||||
|
if timeElapsed > rc.maxSafeToDetachDuration {
|
||||||
|
glog.V(5).Infof("Triggering DetachVolume for volume %q to node %q. Volume is not safe to detach, but max wait time expired.", attachedVolume.VolumeName, attachedVolume.NodeName)
|
||||||
|
rc.attacherDetacher.DetachVolume(&attachedVolume, rc.actualStateOfWorld)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
365
pkg/controller/volume/reconciler/reconciler_test.go
Normal file
365
pkg/controller/volume/reconciler/reconciler_test.go
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 reconciler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/controller/volume/attacherdetacher"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/volume/cache"
|
||||||
|
controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/util/wait"
|
||||||
|
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reconcilerLoopPeriod time.Duration = 0 * time.Millisecond
|
||||||
|
maxSafeToDetachDuration time.Duration = 50 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Run_Positive_DoNothing(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
asw := cache.NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
|
||||||
|
reconciler := NewReconciler(
|
||||||
|
reconcilerLoopPeriod, maxSafeToDetachDuration, dsw, asw, ad)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
go reconciler.Run(wait.NeverStop)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
waitForNewAttacherCallCount(t, 0 /* expectedCallCount */, fakePlugin)
|
||||||
|
verifyNewAttacherCallCount(t, true /* expectZeroNewAttacherCallCount */, fakePlugin)
|
||||||
|
verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin)
|
||||||
|
waitForAttachCallCount(t, 0 /* expectedAttachCallCount */, fakePlugin)
|
||||||
|
waitForDetachCallCount(t, 0 /* expectedDetachCallCount */, fakePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
asw := cache.NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
|
||||||
|
reconciler := NewReconciler(
|
||||||
|
reconcilerLoopPeriod, maxSafeToDetachDuration, dsw, asw, ad)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, podErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
if podErr != nil {
|
||||||
|
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
go reconciler.Run(wait.NeverStop)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
waitForNewAttacherCallCount(t, 1 /* expectedCallCount */, fakePlugin)
|
||||||
|
waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin)
|
||||||
|
verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMarkVolume(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
asw := cache.NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
|
||||||
|
reconciler := NewReconciler(
|
||||||
|
reconcilerLoopPeriod, maxSafeToDetachDuration, dsw, asw, ad)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedVolumeName, podAddErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podAddErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
go reconciler.Run(wait.NeverStop)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
waitForNewAttacherCallCount(t, 1 /* expectedCallCount */, fakePlugin)
|
||||||
|
verifyNewAttacherCallCount(t, false /* expectZeroNewAttacherCallCount */, fakePlugin)
|
||||||
|
waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin)
|
||||||
|
verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin)
|
||||||
|
waitForDetachCallCount(t, 0 /* expectedDetachCallCount */, fakePlugin)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.DeletePod(podName, generatedVolumeName, nodeName)
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Deleted pod %q from volume %q/node %q. Volume should also be deleted but it still exists.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
asw.MarkVolumeNodeSafeToDetach(generatedVolumeName, nodeName)
|
||||||
|
|
||||||
|
// Assert -- Marked SafeToDetach
|
||||||
|
waitForNewDetacherCallCount(t, 1 /* expectedCallCount */, fakePlugin)
|
||||||
|
verifyNewAttacherCallCount(t, false /* expectZeroNewAttacherCallCount */, fakePlugin)
|
||||||
|
waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin)
|
||||||
|
verifyNewDetacherCallCount(t, false /* expectZeroNewDetacherCallCount */, fakePlugin)
|
||||||
|
waitForDetachCallCount(t, 1 /* expectedDetachCallCount */, fakePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithoutMarkVolume(t *testing.T) {
|
||||||
|
// Arrange
|
||||||
|
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
|
||||||
|
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
|
||||||
|
asw := cache.NewActualStateOfWorld(volumePluginMgr)
|
||||||
|
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
|
||||||
|
reconciler := NewReconciler(
|
||||||
|
reconcilerLoopPeriod, maxSafeToDetachDuration, dsw, asw, ad)
|
||||||
|
podName := "pod-name"
|
||||||
|
volumeName := "volume-name"
|
||||||
|
volumeSpec := controllervolumetesting.GetTestVolumeSpec(volumeName, volumeName)
|
||||||
|
nodeName := "node-name"
|
||||||
|
dsw.AddNode(nodeName)
|
||||||
|
volumeExists := dsw.VolumeExists(volumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Volume %q/node %q should not exist, but it does.",
|
||||||
|
volumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedVolumeName, podAddErr := dsw.AddPod(podName, volumeSpec, nodeName)
|
||||||
|
if podAddErr != nil {
|
||||||
|
t.Fatalf("AddPod failed. Expected: <no error> Actual: <%v>", podAddErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
go reconciler.Run(wait.NeverStop)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
waitForNewAttacherCallCount(t, 1 /* expectedCallCount */, fakePlugin)
|
||||||
|
verifyNewAttacherCallCount(t, false /* expectZeroNewAttacherCallCount */, fakePlugin)
|
||||||
|
waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin)
|
||||||
|
verifyNewDetacherCallCount(t, true /* expectZeroNewDetacherCallCount */, fakePlugin)
|
||||||
|
waitForDetachCallCount(t, 0 /* expectedDetachCallCount */, fakePlugin)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
dsw.DeletePod(podName, generatedVolumeName, nodeName)
|
||||||
|
volumeExists = dsw.VolumeExists(generatedVolumeName, nodeName)
|
||||||
|
if volumeExists {
|
||||||
|
t.Fatalf(
|
||||||
|
"Deleted pod %q from volume %q/node %q. Volume should also be deleted but it still exists.",
|
||||||
|
podName,
|
||||||
|
generatedVolumeName,
|
||||||
|
nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert -- Timer will triger detach
|
||||||
|
waitForNewDetacherCallCount(t, 1 /* expectedCallCount */, fakePlugin)
|
||||||
|
verifyNewAttacherCallCount(t, false /* expectZeroNewAttacherCallCount */, fakePlugin)
|
||||||
|
waitForAttachCallCount(t, 1 /* expectedAttachCallCount */, fakePlugin)
|
||||||
|
verifyNewDetacherCallCount(t, false /* expectZeroNewDetacherCallCount */, fakePlugin)
|
||||||
|
waitForDetachCallCount(t, 1 /* expectedDetachCallCount */, fakePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForNewAttacherCallCount(
|
||||||
|
t *testing.T,
|
||||||
|
expectedCallCount int,
|
||||||
|
fakePlugin *volumetesting.FakeVolumePlugin) {
|
||||||
|
err := retryWithExponentialBackOff(
|
||||||
|
time.Duration(5*time.Millisecond),
|
||||||
|
func() (bool, error) {
|
||||||
|
actualCallCount := fakePlugin.GetNewAttacherCallCount()
|
||||||
|
if actualCallCount >= expectedCallCount {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"Warning: Wrong NewAttacherCallCount. Expected: <%v> Actual: <%v>. Will retry.",
|
||||||
|
expectedCallCount,
|
||||||
|
actualCallCount)
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"Timed out waiting for NewAttacherCallCount. Expected: <%v> Actual: <%v>",
|
||||||
|
expectedCallCount,
|
||||||
|
fakePlugin.GetNewAttacherCallCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForNewDetacherCallCount(
|
||||||
|
t *testing.T,
|
||||||
|
expectedCallCount int,
|
||||||
|
fakePlugin *volumetesting.FakeVolumePlugin) {
|
||||||
|
err := retryWithExponentialBackOff(
|
||||||
|
time.Duration(5*time.Millisecond),
|
||||||
|
func() (bool, error) {
|
||||||
|
actualCallCount := fakePlugin.GetNewDetacherCallCount()
|
||||||
|
if actualCallCount >= expectedCallCount {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"Warning: Wrong NewDetacherCallCount. Expected: <%v> Actual: <%v>. Will retry.",
|
||||||
|
expectedCallCount,
|
||||||
|
actualCallCount)
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"Timed out waiting for NewDetacherCallCount. Expected: <%v> Actual: <%v>",
|
||||||
|
expectedCallCount,
|
||||||
|
fakePlugin.GetNewDetacherCallCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForAttachCallCount(
|
||||||
|
t *testing.T,
|
||||||
|
expectedAttachCallCount int,
|
||||||
|
fakePlugin *volumetesting.FakeVolumePlugin) {
|
||||||
|
if len(fakePlugin.Attachers) == 0 && expectedAttachCallCount == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := retryWithExponentialBackOff(
|
||||||
|
time.Duration(5*time.Millisecond),
|
||||||
|
func() (bool, error) {
|
||||||
|
for i, attacher := range fakePlugin.Attachers {
|
||||||
|
actualCallCount := attacher.GetAttachCallCount()
|
||||||
|
if actualCallCount == expectedAttachCallCount {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"Warning: Wrong attacher[%v].GetAttachCallCount(). Expected: <%v> Actual: <%v>. Will try next attacher.",
|
||||||
|
i,
|
||||||
|
expectedAttachCallCount,
|
||||||
|
actualCallCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf(
|
||||||
|
"Warning: No attachers have expected AttachCallCount. Expected: <%v>. Will retry.",
|
||||||
|
expectedAttachCallCount)
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"No attachers have expected AttachCallCount. Expected: <%v>",
|
||||||
|
expectedAttachCallCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForDetachCallCount(
|
||||||
|
t *testing.T,
|
||||||
|
expectedDetachCallCount int,
|
||||||
|
fakePlugin *volumetesting.FakeVolumePlugin) {
|
||||||
|
if len(fakePlugin.Detachers) == 0 && expectedDetachCallCount == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := retryWithExponentialBackOff(
|
||||||
|
time.Duration(5*time.Millisecond),
|
||||||
|
func() (bool, error) {
|
||||||
|
for i, detacher := range fakePlugin.Detachers {
|
||||||
|
actualCallCount := detacher.GetDetachCallCount()
|
||||||
|
if actualCallCount == expectedDetachCallCount {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"Wrong detacher[%v].GetDetachCallCount(). Expected: <%v> Actual: <%v>. Will try next detacher.",
|
||||||
|
i,
|
||||||
|
expectedDetachCallCount,
|
||||||
|
actualCallCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf(
|
||||||
|
"Warning: No detachers have expected DetachCallCount. Expected: <%v>. Will retry.",
|
||||||
|
expectedDetachCallCount)
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"No detachers have expected DetachCallCount. Expected: <%v>",
|
||||||
|
expectedDetachCallCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyNewAttacherCallCount(
|
||||||
|
t *testing.T,
|
||||||
|
expectZeroNewAttacherCallCount bool,
|
||||||
|
fakePlugin *volumetesting.FakeVolumePlugin) {
|
||||||
|
|
||||||
|
if expectZeroNewAttacherCallCount &&
|
||||||
|
fakePlugin.GetNewAttacherCallCount() != 0 {
|
||||||
|
t.Fatalf(
|
||||||
|
"Wrong NewAttacherCallCount. Expected: <0> Actual: <%v>",
|
||||||
|
fakePlugin.GetNewAttacherCallCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyNewDetacherCallCount(
|
||||||
|
t *testing.T,
|
||||||
|
expectZeroNewDetacherCallCount bool,
|
||||||
|
fakePlugin *volumetesting.FakeVolumePlugin) {
|
||||||
|
|
||||||
|
if expectZeroNewDetacherCallCount &&
|
||||||
|
fakePlugin.GetNewDetacherCallCount() != 0 {
|
||||||
|
t.Fatalf("Wrong NewDetacherCallCount. Expected: <0> Actual: <%v>",
|
||||||
|
fakePlugin.GetNewDetacherCallCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retryWithExponentialBackOff(initialDuration time.Duration, fn wait.ConditionFunc) error {
|
||||||
|
backoff := wait.Backoff{
|
||||||
|
Duration: initialDuration,
|
||||||
|
Factor: 3,
|
||||||
|
Jitter: 0,
|
||||||
|
Steps: 6,
|
||||||
|
}
|
||||||
|
return wait.ExponentialBackoff(backoff, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Logf("asw: %v", asw.GetAttachedVolumes())
|
||||||
|
// t.Logf("dsw: %v", dsw.GetVolumesToAttach())
|
102
pkg/controller/volume/testing/testvolumepluginmgr.go
Normal file
102
pkg/controller/volume/testing/testvolumepluginmgr.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||||
|
"k8s.io/kubernetes/pkg/cloudprovider/providers/fake"
|
||||||
|
"k8s.io/kubernetes/pkg/types"
|
||||||
|
"k8s.io/kubernetes/pkg/util/io"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTestVolumePluginMgr creates, initializes, and returns a test volume
|
||||||
|
// plugin manager.
|
||||||
|
func GetTestVolumePluginMgr(t *testing.T) (*volume.VolumePluginMgr, *volumetesting.FakeVolumePlugin) {
|
||||||
|
plugins := []volume.VolumePlugin{}
|
||||||
|
|
||||||
|
// plugins = append(plugins, aws_ebs.ProbeVolumePlugins()...)
|
||||||
|
// plugins = append(plugins, gce_pd.ProbeVolumePlugins()...)
|
||||||
|
// plugins = append(plugins, cinder.ProbeVolumePlugins()...)
|
||||||
|
volumeTestingPlugins := volumetesting.ProbeVolumePlugins(volume.VolumeConfig{})
|
||||||
|
plugins = append(plugins, volumeTestingPlugins...)
|
||||||
|
|
||||||
|
volumePluginMgr := testVolumePluginMgr{}
|
||||||
|
|
||||||
|
if err := volumePluginMgr.InitPlugins(plugins, &volumePluginMgr); err != nil {
|
||||||
|
t.Fatalf("Could not initialize volume plugins for Attach/Detach Controller: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &volumePluginMgr.VolumePluginMgr, volumeTestingPlugins[0].(*volumetesting.FakeVolumePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testVolumePluginMgr struct {
|
||||||
|
volume.VolumePluginMgr
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeHost implementation
|
||||||
|
// This is an unfortunate requirement of the current factoring of volume plugin
|
||||||
|
// initializing code. It requires kubelet specific methods used by the mounting
|
||||||
|
// code to be implemented by all initializers even if the initializer does not
|
||||||
|
// do mounting (like this attach/detach controller).
|
||||||
|
// Issue kubernetes/kubernetes/issues/14217 to fix this.
|
||||||
|
func (vpm *testVolumePluginMgr) GetPluginDir(podUID string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) GetKubeClient() internalclientset.Interface {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) NewWrapperMounter(volName string, spec volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
|
||||||
|
return nil, fmt.Errorf("NewWrapperMounter not supported by Attach/Detach controller's VolumeHost implementation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) {
|
||||||
|
return nil, fmt.Errorf("NewWrapperUnmounter not supported by Attach/Detach controller's VolumeHost implementation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) GetCloudProvider() cloudprovider.Interface {
|
||||||
|
return &fake.FakeCloud{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) GetMounter() mount.Interface {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) GetWriter() io.Writer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vpm *testVolumePluginMgr) GetHostName() string {
|
||||||
|
return ""
|
||||||
|
}
|
38
pkg/controller/volume/testing/testvolumespec.go
Normal file
38
pkg/controller/volume/testing/testvolumespec.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTestVolumeSpec returns a test volume spec
|
||||||
|
func GetTestVolumeSpec(volumeName, diskName string) *volume.Spec {
|
||||||
|
return &volume.Spec{
|
||||||
|
Volume: &api.Volume{
|
||||||
|
Name: volumeName,
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
|
||||||
|
PDName: diskName,
|
||||||
|
FSType: "fake",
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,24 @@ func (plugin *gcePersistentDiskPlugin) NewAttacher() (volume.Attacher, error) {
|
|||||||
return &gcePersistentDiskAttacher{host: plugin.host}, nil
|
return &gcePersistentDiskAttacher{host: plugin.host}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (plugin *gcePersistentDiskPlugin) GetUniqueVolumeName(spec *volume.Spec) (string, error) {
|
||||||
|
volumeSource, _ := getVolumeSource(spec)
|
||||||
|
if volumeSource == nil {
|
||||||
|
return "", fmt.Errorf("Spec does not reference a GCE volume type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s:%v", gcePersistentDiskPluginName, volumeSource.PDName, volumeSource.ReadOnly), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *gcePersistentDiskPlugin) GetDeviceName(spec *volume.Spec) (string, error) {
|
||||||
|
volumeSource, _ := getVolumeSource(spec)
|
||||||
|
if volumeSource == nil {
|
||||||
|
return "", fmt.Errorf("Spec does not reference a GCE volume type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumeSource.PDName, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (attacher *gcePersistentDiskAttacher) Attach(spec *volume.Spec, hostName string) error {
|
func (attacher *gcePersistentDiskAttacher) Attach(spec *volume.Spec, hostName string) error {
|
||||||
volumeSource, readOnly := getVolumeSource(spec)
|
volumeSource, readOnly := getVolumeSource(spec)
|
||||||
pdName := volumeSource.PDName
|
pdName := volumeSource.PDName
|
||||||
|
@ -133,6 +133,18 @@ type AttachableVolumePlugin interface {
|
|||||||
VolumePlugin
|
VolumePlugin
|
||||||
NewAttacher() (Attacher, error)
|
NewAttacher() (Attacher, error)
|
||||||
NewDetacher() (Detacher, error)
|
NewDetacher() (Detacher, error)
|
||||||
|
|
||||||
|
// GetUniqueVolumeName returns a unique name representing the volume
|
||||||
|
// defined in spec. e.g. pluginname-deviceName-readwrite
|
||||||
|
// This helps ensures that the same operation (attach/detach) is never
|
||||||
|
// started on the same volume.
|
||||||
|
// If the plugin does not support the given spec, this returns an error.
|
||||||
|
GetUniqueVolumeName(spec *Spec) (string, error)
|
||||||
|
|
||||||
|
// GetDeviceName returns the name or ID of the device referenced in the
|
||||||
|
// specified volume spec. This is passed by callers to the Deatch method.
|
||||||
|
// If the plugin does not support the given spec, this returns an error.
|
||||||
|
GetDeviceName(spec *Spec) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VolumeHost is an interface that plugins can use to access the kubelet.
|
// VolumeHost is an interface that plugins can use to access the kubelet.
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
@ -132,6 +133,7 @@ func ProbeVolumePlugins(config VolumeConfig) []VolumePlugin {
|
|||||||
// Use as:
|
// Use as:
|
||||||
// volume.RegisterPlugin(&FakePlugin{"fake-name"})
|
// volume.RegisterPlugin(&FakePlugin{"fake-name"})
|
||||||
type FakeVolumePlugin struct {
|
type FakeVolumePlugin struct {
|
||||||
|
sync.RWMutex
|
||||||
PluginName string
|
PluginName string
|
||||||
Host VolumeHost
|
Host VolumeHost
|
||||||
Config VolumeConfig
|
Config VolumeConfig
|
||||||
@ -158,11 +160,15 @@ func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) Init(host VolumeHost) error {
|
func (plugin *FakeVolumePlugin) Init(host VolumeHost) error {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
plugin.Host = host
|
plugin.Host = host
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) Name() string {
|
func (plugin *FakeVolumePlugin) Name() string {
|
||||||
|
plugin.RLock()
|
||||||
|
defer plugin.RUnlock()
|
||||||
return plugin.PluginName
|
return plugin.PluginName
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +178,8 @@ func (plugin *FakeVolumePlugin) CanSupport(spec *Spec) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) NewMounter(spec *Spec, pod *api.Pod, opts VolumeOptions) (Mounter, error) {
|
func (plugin *FakeVolumePlugin) NewMounter(spec *Spec, pod *api.Pod, opts VolumeOptions) (Mounter, error) {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
volume := plugin.getFakeVolume(&plugin.Mounters)
|
volume := plugin.getFakeVolume(&plugin.Mounters)
|
||||||
volume.PodUID = pod.UID
|
volume.PodUID = pod.UID
|
||||||
volume.VolName = spec.Name()
|
volume.VolName = spec.Name()
|
||||||
@ -181,6 +189,8 @@ func (plugin *FakeVolumePlugin) NewMounter(spec *Spec, pod *api.Pod, opts Volume
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) NewUnmounter(volName string, podUID types.UID) (Unmounter, error) {
|
func (plugin *FakeVolumePlugin) NewUnmounter(volName string, podUID types.UID) (Unmounter, error) {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
volume := plugin.getFakeVolume(&plugin.Unmounters)
|
volume := plugin.getFakeVolume(&plugin.Unmounters)
|
||||||
volume.PodUID = podUID
|
volume.PodUID = podUID
|
||||||
volume.VolName = volName
|
volume.VolName = volName
|
||||||
@ -190,15 +200,41 @@ func (plugin *FakeVolumePlugin) NewUnmounter(volName string, podUID types.UID) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) NewAttacher() (Attacher, error) {
|
func (plugin *FakeVolumePlugin) NewAttacher() (Attacher, error) {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
plugin.NewAttacherCallCount = plugin.NewAttacherCallCount + 1
|
plugin.NewAttacherCallCount = plugin.NewAttacherCallCount + 1
|
||||||
return plugin.getFakeVolume(&plugin.Attachers), nil
|
return plugin.getFakeVolume(&plugin.Attachers), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (plugin *FakeVolumePlugin) GetNewAttacherCallCount() int {
|
||||||
|
plugin.RLock()
|
||||||
|
defer plugin.RUnlock()
|
||||||
|
return plugin.NewAttacherCallCount
|
||||||
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) NewDetacher() (Detacher, error) {
|
func (plugin *FakeVolumePlugin) NewDetacher() (Detacher, error) {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
plugin.NewDetacherCallCount = plugin.NewDetacherCallCount + 1
|
plugin.NewDetacherCallCount = plugin.NewDetacherCallCount + 1
|
||||||
return plugin.getFakeVolume(&plugin.Detachers), nil
|
return plugin.getFakeVolume(&plugin.Detachers), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (plugin *FakeVolumePlugin) GetNewDetacherCallCount() int {
|
||||||
|
plugin.RLock()
|
||||||
|
defer plugin.RUnlock()
|
||||||
|
return plugin.NewDetacherCallCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *FakeVolumePlugin) GetUniqueVolumeName(spec *Spec) (string, error) {
|
||||||
|
plugin.RLock()
|
||||||
|
defer plugin.RUnlock()
|
||||||
|
return plugin.Name() + "/" + spec.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *FakeVolumePlugin) GetDeviceName(spec *Spec) (string, error) {
|
||||||
|
return spec.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) NewRecycler(pvName string, spec *Spec) (Recycler, error) {
|
func (plugin *FakeVolumePlugin) NewRecycler(pvName string, spec *Spec) (Recycler, error) {
|
||||||
return &fakeRecycler{"/attributesTransferredFromSpec", MetricsNil{}}, nil
|
return &fakeRecycler{"/attributesTransferredFromSpec", MetricsNil{}}, nil
|
||||||
}
|
}
|
||||||
@ -208,6 +244,8 @@ func (plugin *FakeVolumePlugin) NewDeleter(spec *Spec) (Deleter, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *FakeVolumePlugin) NewProvisioner(options VolumeOptions) (Provisioner, error) {
|
func (plugin *FakeVolumePlugin) NewProvisioner(options VolumeOptions) (Provisioner, error) {
|
||||||
|
plugin.Lock()
|
||||||
|
defer plugin.Unlock()
|
||||||
plugin.LastProvisionerOptions = options
|
plugin.LastProvisionerOptions = options
|
||||||
return &FakeProvisioner{options, plugin.Host}, nil
|
return &FakeProvisioner{options, plugin.Host}, nil
|
||||||
}
|
}
|
||||||
@ -217,6 +255,7 @@ func (plugin *FakeVolumePlugin) GetAccessModes() []api.PersistentVolumeAccessMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeVolume struct {
|
type FakeVolume struct {
|
||||||
|
sync.RWMutex
|
||||||
PodUID types.UID
|
PodUID types.UID
|
||||||
VolName string
|
VolName string
|
||||||
Plugin *FakeVolumePlugin
|
Plugin *FakeVolumePlugin
|
||||||
@ -242,8 +281,10 @@ func (_ *FakeVolume) GetAttributes() Attributes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) SetUp(fsGroup *int64) error {
|
func (fv *FakeVolume) SetUp(fsGroup *int64) error {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.SetUpCallCount++
|
fv.SetUpCallCount++
|
||||||
return fv.SetUpAt(fv.GetPath(), fsGroup)
|
return fv.SetUpAt(fv.getPath(), fsGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) SetUpAt(dir string, fsGroup *int64) error {
|
func (fv *FakeVolume) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
@ -251,12 +292,20 @@ func (fv *FakeVolume) SetUpAt(dir string, fsGroup *int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) GetPath() string {
|
func (fv *FakeVolume) GetPath() string {
|
||||||
|
fv.RLock()
|
||||||
|
defer fv.RUnlock()
|
||||||
|
return fv.getPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fv *FakeVolume) getPath() string {
|
||||||
return path.Join(fv.Plugin.Host.GetPodVolumeDir(fv.PodUID, utilstrings.EscapeQualifiedNameForDisk(fv.Plugin.PluginName), fv.VolName))
|
return path.Join(fv.Plugin.Host.GetPodVolumeDir(fv.PodUID, utilstrings.EscapeQualifiedNameForDisk(fv.Plugin.PluginName), fv.VolName))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) TearDown() error {
|
func (fv *FakeVolume) TearDown() error {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.TearDownCallCount++
|
fv.TearDownCallCount++
|
||||||
return fv.TearDownAt(fv.GetPath())
|
return fv.TearDownAt(fv.getPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) TearDownAt(dir string) error {
|
func (fv *FakeVolume) TearDownAt(dir string) error {
|
||||||
@ -264,36 +313,62 @@ func (fv *FakeVolume) TearDownAt(dir string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) Attach(spec *Spec, hostName string) error {
|
func (fv *FakeVolume) Attach(spec *Spec, hostName string) error {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.AttachCallCount++
|
fv.AttachCallCount++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fv *FakeVolume) GetAttachCallCount() int {
|
||||||
|
fv.RLock()
|
||||||
|
defer fv.RUnlock()
|
||||||
|
return fv.AttachCallCount
|
||||||
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) WaitForAttach(spec *Spec, spectimeout time.Duration) (string, error) {
|
func (fv *FakeVolume) WaitForAttach(spec *Spec, spectimeout time.Duration) (string, error) {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.WaitForAttachCallCount++
|
fv.WaitForAttachCallCount++
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) GetDeviceMountPath(spec *Spec) string {
|
func (fv *FakeVolume) GetDeviceMountPath(spec *Spec) string {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.GetDeviceMountPathCallCount++
|
fv.GetDeviceMountPathCallCount++
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) MountDevice(spec *Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
|
func (fv *FakeVolume) MountDevice(spec *Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.MountDeviceCallCount++
|
fv.MountDeviceCallCount++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) Detach(deviceMountPath string, hostName string) error {
|
func (fv *FakeVolume) Detach(deviceMountPath string, hostName string) error {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.DetachCallCount++
|
fv.DetachCallCount++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fv *FakeVolume) GetDetachCallCount() int {
|
||||||
|
fv.RLock()
|
||||||
|
defer fv.RUnlock()
|
||||||
|
return fv.DetachCallCount
|
||||||
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) WaitForDetach(devicePath string, timeout time.Duration) error {
|
func (fv *FakeVolume) WaitForDetach(devicePath string, timeout time.Duration) error {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.WaitForDetachCallCount++
|
fv.WaitForDetachCallCount++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) UnmountDevice(globalMountPath string, mounter mount.Interface) error {
|
func (fv *FakeVolume) UnmountDevice(globalMountPath string, mounter mount.Interface) error {
|
||||||
|
fv.Lock()
|
||||||
|
defer fv.Unlock()
|
||||||
fv.UnmountDeviceCallCount++
|
fv.UnmountDeviceCallCount++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -291,7 +291,18 @@ func createClients(s *httptest.Server) (*clientset.Clientset, *persistentvolumec
|
|||||||
// creates many claims and default values were too low.
|
// creates many claims and default values were too low.
|
||||||
testClient := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, QPS: 1000, Burst: 100000})
|
testClient := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, QPS: 1000, Burst: 100000})
|
||||||
host := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil)
|
host := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil)
|
||||||
plugins := []volume.VolumePlugin{&volumetest.FakeVolumePlugin{"plugin-name", host, volume.VolumeConfig{}, volume.VolumeOptions{}, 0, 0, nil, nil, nil, nil}}
|
plugins := []volume.VolumePlugin{&volumetest.FakeVolumePlugin{
|
||||||
|
PluginName: "plugin-name",
|
||||||
|
Host: host,
|
||||||
|
Config: volume.VolumeConfig{},
|
||||||
|
LastProvisionerOptions: volume.VolumeOptions{},
|
||||||
|
NewAttacherCallCount: 0,
|
||||||
|
NewDetacherCallCount: 0,
|
||||||
|
Mounters: nil,
|
||||||
|
Unmounters: nil,
|
||||||
|
Attachers: nil,
|
||||||
|
Detachers: nil,
|
||||||
|
}}
|
||||||
cloud := &fake_cloud.FakeCloud{}
|
cloud := &fake_cloud.FakeCloud{}
|
||||||
ctrl := persistentvolumecontroller.NewPersistentVolumeController(testClient, 10*time.Second, nil, plugins, cloud, "", nil, nil, nil)
|
ctrl := persistentvolumecontroller.NewPersistentVolumeController(testClient, 10*time.Second, nil, plugins, cloud, "", nil, nil, nil)
|
||||||
return testClient, ctrl
|
return testClient, ctrl
|
||||||
|
Loading…
Reference in New Issue
Block a user