Replace Preconditions.CheckNotNull per RequireNonNull
[bgpcep.git] / programming / impl / src / main / java / org / opendaylight / bgpcep / programming / impl / InstructionImpl.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.bgpcep.programming.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.SettableFuture;
16 import io.netty.util.Timeout;
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 import java.util.List;
20 import javax.annotation.concurrent.GuardedBy;
21 import org.opendaylight.bgpcep.programming.spi.ExecutionResult;
22 import org.opendaylight.bgpcep.programming.spi.Instruction;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.CancelFailure;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.InstructionId;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.InstructionStatus;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.UncancellableInstruction;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.instruction.status.changed.Details;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.programming.rev150720.instruction.status.changed.DetailsBuilder;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 final class InstructionImpl implements Instruction {
33     private static final Logger LOG = LoggerFactory.getLogger(InstructionImpl.class);
34     private final List<InstructionImpl> dependants = new ArrayList<>();
35     private final SettableFuture<Instruction> schedulingFuture;
36     private final List<InstructionImpl> dependencies;
37     private final QueueInstruction queue;
38     private final InstructionId id;
39     private SettableFuture<ExecutionResult<Details>> executionFuture;
40     private InstructionStatus status = InstructionStatus.Queued;
41     private Details heldUpDetails;
42     private Timeout timeout;
43
44     InstructionImpl(final QueueInstruction queue, final SettableFuture<Instruction> future, final InstructionId id,
45             final List<InstructionImpl> dependencies, final Timeout timeout) {
46         this.schedulingFuture = requireNonNull(future);
47         this.dependencies = requireNonNull(dependencies);
48         this.timeout = requireNonNull(timeout);
49         this.queue = requireNonNull(queue);
50         this.id = requireNonNull(id);
51     }
52
53     InstructionId getId() {
54         return this.id;
55     }
56
57     synchronized InstructionStatus getStatus() {
58         return this.status;
59     }
60
61     private synchronized void setStatus(final InstructionStatus status, final Details details) {
62         // Set the status
63         this.status = status;
64         LOG.debug("Instruction {} transitioned to status {}", this.id, status);
65
66         // Send out a notification
67         this.queue.instructionUpdated(status, details);
68
69         switch (status) {
70         case Cancelled:
71         case Failed:
72         case Unknown:
73             cancelDependants();
74             break;
75         case Executing:
76         case Queued:
77         case Scheduled:
78         case Successful:
79             break;
80         default:
81             break;
82         }
83     }
84
85     @GuardedBy("this")
86     private void cancelTimeout() {
87         if (this.timeout != null) {
88             this.timeout.cancel();
89             this.timeout = null;
90         }
91     }
92
93     public synchronized void timeout() {
94         if (this.timeout == null) {
95             return;
96         }
97         this.timeout = null;
98         switch (this.status) {
99         case Cancelled:
100         case Failed:
101         case Successful:
102             LOG.debug("Instruction {} has status {}, timeout is a no-op", this.id, this.status);
103             break;
104         case Unknown:
105             LOG.warn("Instruction {} has status {} before timeout completed", this.id, this.status);
106             break;
107         case Executing:
108             LOG.info("Instruction {} timed out while executing, transitioning into Unknown", this.id);
109             setStatus(InstructionStatus.Unknown, null);
110             cancelDependants();
111             break;
112         case Queued:
113             LOG.debug("Instruction {} timed out while Queued, cancelling it", this.id);
114             cancelInstrunction();
115             break;
116         case Scheduled:
117             LOG.debug("Instruction {} timed out while Scheduled, cancelling it", this.id);
118             cancel(this.heldUpDetails);
119             break;
120         default:
121             break;
122         }
123     }
124
125     private synchronized void cancelInstrunction() {
126         final List<InstructionId> ids = new ArrayList<>();
127         for (final InstructionImpl d : this.dependencies) {
128             if (d.getStatus() != InstructionStatus.Successful) {
129                 ids.add(d.getId());
130             }
131         }
132         cancel(new DetailsBuilder().setUnmetDependencies(ids).build());
133     }
134
135     @GuardedBy("this")
136     private void cancelDependants() {
137         final Details details = new DetailsBuilder().setUnmetDependencies(ImmutableList.of(this.id)).build();
138         for (final InstructionImpl d : this.dependants) {
139             d.tryCancel(details);
140         }
141     }
142
143     @GuardedBy("this")
144     private void cancel(final Details details) {
145         cancelTimeout();
146         this.schedulingFuture.cancel(false);
147         setStatus(InstructionStatus.Cancelled, details);
148     }
149
150     synchronized Class<? extends CancelFailure> tryCancel(final Details details) {
151         switch (this.status) {
152         case Cancelled:
153         case Executing:
154         case Failed:
155         case Successful:
156         case Unknown:
157             LOG.debug("Instruction {} can no longer be cancelled due to status {}", this.id, this.status);
158             return UncancellableInstruction.class;
159         case Queued:
160         case Scheduled:
161             cancel(details);
162             return null;
163         default:
164             throw new IllegalStateException("Unhandled instruction state " + this.status);
165         }
166     }
167
168     @Override
169     public synchronized boolean checkedExecutionStart() {
170         if (this.status != InstructionStatus.Scheduled) {
171             return false;
172         }
173
174         setStatus(InstructionStatus.Executing, null);
175         return true;
176     }
177
178     @Override
179     public synchronized boolean executionHeldUp(final Details details) {
180         if (this.status != InstructionStatus.Scheduled) {
181             return false;
182         }
183
184         this.heldUpDetails = details;
185         return true;
186     }
187
188     @Override
189     public void executionCompleted(final InstructionStatus status, final Details details) {
190         final ExecutionResult<Details> result;
191
192         synchronized (this) {
193             Preconditions.checkState(this.executionFuture != null);
194
195             cancelTimeout();
196
197             // We reuse the preconditions set down in this class
198             result = new ExecutionResult<>(status, details);
199             setStatus(status, details);
200         }
201
202         this.executionFuture.set(result);
203     }
204
205     synchronized void addDependant(final InstructionImpl d) {
206         this.dependants.add(d);
207     }
208
209     private synchronized void removeDependant(final InstructionImpl d) {
210         this.dependants.remove(d);
211     }
212
213     private synchronized void removeDependency(final InstructionImpl other) {
214         this.dependencies.remove(other);
215     }
216
217     synchronized Iterator<InstructionImpl> getDependants() {
218         return this.dependants.iterator();
219     }
220
221     synchronized void clean() {
222         for (final InstructionImpl dependency : this.dependencies) {
223             dependency.removeDependant(this);
224         }
225         this.dependencies.clear();
226
227         for (final InstructionImpl dependant : this.dependants) {
228             dependant.removeDependency(this);
229         }
230         this.dependants.clear();
231
232         this.queue.instructionRemoved();
233     }
234
235     private Boolean checkDependencies() {
236         boolean ready = true;
237         final List<InstructionId> unmet = new ArrayList<>();
238         for (final InstructionImpl d : this.dependencies) {
239             switch (d.getStatus()) {
240             case Cancelled:
241             case Failed:
242             case Unknown:
243                 unmet.add(d.getId());
244                 break;
245             case Executing:
246             case Queued:
247             case Scheduled:
248                 ready = false;
249                 break;
250             case Successful:
251                 // No-op
252                 break;
253             default:
254                 break;
255             }
256         }
257         if (!unmet.isEmpty()) {
258             LOG.warn("Instruction {} was Queued, while some dependencies were resolved unsuccessfully, cancelling it", this.id);
259             cancel(new DetailsBuilder().setUnmetDependencies(unmet).build());
260             return false;
261         }
262         return ready;
263     }
264
265     synchronized ListenableFuture<ExecutionResult<Details>> ready() {
266         Preconditions.checkState(this.status == InstructionStatus.Queued);
267         Preconditions.checkState(this.executionFuture == null);
268         /*
269          * Check all vertices we depend on. We start off as ready for
270          * scheduling. If we encounter a cancelled/failed/unknown
271          * dependency, we cancel this instruction (and cascade). If we
272          * encounter an executing/queued/scheduled dependency, we hold
273          * of scheduling this one.
274          */
275         if (!checkDependencies()) {
276             return null;
277         }
278         LOG.debug("Instruction {} is ready for execution", this.id);
279         setStatus(InstructionStatus.Scheduled, null);
280         this.executionFuture = SettableFuture.create();
281         this.schedulingFuture.set(this);
282         return this.executionFuture;
283     }
284 }