5eed2e1ff1fe1428792803b41a74a92c5ac8a1e4
[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 instruction : this.dependencies) {
128             if (instruction.getStatus() != InstructionStatus.Successful) {
129                 ids.add(instruction.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 instruction : this.dependants) {
139             instruction.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             this.executionFuture.set(result);
201         }
202     }
203
204     synchronized void addDependant(final InstructionImpl instruction) {
205         this.dependants.add(instruction);
206     }
207
208     private synchronized void removeDependant(final InstructionImpl instruction) {
209         this.dependants.remove(instruction);
210     }
211
212     private synchronized void removeDependency(final InstructionImpl other) {
213         this.dependencies.remove(other);
214     }
215
216     synchronized Iterator<InstructionImpl> getDependants() {
217         return this.dependants.iterator();
218     }
219
220     synchronized void clean() {
221         for (final InstructionImpl dependency : this.dependencies) {
222             dependency.removeDependant(this);
223         }
224         this.dependencies.clear();
225
226         for (final InstructionImpl dependant : this.dependants) {
227             dependant.removeDependency(this);
228         }
229         this.dependants.clear();
230
231         this.queue.instructionRemoved();
232     }
233
234     private Boolean checkDependencies() {
235         boolean ready = true;
236         final List<InstructionId> unmet = new ArrayList<>();
237         for (final InstructionImpl instruction : this.dependencies) {
238             switch (instruction.getStatus()) {
239                 case Cancelled:
240                 case Failed:
241                 case Unknown:
242                     unmet.add(instruction.getId());
243                     break;
244                 case Executing:
245                 case Queued:
246                 case Scheduled:
247                     ready = false;
248                     break;
249                 case Successful:
250                     // No-op
251                     break;
252                 default:
253                     break;
254             }
255         }
256         if (!unmet.isEmpty()) {
257             LOG.warn("Instruction {} was Queued, while some dependencies were resolved unsuccessfully, cancelling it",
258                     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 }