|
5 | 5 | import java.util.Map;
|
6 | 6 | import java.util.Optional;
|
7 | 7 | import java.util.Set;
|
| 8 | +import java.util.UUID; |
8 | 9 | import java.util.function.Function;
|
9 | 10 | import java.util.stream.Collectors;
|
10 | 11 |
|
|
18 | 19 | import io.javaoperatorsdk.operator.api.config.ConfigurationService;
|
19 | 20 | import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
|
20 | 21 | import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
|
21 |
| -import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationEventFilter; |
| 22 | +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; |
22 | 23 | import io.javaoperatorsdk.operator.processing.event.Event;
|
23 | 24 | import io.javaoperatorsdk.operator.processing.event.EventHandler;
|
24 | 25 | import io.javaoperatorsdk.operator.processing.event.ResourceID;
|
|
72 | 73 | */
|
73 | 74 | public class InformerEventSource<R extends HasMetadata, P extends HasMetadata>
|
74 | 75 | extends ManagedInformerEventSource<R, P, InformerConfiguration<R>>
|
75 |
| - implements ResourceEventHandler<R>, RecentOperationEventFilter<R> { |
| 76 | + implements ResourceEventHandler<R> { |
76 | 77 |
|
77 | 78 | private static final Logger log = LoggerFactory.getLogger(InformerEventSource.class);
|
78 | 79 |
|
79 | 80 | private final InformerConfiguration<R> configuration;
|
80 |
| - // always called from a synchronized method |
81 |
| - private final EventRecorder<R> eventRecorder = new EventRecorder<>(); |
82 | 81 | // we need direct control for the indexer to propagate the just update resource also to the index
|
83 | 82 | private final PrimaryToSecondaryIndex<R> primaryToSecondaryIndex;
|
84 | 83 | private final PrimaryToSecondaryMapper<P> primaryToSecondaryMapper;
|
85 | 84 | private Map<String, Function<R, List<String>>> indexerBuffer = new HashMap<>();
|
| 85 | + private String id = UUID.randomUUID().toString(); |
86 | 86 |
|
87 | 87 | public InformerEventSource(
|
88 | 88 | InformerConfiguration<R> configuration, EventSourceContext<P> context) {
|
@@ -110,6 +110,10 @@ public InformerEventSource(InformerConfiguration<R> configuration, KubernetesCli
|
110 | 110 | genericFilter = configuration.genericFilter().orElse(null);
|
111 | 111 | }
|
112 | 112 |
|
| 113 | + public String getId() { |
| 114 | + return id; |
| 115 | + } |
| 116 | + |
113 | 117 | @Override
|
114 | 118 | public void onAdd(R newResource) {
|
115 | 119 | if (log.isDebugEnabled()) {
|
@@ -154,12 +158,22 @@ public void onDelete(R resource, boolean b) {
|
154 | 158 | private synchronized void onAddOrUpdate(Operation operation, R newObject, R oldObject,
|
155 | 159 | Runnable superOnOp) {
|
156 | 160 | var resourceID = ResourceID.fromResource(newObject);
|
157 |
| - if (eventRecorder.isRecordingFor(resourceID)) { |
158 |
| - log.debug("Recording event for: {}", resourceID); |
159 |
| - eventRecorder.recordEvent(newObject); |
160 |
| - return; |
| 161 | + |
| 162 | + String previous = newObject.getMetadata().getAnnotations() |
| 163 | + .get(KubernetesDependentResource.PREVIOUS_ANNOTATION_KEY); |
| 164 | + boolean known = false; |
| 165 | + if (previous != null) { |
| 166 | + String[] parts = previous.split(","); |
| 167 | + if (id.equals(parts[0])) { |
| 168 | + if (oldObject == null && parts.length == 1) { |
| 169 | + known = true; |
| 170 | + } else if (oldObject != null && parts.length == 2 |
| 171 | + && oldObject.getMetadata().getResourceVersion().equals(parts[1])) { |
| 172 | + known = true; |
| 173 | + } |
| 174 | + } |
161 | 175 | }
|
162 |
| - if (temporaryCacheHasResourceWithSameVersionAs(newObject)) { |
| 176 | + if (known || temporaryCacheHasResourceWithSameVersionAs(newObject)) { |
163 | 177 | log.debug(
|
164 | 178 | "Skipping event propagation for {}, since was a result of a reconcile action. Resource ID: {}",
|
165 | 179 | operation,
|
@@ -239,99 +253,37 @@ public InformerConfiguration<R> getConfiguration() {
|
239 | 253 | @Override
|
240 | 254 | public synchronized void handleRecentResourceUpdate(ResourceID resourceID, R resource,
|
241 | 255 | R previousVersionOfResource) {
|
242 |
| - handleRecentCreateOrUpdate(Operation.UPDATE, resource, previousVersionOfResource, |
243 |
| - () -> super.handleRecentResourceUpdate(resourceID, resource, previousVersionOfResource)); |
| 256 | + handleRecentCreateOrUpdate(Operation.UPDATE, resource, previousVersionOfResource); |
244 | 257 | }
|
245 | 258 |
|
246 | 259 | @Override
|
247 | 260 | public synchronized void handleRecentResourceCreate(ResourceID resourceID, R resource) {
|
248 |
| - handleRecentCreateOrUpdate(Operation.ADD, resource, null, |
249 |
| - () -> super.handleRecentResourceCreate(resourceID, resource)); |
| 261 | + handleRecentCreateOrUpdate(Operation.ADD, resource, null); |
250 | 262 | }
|
251 | 263 |
|
252 |
| - private void handleRecentCreateOrUpdate(Operation operation, R resource, R oldResource, |
253 |
| - Runnable runnable) { |
254 |
| - primaryToSecondaryIndex.onAddOrUpdate(resource); |
255 |
| - if (eventRecorder.isRecordingFor(ResourceID.fromResource(resource))) { |
256 |
| - handleRecentResourceOperationAndStopEventRecording(operation, resource, oldResource); |
257 |
| - } else { |
258 |
| - runnable.run(); |
259 |
| - } |
260 |
| - } |
261 |
| - |
262 |
| - /** |
263 |
| - * There can be the following cases: |
264 |
| - * <ul> |
265 |
| - * <li>1. Did not receive the event yet for the target resource, then we need to put it to temp |
266 |
| - * cache. Because event will arrive. Note that this not necessary mean that the even is not sent |
267 |
| - * yet (we are in sync context). Also does not mean that there are no more events received after |
268 |
| - * that. But during the event processing (onAdd, onUpdate) we make sure that the propagation just |
269 |
| - * skipped for the right event.</li> |
270 |
| - * <li>2. Received the event about the operation already, it was the last. This means already is |
271 |
| - * on cache of informer. So we have to do nothing. Since it was just recorded and not propagated. |
272 |
| - * </li> |
273 |
| - * <li>3. Received the event but more events received since, so those were not propagated yet. So |
274 |
| - * an event needs to be propagated to compensate.</li> |
275 |
| - * </ul> |
276 |
| - * |
277 |
| - * @param newResource just created or updated resource |
278 |
| - */ |
279 |
| - private void handleRecentResourceOperationAndStopEventRecording(Operation operation, |
280 |
| - R newResource, R oldResource) { |
| 264 | + private void handleRecentCreateOrUpdate(Operation operation, R newResource, R oldResource) { |
| 265 | + primaryToSecondaryIndex.onAddOrUpdate(newResource); |
281 | 266 | ResourceID resourceID = ResourceID.fromResource(newResource);
|
282 |
| - try { |
283 |
| - if (!eventRecorder.containsEventWithResourceVersion( |
284 |
| - resourceID, newResource.getMetadata().getResourceVersion())) { |
285 |
| - log.debug( |
286 |
| - "Did not found event in buffer with target version and resource id: {}", resourceID); |
287 |
| - temporaryResourceCache.unconditionallyCacheResource(newResource); |
288 |
| - } else { |
289 |
| - // if the resource is not added to the temp cache, it is cleared, since |
290 |
| - // the cache is cleared by subsequent events after updates, but if those did not receive |
291 |
| - // the temp cache is still filled at this point with an old resource |
292 |
| - log.debug("Cleaning temporary cache for resource id: {}", resourceID); |
293 |
| - temporaryResourceCache.removeResourceFromCache(newResource); |
294 |
| - if (eventRecorder.containsEventWithVersionButItsNotLastOne( |
295 |
| - resourceID, newResource.getMetadata().getResourceVersion())) { |
296 |
| - R lastEvent = eventRecorder.getLastEvent(resourceID); |
297 |
| - |
298 |
| - log.debug( |
299 |
| - "Found events in event buffer but the target event is not last for id: {}. Propagating event.", |
300 |
| - resourceID); |
301 |
| - if (eventAcceptedByFilter(operation, newResource, oldResource)) { |
302 |
| - propagateEvent(lastEvent); |
303 |
| - } |
304 |
| - } |
305 |
| - } |
306 |
| - } finally { |
307 |
| - log.debug("Stopping event recording for: {}", resourceID); |
308 |
| - eventRecorder.stopEventRecording(resourceID); |
| 267 | + R current = get(resourceID).orElse(null); |
| 268 | + if ((oldResource == null && current == null) || (current != null && oldResource != null |
| 269 | + && current.getMetadata().getResourceVersion().equals(oldResource.getMetadata().getResourceVersion()))) { |
| 270 | + log.debug( |
| 271 | + "Temporarily moving ahead to target version {} for resource id: {}", |
| 272 | + newResource.getMetadata().getResourceVersion(), resourceID); |
| 273 | + temporaryResourceCache.unconditionallyCacheResource(newResource); |
| 274 | + } else { |
| 275 | + // if the resource is not added to the temp cache, it is cleared, since |
| 276 | + // the cache is cleared by subsequent events after updates, but if those did not receive |
| 277 | + // the temp cache is still filled at this point with an old resource |
| 278 | + log.debug("Cleaning temporary cache for resource id: {}", resourceID); |
| 279 | + temporaryResourceCache.removeResourceFromCache(newResource); |
309 | 280 | }
|
310 | 281 | }
|
311 | 282 |
|
312 | 283 | private boolean useSecondaryToPrimaryIndex() {
|
313 | 284 | return this.primaryToSecondaryMapper == null;
|
314 | 285 | }
|
315 | 286 |
|
316 |
| - @Override |
317 |
| - public synchronized void prepareForCreateOrUpdateEventFiltering(ResourceID resourceID, |
318 |
| - R resource) { |
319 |
| - log.debug("Starting event recording for: {}", resourceID); |
320 |
| - eventRecorder.startEventRecording(resourceID); |
321 |
| - } |
322 |
| - |
323 |
| - /** |
324 |
| - * Mean to be called to clean up in case of an exception from the client. Usually in a catch |
325 |
| - * block. |
326 |
| - * |
327 |
| - * @param resourceID to cleanup |
328 |
| - */ |
329 |
| - @Override |
330 |
| - public synchronized void cleanupOnCreateOrUpdateEventFiltering(ResourceID resourceID) { |
331 |
| - log.debug("Stopping event recording for: {}", resourceID); |
332 |
| - eventRecorder.stopEventRecording(resourceID); |
333 |
| - } |
334 |
| - |
335 | 287 | @Override
|
336 | 288 | public boolean allowsNamespaceChanges() {
|
337 | 289 | return getConfiguration().followControllerNamespaceChanges();
|
@@ -361,13 +313,15 @@ private boolean acceptedByDeleteFilters(R resource, boolean b) {
|
361 | 313 |
|
362 | 314 | // Since this event source instance is created by the user, the ConfigurationService is actually
|
363 | 315 | // injected after it is registered. Some of the subcomponents are initialized at that time here.
|
| 316 | + @Override |
364 | 317 | public void setConfigurationService(ConfigurationService configurationService) {
|
365 | 318 | super.setConfigurationService(configurationService);
|
366 | 319 |
|
367 | 320 | cache.addIndexers(indexerBuffer);
|
368 | 321 | indexerBuffer = null;
|
369 | 322 | }
|
370 | 323 |
|
| 324 | + @Override |
371 | 325 | public void addIndexers(Map<String, Function<R, List<String>>> indexers) {
|
372 | 326 | if (indexerBuffer == null) {
|
373 | 327 | throw new OperatorException("Cannot add indexers after InformerEventSource started.");
|
|
0 commit comments