@@ -18,6 +18,7 @@ package volumerestrictions
1818
1919import (
2020 "context"
21+ "fmt"
2122
2223 v1 "k8s.io/api/core/v1"
2324 apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -40,17 +41,57 @@ type VolumeRestrictions struct {
4041var _ framework.PreFilterPlugin = & VolumeRestrictions {}
4142var _ framework.FilterPlugin = & VolumeRestrictions {}
4243var _ framework.EnqueueExtensions = & VolumeRestrictions {}
43-
44- // Name is the name of the plugin used in the plugin registry and configurations.
45- const Name = names .VolumeRestrictions
44+ var _ framework.StateData = & preFilterState {}
4645
4746const (
47+ // Name is the name of the plugin used in the plugin registry and configurations.
48+ Name = names .VolumeRestrictions
49+ // preFilterStateKey is the key in CycleState to VolumeRestrictions pre-computed data for Filtering.
50+ // Using the name of the plugin will likely help us avoid collisions with other plugins.
51+ preFilterStateKey = "PreFilter" + Name
52+
4853 // ErrReasonDiskConflict is used for NoDiskConflict predicate error.
4954 ErrReasonDiskConflict = "node(s) had no available disk"
5055 // ErrReasonReadWriteOncePodConflict is used when a pod is found using the same PVC with the ReadWriteOncePod access mode.
5156 ErrReasonReadWriteOncePodConflict = "node has pod using PersistentVolumeClaim with the same name and ReadWriteOncePod access mode"
5257)
5358
59+ // preFilterState computed at PreFilter and used at Filter.
60+ type preFilterState struct {
61+ // Names of the pod's volumes using the ReadWriteOncePod access mode.
62+ readWriteOncePodPVCs sets.Set [string ]
63+ // The number of references to these ReadWriteOncePod volumes by scheduled pods.
64+ conflictingPVCRefCount int
65+ }
66+
67+ func (s * preFilterState ) updateWithPod (podInfo * framework.PodInfo , multiplier int ) {
68+ s .conflictingPVCRefCount += multiplier * s .conflictingPVCRefCountForPod (podInfo )
69+ }
70+
71+ func (s * preFilterState ) conflictingPVCRefCountForPod (podInfo * framework.PodInfo ) int {
72+ conflicts := 0
73+ for _ , volume := range podInfo .Pod .Spec .Volumes {
74+ if volume .PersistentVolumeClaim == nil {
75+ continue
76+ }
77+ if s .readWriteOncePodPVCs .Has (volume .PersistentVolumeClaim .ClaimName ) {
78+ conflicts += 1
79+ }
80+ }
81+ return conflicts
82+ }
83+
84+ // Clone the prefilter state.
85+ func (s * preFilterState ) Clone () framework.StateData {
86+ if s == nil {
87+ return nil
88+ }
89+ return & preFilterState {
90+ readWriteOncePodPVCs : s .readWriteOncePodPVCs ,
91+ conflictingPVCRefCount : s .conflictingPVCRefCount ,
92+ }
93+ }
94+
5495// Name returns name of the plugin. It is used in logs, etc.
5596func (pl * VolumeRestrictions ) Name () string {
5697 return Name
@@ -117,46 +158,138 @@ func haveOverlap(a1, a2 []string) bool {
117158 return false
118159}
119160
161+ // PreFilter computes and stores cycleState containing details for enforcing ReadWriteOncePod.
120162func (pl * VolumeRestrictions ) PreFilter (ctx context.Context , cycleState * framework.CycleState , pod * v1.Pod ) (* framework.PreFilterResult , * framework.Status ) {
121- if pl .enableReadWriteOncePod {
122- return nil , pl .isReadWriteOncePodAccessModeConflict (ctx , pod )
163+ if ! pl .enableReadWriteOncePod {
164+ return nil , nil
165+ }
166+
167+ pvcs , err := pl .readWriteOncePodPVCsForPod (ctx , pod )
168+ if err != nil {
169+ if apierrors .IsNotFound (err ) {
170+ return nil , framework .NewStatus (framework .UnschedulableAndUnresolvable , err .Error ())
171+ }
172+ return nil , framework .AsStatus (err )
173+ }
174+
175+ s , err := pl .calPreFilterState (ctx , pod , pvcs )
176+ if err != nil {
177+ return nil , framework .AsStatus (err )
178+ }
179+ cycleState .Write (preFilterStateKey , s )
180+ return nil , nil
181+ }
182+
183+ // AddPod from pre-computed data in cycleState.
184+ func (pl * VolumeRestrictions ) AddPod (ctx context.Context , cycleState * framework.CycleState , podToSchedule * v1.Pod , podInfoToAdd * framework.PodInfo , nodeInfo * framework.NodeInfo ) * framework.Status {
185+ if ! pl .enableReadWriteOncePod {
186+ return nil
187+ }
188+ state , err := getPreFilterState (cycleState )
189+ if err != nil {
190+ return framework .AsStatus (err )
123191 }
124- return nil , framework .NewStatus (framework .Success )
192+ state .updateWithPod (podInfoToAdd , 1 )
193+ return nil
125194}
126195
127- // isReadWriteOncePodAccessModeConflict checks if a pod uses a PVC with the ReadWriteOncePod access mode.
128- // This access mode restricts volume access to a single pod on a single node. Since only a single pod can
129- // use a ReadWriteOncePod PVC, mark any other pods attempting to use this PVC as UnschedulableAndUnresolvable.
130- // TODO(#103132): Mark pod as Unschedulable and add preemption logic.
131- func (pl * VolumeRestrictions ) isReadWriteOncePodAccessModeConflict (ctx context.Context , pod * v1.Pod ) * framework.Status {
196+ // RemovePod from pre-computed data in cycleState.
197+ func (pl * VolumeRestrictions ) RemovePod (ctx context.Context , cycleState * framework.CycleState , podToSchedule * v1.Pod , podInfoToRemove * framework.PodInfo , nodeInfo * framework.NodeInfo ) * framework.Status {
198+ if ! pl .enableReadWriteOncePod {
199+ return nil
200+ }
201+ state , err := getPreFilterState (cycleState )
202+ if err != nil {
203+ return framework .AsStatus (err )
204+ }
205+ state .updateWithPod (podInfoToRemove , - 1 )
206+ return nil
207+ }
208+
209+ func getPreFilterState (cycleState * framework.CycleState ) (* preFilterState , error ) {
210+ c , err := cycleState .Read (preFilterStateKey )
211+ if err != nil {
212+ // preFilterState doesn't exist, likely PreFilter wasn't invoked.
213+ return nil , fmt .Errorf ("cannot read %q from cycleState" , preFilterStateKey )
214+ }
215+
216+ s , ok := c .(* preFilterState )
217+ if ! ok {
218+ return nil , fmt .Errorf ("%+v convert to volumerestrictions.state error" , c )
219+ }
220+ return s , nil
221+ }
222+
223+ // calPreFilterState computes preFilterState describing which PVCs use ReadWriteOncePod
224+ // and which pods in the cluster are in conflict.
225+ func (pl * VolumeRestrictions ) calPreFilterState (ctx context.Context , pod * v1.Pod , pvcs sets.Set [string ]) (* preFilterState , error ) {
226+ conflictingPVCRefCount := 0
227+ for pvc := range pvcs {
228+ key := framework .GetNamespacedName (pod .Namespace , pvc )
229+ if pl .sharedLister .StorageInfos ().IsPVCUsedByPods (key ) {
230+ // There can only be at most one pod using the ReadWriteOncePod PVC.
231+ conflictingPVCRefCount += 1
232+ }
233+ }
234+ return & preFilterState {
235+ readWriteOncePodPVCs : pvcs ,
236+ conflictingPVCRefCount : conflictingPVCRefCount ,
237+ }, nil
238+ }
239+
240+ func (pl * VolumeRestrictions ) readWriteOncePodPVCsForPod (ctx context.Context , pod * v1.Pod ) (sets.Set [string ], error ) {
241+ pvcs := sets .New [string ]()
132242 for _ , volume := range pod .Spec .Volumes {
133243 if volume .PersistentVolumeClaim == nil {
134244 continue
135245 }
136246
137247 pvc , err := pl .pvcLister .PersistentVolumeClaims (pod .Namespace ).Get (volume .PersistentVolumeClaim .ClaimName )
138248 if err != nil {
139- if apierrors .IsNotFound (err ) {
140- return framework .NewStatus (framework .UnschedulableAndUnresolvable , err .Error ())
141- }
142- return framework .AsStatus (err )
249+ return nil , err
143250 }
144251
145252 if ! v1helper .ContainsAccessMode (pvc .Spec .AccessModes , v1 .ReadWriteOncePod ) {
146253 continue
147254 }
255+ pvcs .Insert (pvc .Name )
256+ }
257+ return pvcs , nil
258+ }
148259
149- key := framework .GetNamespacedName (pod .Namespace , volume .PersistentVolumeClaim .ClaimName )
150- if pl .sharedLister .StorageInfos ().IsPVCUsedByPods (key ) {
151- return framework .NewStatus (framework .UnschedulableAndUnresolvable , ErrReasonReadWriteOncePodConflict )
260+ // Checks if scheduling the pod onto this node would cause any conflicts with
261+ // existing volumes.
262+ func satisfyVolumeConflicts (pod * v1.Pod , nodeInfo * framework.NodeInfo ) bool {
263+ for i := range pod .Spec .Volumes {
264+ v := & pod .Spec .Volumes [i ]
265+ // fast path if there is no conflict checking targets.
266+ if v .GCEPersistentDisk == nil && v .AWSElasticBlockStore == nil && v .RBD == nil && v .ISCSI == nil {
267+ continue
268+ }
269+
270+ for _ , ev := range nodeInfo .Pods {
271+ if isVolumeConflict (v , ev .Pod ) {
272+ return false
273+ }
152274 }
153275 }
276+ return true
277+ }
154278
279+ // Checks if scheduling the pod would cause any ReadWriteOncePod PVC access mode conflicts.
280+ func satisfyReadWriteOncePod (ctx context.Context , state * preFilterState ) * framework.Status {
281+ if state == nil {
282+ return nil
283+ }
284+ if state .conflictingPVCRefCount > 0 {
285+ return framework .NewStatus (framework .Unschedulable , ErrReasonReadWriteOncePodConflict )
286+ }
155287 return nil
156288}
157289
290+ // PreFilterExtensions returns prefilter extensions, pod add and remove.
158291func (pl * VolumeRestrictions ) PreFilterExtensions () framework.PreFilterExtensions {
159- return nil
292+ return pl
160293}
161294
162295// Filter invoked at the filter extension point.
@@ -168,21 +301,20 @@ func (pl *VolumeRestrictions) PreFilterExtensions() framework.PreFilterExtension
168301// - AWS EBS forbids any two pods mounting the same volume ID
169302// - Ceph RBD forbids if any two pods share at least same monitor, and match pool and image, and the image is read-only
170303// - ISCSI forbids if any two pods share at least same IQN and ISCSI volume is read-only
171- func (pl * VolumeRestrictions ) Filter (ctx context.Context , _ * framework.CycleState , pod * v1.Pod , nodeInfo * framework.NodeInfo ) * framework.Status {
172- for i := range pod .Spec .Volumes {
173- v := & pod .Spec .Volumes [i ]
174- // fast path if there is no conflict checking targets.
175- if v .GCEPersistentDisk == nil && v .AWSElasticBlockStore == nil && v .RBD == nil && v .ISCSI == nil {
176- continue
177- }
178-
179- for _ , ev := range nodeInfo .Pods {
180- if isVolumeConflict (v , ev .Pod ) {
181- return framework .NewStatus (framework .Unschedulable , ErrReasonDiskConflict )
182- }
183- }
304+ // If the pod uses PVCs with the ReadWriteOncePod access mode, it evaluates if
305+ // these PVCs are already in-use and if preemption will help.
306+ func (pl * VolumeRestrictions ) Filter (ctx context.Context , cycleState * framework.CycleState , pod * v1.Pod , nodeInfo * framework.NodeInfo ) * framework.Status {
307+ if ! satisfyVolumeConflicts (pod , nodeInfo ) {
308+ return framework .NewStatus (framework .Unschedulable , ErrReasonDiskConflict )
184309 }
185- return nil
310+ if ! pl .enableReadWriteOncePod {
311+ return nil
312+ }
313+ state , err := getPreFilterState (cycleState )
314+ if err != nil {
315+ return framework .AsStatus (err )
316+ }
317+ return satisfyReadWriteOncePod (ctx , state )
186318}
187319
188320// EventsToRegister returns the possible events that may make a Pod
0 commit comments