7
7
import io .fabric8 .kubernetes .client .Watcher ;
8
8
import org .slf4j .Logger ;
9
9
import org .slf4j .LoggerFactory ;
10
- import org .springframework .util .backoff .BackOffExecution ;
11
- import org .springframework .util .backoff .ExponentialBackOff ;
12
10
13
- import java .util .HashMap ;
14
- import java .util .Map ;
11
+ import java .util .Optional ;
15
12
import java .util .concurrent .ScheduledFuture ;
16
13
import java .util .concurrent .ScheduledThreadPoolExecutor ;
17
14
import java .util .concurrent .ThreadFactory ;
18
15
import java .util .concurrent .TimeUnit ;
19
16
import java .util .concurrent .locks .ReentrantLock ;
20
17
18
+ import static com .github .containersolutions .operator .processing .CustomResourceEvent .MAX_RETRY_COUNT ;
19
+
21
20
22
21
/**
23
22
* Requirements:
45
44
46
45
public class EventScheduler <R extends CustomResource > implements Watcher <R > {
47
46
48
- // todo limit number of back offs
49
- private final static ExponentialBackOff backOff = new ExponentialBackOff (2000L , 1.5 );
50
-
51
47
private final static Logger log = LoggerFactory .getLogger (EventScheduler .class );
48
+
52
49
private final EventDispatcher eventDispatcher ;
53
50
private final ScheduledThreadPoolExecutor executor ;
54
- private final HashMap <CustomResourceEvent , BackOffExecution > backoffSchedulerCache = new HashMap <>();
55
-
56
- private final Map <String , CustomResourceEvent > eventsNotScheduledYet = new HashMap <>();
57
- private final Map <String , ResourceScheduleHolder > eventsScheduledForProcessing = new HashMap <>();
58
- private final Map <String , CustomResourceEvent > eventsUnderProcessing = new HashMap <>();
51
+ private final EventStore eventStore = new EventStore ();
59
52
60
53
private ReentrantLock lock = new ReentrantLock ();
61
54
@@ -86,46 +79,49 @@ void scheduleEvent(CustomResourceEvent newEvent) {
86
79
lock .lock ();
87
80
if (newEvent .getAction () == Action .DELETED ) {
88
81
// this is a tricky situation, do we want to process only events which are marked for deletion?
89
- // or just ignore the problem
82
+ // or just ignore the problem. Note that marked for deletion event should already be the last event either
83
+ // under processing, or scheduled for it.
84
+ // There could be some corner case when we do have a event which we received before marked for deletion,
85
+ // and did not received the marked for deletion, but this is such corner case that for sake of simplicity will ignore this.
90
86
return ;
91
87
}
92
88
// if there is an event waiting for to be scheduled we just replace that.
93
- if (eventsNotScheduledYet .containsKey (newEvent .resourceUid ()) &&
94
- newEvent .isSameResourceAndNewerVersion (eventsNotScheduledYet .get (newEvent .resourceUid ()))) {
95
- log .debug ("Replacing event which is not scheduled yet, since incoming event is more recent. new Event:{}"
96
- , newEvent );
97
- eventsNotScheduledYet .put (newEvent .resourceUid (), newEvent );
89
+ if (eventStore .containsOlderVersionOfNotScheduledEvent (newEvent )) {
90
+ log .debug ("Replacing event which is not scheduled yet, since incoming event is more recent. new Event:{}" , newEvent );
91
+ eventStore .addOrReplaceEventAsNotScheduledYet (newEvent );
98
92
return ;
99
- } else if ( eventsUnderProcessing . containsKey ( newEvent . resourceUid ()) &&
100
- newEvent . isSameResourceAndNewerVersion ( eventsUnderProcessing . get (newEvent . resourceUid ()) )) {
93
+ }
94
+ if ( eventStore . containsOlderVersionOfEventUnderProcessing (newEvent )) {
101
95
log .debug ("Scheduling event for later processing since there is an event under processing for same kind." +
102
96
" New event: {}" , newEvent );
103
- eventsNotScheduledYet . put ( newEvent . resourceUid (), newEvent );
97
+ eventStore . addOrReplaceEventAsNotScheduledYet ( newEvent );
104
98
return ;
105
99
}
106
-
107
- if (eventsScheduledForProcessing .containsKey (newEvent .resourceUid ())) {
108
- ResourceScheduleHolder scheduleHolder = eventsScheduledForProcessing .get (newEvent .resourceUid ());
100
+ if (eventStore .containsEventScheduledForProcessing (newEvent .resourceUid ())) {
101
+ EventStore .ResourceScheduleHolder scheduleHolder = eventStore .getEventScheduledForProcessing (newEvent .resourceUid ());
109
102
CustomResourceEvent scheduledEvent = scheduleHolder .getCustomResourceEvent ();
110
103
ScheduledFuture <?> scheduledFuture = scheduleHolder .getScheduledFuture ();
111
- // If newEvent is newer than existing in queue, cancel and remove queuedEvent
112
- if (newEvent .isSameResourceAndNewerVersion (scheduledEvent )) {
113
- log .debug ("Queued event canceled because incoming event is newer. [{}]" , scheduledEvent );
114
- scheduledFuture .cancel (false );
115
- eventsScheduledForProcessing .remove (scheduledEvent .resourceUid ());
116
- }
117
104
// If newEvent is older than existing in queue, don't schedule and remove from cache
118
105
if (scheduledEvent .isSameResourceAndNewerVersion (newEvent )) {
119
- log .debug ("Incoming event discarded because queued event is newer. [{}] " , newEvent );
106
+ log .debug ("Incoming event discarded because queued event is newer. {} " , newEvent );
120
107
return ;
121
108
}
109
+ // If newEvent is newer than existing in queue, cancel and remove queuedEvent
110
+ if (newEvent .isSameResourceAndNewerVersion (scheduledEvent )) {
111
+ log .debug ("Queued event canceled because incoming event is newer. {}" , scheduledEvent );
112
+ scheduledFuture .cancel (false );
113
+ eventStore .removeEventScheduledForProcessing (scheduledEvent .resourceUid ());
114
+ }
122
115
}
123
116
124
- // todo handle backoff instances
125
- backoffSchedulerCache .put (newEvent , backOff .start ());
117
+ Optional <Long > nextBackOff = newEvent .nextBackOff ();
118
+ if (!nextBackOff .isPresent ()) {
119
+ log .warn ("Event limited max retry limit ({}), will be discarded. {}" , MAX_RETRY_COUNT , newEvent );
120
+ return ;
121
+ }
126
122
ScheduledFuture <?> scheduledTask = executor .schedule (new EventConsumer (newEvent , eventDispatcher , this ),
127
- newEvent . getRetryIndex () < 1 ? 0 : backoffSchedulerCache . get ( newEvent ). nextBackOff (), TimeUnit .MILLISECONDS );
128
- eventsScheduledForProcessing . put ( newEvent . resourceUid (), new ResourceScheduleHolder (newEvent , scheduledTask ));
123
+ nextBackOff . get (), TimeUnit .MILLISECONDS );
124
+ eventStore . addEventScheduledForProcessing ( new EventStore . ResourceScheduleHolder (newEvent , scheduledTask ));
129
125
} finally {
130
126
lock .unlock ();
131
127
}
@@ -134,15 +130,15 @@ void scheduleEvent(CustomResourceEvent newEvent) {
134
130
boolean eventProcessingStarted (CustomResourceEvent event ) {
135
131
try {
136
132
lock .lock ();
137
- ResourceScheduleHolder res = eventsScheduledForProcessing . remove (event .resourceUid ());
133
+ EventStore . ResourceScheduleHolder res = eventStore . removeEventScheduledForProcessing (event .resourceUid ());
138
134
if (res == null ) {
139
135
// if its still scheduled for processing.
140
136
// note that it can happen that we scheduled an event for processing, it took some time that is was picked
141
137
// by executor, and it was removed during that time from the schedule but not cancelled yet. So to be correct
142
138
// this should be checked also here. In other word scheduleEvent function can run in parallel with eventDispatcher.
143
139
return false ;
144
140
}
145
- eventsUnderProcessing . put ( event . resourceUid (), event );
141
+ eventStore . addEventUnderProcessing ( event );
146
142
return true ;
147
143
} finally {
148
144
lock .unlock ();
@@ -152,10 +148,8 @@ boolean eventProcessingStarted(CustomResourceEvent event) {
152
148
void eventProcessingFinishedSuccessfully (CustomResourceEvent event ) {
153
149
try {
154
150
lock .lock ();
155
- eventsUnderProcessing .remove (event .resourceUid ());
156
- backoffSchedulerCache .remove (event );
157
-
158
- CustomResourceEvent notScheduledYetEvent = eventsNotScheduledYet .remove (event .resourceUid ());
151
+ eventStore .removeEventUnderProcessing (event .resourceUid ());
152
+ CustomResourceEvent notScheduledYetEvent = eventStore .removeEventNotScheduledYet (event .resourceUid ());
159
153
if (notScheduledYetEvent != null ) {
160
154
scheduleEvent (notScheduledYetEvent );
161
155
}
@@ -167,7 +161,7 @@ void eventProcessingFinishedSuccessfully(CustomResourceEvent event) {
167
161
void eventProcessingFailed (CustomResourceEvent event ) {
168
162
try {
169
163
lock .lock ();
170
- eventsUnderProcessing . remove (event );
164
+ eventStore . removeEventUnderProcessing (event . resourceUid () );
171
165
scheduleEvent (event );
172
166
} finally {
173
167
lock .unlock ();
@@ -179,24 +173,6 @@ void eventProcessingFailed(CustomResourceEvent event) {
179
173
public void onClose (KubernetesClientException e ) {
180
174
// todo re apply the watch
181
175
}
182
-
183
- private static class ResourceScheduleHolder {
184
- private CustomResourceEvent customResourceEvent ;
185
- private ScheduledFuture <?> scheduledFuture ;
186
-
187
- public ResourceScheduleHolder (CustomResourceEvent customResourceEvent , ScheduledFuture <?> scheduledFuture ) {
188
- this .customResourceEvent = customResourceEvent ;
189
- this .scheduledFuture = scheduledFuture ;
190
- }
191
-
192
- public CustomResourceEvent getCustomResourceEvent () {
193
- return customResourceEvent ;
194
- }
195
-
196
- public ScheduledFuture <?> getScheduledFuture () {
197
- return scheduledFuture ;
198
- }
199
- }
200
176
}
201
177
202
178
0 commit comments